diff options
Diffstat (limited to 'packages')
108 files changed, 2469 insertions, 565 deletions
diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index 7ce7126cb..fa3b3db91 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -3,6 +3,7 @@ ## v0.34.0 - _TBD_ * Update Kovan EtherToken artifact address to match TokenRegistry. + * Fix the bug causing `zeroEx.exchange.fillOrdersUpToAsync` validation to fail if there were some extra orders passed (#470) ## v0.33.2 - _March 18, 2018_ diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index d1374468c..5f5b32aac 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -15,8 +15,6 @@ "build:watch": "tsc -w", "prebuild": "run-s clean generate_contract_wrappers", "build": "run-p build:umd:prod build:commonjs; exit 0;", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", - "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", "generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'", "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", "test:circleci": "run-s test:coverage", @@ -28,7 +26,10 @@ "build:umd:prod": "NODE_ENV=production webpack", "build:commonjs": "tsc && copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", "test:commonjs": "run-s build:commonjs run_mocha", - "run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit" + "run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit", + "docs:stage": "yarn build && node ./scripts/stage_docs.js", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" }, "config": { "artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken", @@ -38,11 +39,9 @@ "packages/0x.js/_bundles/index.min.js" ], "docPublishConfigs": { - "extraFileIncludes": [ - "../types/src/index.ts" - ], - "s3BucketPath": "s3://0xjs-docs-jsons/", - "s3StagingBucketPath": "s3://staging-0xjs-docs-jsons/" + "extraFileIncludes": ["../types/src/index.ts", "./src/contract_wrappers/generated/ether_token.ts", "./src/contract_wrappers/generated/token.ts", "./src/contract_wrappers/generated/exchange.ts"], + "s3BucketPath": "s3://doc-jsons/0x.js/", + "s3StagingBucketPath": "s3://staging-doc-jsons/0x.js/" } } }, diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index 09da4b046..0dd728ff1 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -83,7 +83,7 @@ export class ZeroEx { } /** * Generates a pseudo-random 256-bit salt. - * The salt can be included in an 0x order, ensuring that the order generates a unique orderHash + * The salt can be included in a 0x order, ensuring that the order generates a unique orderHash * and will not collide with other outstanding orders that are identical in all other parameters. * @return A pseudo-random 256-bit number that can be used as a salt. */ @@ -260,7 +260,7 @@ export class ZeroEx { msgHashHex = ethUtil.bufferToHex(msgHashBuff); } - const signature = await this._web3Wrapper.signTransactionAsync(normalizedSignerAddress, msgHashHex); + const signature = await this._web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex); // HACK: There is no consensus on whether the signatureHex string should be formatted as // v + r + s OR r + s + v, and different clients (even different versions of the same client) diff --git a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts index 42f8213a2..4a4da116b 100644 --- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts @@ -64,7 +64,7 @@ export class EtherTokenWrapper extends ContractWrapper { * equivalent number of wrapped ETH tokens. * @param etherTokenAddress EtherToken address you wish to withdraw from. * @param amountInWei Amount of ETH in Wei the caller wishes to withdraw. - * @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl. + * @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawal. * @param txOpts Transaction parameters. * @return Transaction hash. */ @@ -96,7 +96,7 @@ export class EtherTokenWrapper extends ContractWrapper { } /** * Gets historical logs without creating a subscription - * @param etherTokenAddress An address of the ether token that emmited the logs. + * @param etherTokenAddress An address of the ether token that emitted the logs. * @param eventName The ether token contract event you would like to subscribe to. * @param blockRange Block range to get logs from. * @param indexFilterValues An object where the keys are indexed args returned by the event and diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts index 6414985e6..59bd4db6b 100644 --- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts @@ -281,6 +281,9 @@ export class ExchangeWrapper extends ContractWrapper { zrxTokenAddress, ); filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount); + if (filledTakerTokenAmount.eq(fillTakerTokenAmount)) { + break; + } } } @@ -857,7 +860,7 @@ export class ExchangeWrapper extends ContractWrapper { return isRoundingError; } /** - * Checks if logs contain LogError, which is emmited by Exchange contract on transaction failure. + * Checks if logs contain LogError, which is emitted by Exchange contract on transaction failure. * @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync` */ public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>): void { diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts index 0f688cb71..e350dfb92 100644 --- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts @@ -385,7 +385,7 @@ export class TokenWrapper extends ContractWrapper { } /** * Gets historical logs without creating a subscription - * @param tokenAddress An address of the token that emmited the logs. + * @param tokenAddress An address of the token that emitted the logs. * @param eventName The token contract event you would like to subscribe to. * @param blockRange Block range to get logs from. * @param indexFilterValues An object where the keys are indexed args returned by the event and diff --git a/packages/0x.js/src/monorepo_scripts/stagedocs.ts b/packages/0x.js/src/monorepo_scripts/stage_docs.ts index e732ac8eb..e732ac8eb 100644 --- a/packages/0x.js/src/monorepo_scripts/stagedocs.ts +++ b/packages/0x.js/src/monorepo_scripts/stage_docs.ts diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts index f7515e59e..9cccadb7f 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -59,7 +59,7 @@ const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h /** * This class includes all the functionality related to watching a set of orders * for potential changes in order validity/fillability. The orderWatcher notifies - * the subscriber of these changes so that a final decison can be made on whether + * the subscriber of these changes so that a final decision can be made on whether * the order should be deemed invalid. */ export class OrderStateWatcher { diff --git a/packages/0x.js/test/0x.js_test.ts b/packages/0x.js/test/0x.js_test.ts index d1f601c92..d61847d1d 100644 --- a/packages/0x.js/test/0x.js_test.ts +++ b/packages/0x.js/test/0x.js_test.ts @@ -41,11 +41,11 @@ describe('ZeroEx library', () => { expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined(); // Check that all nested web3 wrapper instances return the updated provider - const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getCurrentProvider(); + const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getProvider(); expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number'); - const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider(); + const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getProvider(); expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number'); - const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getCurrentProvider(); + const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getProvider(); expect(tokenRegistryWeb3WrapperProvider.zeroExTestId).to.be.a('number'); }); }); diff --git a/packages/0x.js/test/ether_token_wrapper_test.ts b/packages/0x.js/test/ether_token_wrapper_test.ts index da6d95d07..6237ccf23 100644 --- a/packages/0x.js/test/ether_token_wrapper_test.ts +++ b/packages/0x.js/test/ether_token_wrapper_test.ts @@ -137,7 +137,7 @@ describe('EtherTokenWrapper', () => { gasCost = expectedETHBalance.minus(postETHBalance); expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); }); - it('should throw if user has insufficient WETH balance for withdrawl', async () => { + it('should throw if user has insufficient WETH balance for withdrawal', async () => { const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); expect(preWETHBalance).to.be.bignumber.equal(0); diff --git a/packages/0x.js/test/exchange_wrapper_test.ts b/packages/0x.js/test/exchange_wrapper_test.ts index 0a4ea608d..cfc390bae 100644 --- a/packages/0x.js/test/exchange_wrapper_test.ts +++ b/packages/0x.js/test/exchange_wrapper_test.ts @@ -596,6 +596,19 @@ describe('ExchangeWrapper', () => { const remainingFillAmount = fillableAmount.minus(1); expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); }); + it('should successfully fill up to specified amount and leave the rest of the orders untouched', async () => { + const txHash = await zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); + const zeroAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); + expect(filledAmount).to.be.bignumber.equal(fillableAmount); + expect(zeroAmount).to.be.bignumber.equal(0); + }); it('should successfully fill up to specified amount even if filling all orders would fail', async () => { const missingBalance = new BigNumber(1); // User will still have enough balance to fill up to 9, // but won't have 10 to fully fill all orders in a batch. diff --git a/packages/connect/package.json b/packages/connect/package.json index 8200019de..ae243f288 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -15,22 +15,23 @@ "build:watch": "tsc -w", "build": "tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", "clean": "shx rm -rf lib test_temp scripts", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", - "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", "copy_test_fixtures": "copyfiles -u 2 './test/fixtures/**/*.json' ./lib/test/fixtures", "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", "run_mocha": "mocha lib/test/**/*_test.js", "test": "run-s clean build copy_test_fixtures run_mocha", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "test:circleci": "yarn test:coverage" + "test:circleci": "yarn test:coverage", + "docs:stage": "yarn build && node ./scripts/stage_docs.js", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" }, "config": { "postpublish": { "assets": [], "docPublishConfigs": { - "s3BucketPath": "s3://connect-docs-jsons/", - "s3StagingBucketPath": "s3://staging-connect-docs-jsons/" + "s3BucketPath": "s3://doc-jsons/connect/", + "s3StagingBucketPath": "s3://staging-doc-jsons/connect/" } } }, diff --git a/packages/connect/src/monorepo_scripts/stagedocs.ts b/packages/connect/src/monorepo_scripts/stage_docs.ts index e732ac8eb..e732ac8eb 100644 --- a/packages/connect/src/monorepo_scripts/stagedocs.ts +++ b/packages/connect/src/monorepo_scripts/stage_docs.ts diff --git a/packages/contracts/package.json b/packages/contracts/package.json index a51e3da47..377fba473 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -57,6 +57,7 @@ "ethers-typescript-typings": "^0.0.4", "mocha": "^4.0.1", "npm-run-all": "^4.1.2", + "prettier": "^1.11.1", "shx": "^0.2.2", "solc": "^0.4.18", "tslint": "5.8.0", diff --git a/packages/deployer/README.md b/packages/deployer/README.md index 9abcfa6fe..8fe82f59e 100644 --- a/packages/deployer/README.md +++ b/packages/deployer/README.md @@ -2,66 +2,34 @@ This repository contains a CLI tool that facilitates compiling and deployment of smart contracts. +### Read the [Documentation](0xproject.com/docs/deployer). + ## Installation +#### CLI Installation + ```bash -yarn add @0xproject/deployer +yarn global add @0xproject/deployer ``` -## Usage - -### CLI Usage +#### API Installation ```bash -node ./node_modules/@0xproject/deployer/lib/cli.js --help -cli.js [command] - -Commands: - cli.js compile compile contracts - cli.js deploy deploy a single contract with provided arguments - -Options: - --version Show version number [boolean] - --contracts-dir path of contracts directory to compile - [string] [default: "/Users/leonidlogvinov/Dev/0x/contracts"] - --network-id mainnet=1, kovan=42, testrpc=50 [number] [default: 50] - --should-optimize enable optimizer [boolean] [default: false] - --artifacts-dir path to write contracts artifacts to - [string] [default: "/Users/leonidlogvinov/Dev/0x/build/artifacts/"] - --jsonrpc-port port connected to JSON RPC [number] [default: 8545] - --gas-price gasPrice to be used for transactions - [string] [default: "2000000000"] - --account account to use for deploying contracts [string] - --help Show help [boolean] +yarn add @0xproject/deployer ``` -### API Usage - -## Migrations - -You might want to write a migration scripts (similar to `truffle migrate`), that deploys multiple contracts and configures them. Below you'll find a simple example of such a script to help you get started. +**Import** ```typescript -import { Deployer } from '@0xproject/deployer'; -import * as path from 'path'; - -const deployerOpts = { - artifactsDir: path.resolve('src', 'artifacts'), - jsonrpcUrl: 'http://localhost:8545', - networkId: 50, - defaults: { - gas: 1000000, - }, -}; - -const deployer = new Deployer(deployerOpts); - -(async () => { - const etherToken = await deployer.deployAndSaveAsync('WETH9'); -})().catch(console.log); +import { Deployer, Compiler } from '@0xproject/deployer'; ``` -A more sophisticated example can be found [here](https://github.com/0xProject/0x-monorepo/tree/development/packages/contracts/migrations) +or + +```javascript +var Deployer = require('@0xproject/deployer').Deployer; +var Compiler = require('@0xproject/deployer').Compiler; +``` ## Contributing diff --git a/packages/deployer/package.json b/packages/deployer/package.json index 3f0613677..a8b847cd0 100644 --- a/packages/deployer/package.json +++ b/packages/deployer/package.json @@ -15,7 +15,20 @@ "clean": "shx rm -rf lib scripts", "migrate": "npm run build; node lib/src/cli.js migrate", "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", - "test:circleci": "yarn test:coverage" + "test:circleci": "yarn test:coverage", + "docs:stage": "yarn build && node ./scripts/stage_docs.js", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" + }, + "config": { + "postpublish": { + "assets": [], + "docPublishConfigs": { + "extraFileIncludes": ["../types/src/index.ts"], + "s3BucketPath": "s3://doc-jsons/deployer/", + "s3StagingBucketPath": "s3://staging-doc-jsons/deployer/" + } + } }, "bin": { "0x-deployer": "lib/src/cli.js" @@ -46,6 +59,7 @@ "shx": "^0.2.2", "tslint": "5.8.0", "types-bn": "^0.0.1", + "typedoc": "0xProject/typedoc", "typescript": "2.7.1", "web3-typescript-typings": "^0.10.2" }, diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index 712169c89..1f521dca1 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -33,6 +33,10 @@ import { utils } from './utils/utils'; const ALL_CONTRACTS_IDENTIFIER = '*'; +/** + * The Compiler facilitates compiling Solidity smart contracts and saves the results + * to artifact files. + */ export class Compiler { private _contractsDir: string; private _networkId: number; @@ -96,7 +100,7 @@ export class Compiler { this._specifiedContracts = opts.specifiedContracts; } /** - * Compiles selected Solidity files and writes JSON artifacts to artifactsDir. + * Compiles selected Solidity files found in `contractsDir` and writes JSON artifacts to `artifactsDir`. */ public async compileAsync(): Promise<void> { await createArtifactsDirIfDoesNotExistAsync(this._artifactsDir); diff --git a/packages/deployer/src/deployer.ts b/packages/deployer/src/deployer.ts index 6c247d328..68518a931 100644 --- a/packages/deployer/src/deployer.ts +++ b/packages/deployer/src/deployer.ts @@ -19,12 +19,21 @@ import { utils } from './utils/utils'; // Gas added to gas estimate to make sure there is sufficient gas for deployment. const EXTRA_GAS = 200000; +/** + * The Deployer facilitates deploying Solidity smart contracts to the blockchain. + * It can be used to build custom migration scripts. + */ export class Deployer { public web3Wrapper: Web3Wrapper; private _artifactsDir: string; private _networkId: number; private _defaults: Partial<TxData>; + /** + * Instantiate a new instance of the Deployer class. + * @param opts Deployer options, including either an RPC url or Provider instance. + * @returns A Deployer instance + */ constructor(opts: DeployerOptions) { this._artifactsDir = opts.artifactsDir; this._networkId = opts.networkId; @@ -42,8 +51,8 @@ export class Deployer { this.web3Wrapper = new Web3Wrapper(web3Provider, this._defaults); } /** - * Loads contract artifact and deploys contract with given arguments. - * @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory. + * Loads a contract's corresponding artifacts and deploys it with the supplied constructor arguments. + * @param contractName Name of the contract to deploy. Must match name of an artifact in supplied artifacts directory. * @param args Array of contract constructor arguments. * @return Deployed contract instance. */ @@ -80,7 +89,8 @@ export class Deployer { return contractInstance; } /** - * Loads contract artifact, deploys with given arguments, and saves updated data to artifact. + * Loads a contract's artifact, deploys it with supplied constructor arguments, and saves the updated data + * back to the artifact file. * @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory. * @param args Array of contract constructor arguments. * @return Deployed contract instance. diff --git a/packages/deployer/src/monorepo_scripts/stage_docs.ts b/packages/deployer/src/monorepo_scripts/stage_docs.ts new file mode 100644 index 000000000..e732ac8eb --- /dev/null +++ b/packages/deployer/src/monorepo_scripts/stage_docs.ts @@ -0,0 +1,8 @@ +import { postpublishUtils } from '@0xproject/monorepo-scripts'; + +import * as packageJSON from '../package.json'; +import * as tsConfigJSON from '../tsconfig.json'; + +const cwd = `${__dirname}/..`; +// tslint:disable-next-line:no-floating-promises +postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/json-schemas/README.md b/packages/json-schemas/README.md index 754ce4e95..f320196e9 100644 --- a/packages/json-schemas/README.md +++ b/packages/json-schemas/README.md @@ -2,25 +2,24 @@ Contains 0x-related json schemas +### Read the [Documentation](0xproject.com/docs/json-schemas). + ## Installation ```bash yarn add @0xproject/json-schemas ``` -## Usage +**Import** ```javascript -import {SchemaValidator, ValidatorResult, schemas} from '@0xproject/json-schemas'; +import { schemas } from '@0xproject/json-schemas'; +``` -const {orderSchema} = schemas; -const validator = new SchemaValidator(); +or -const order = { - ... -}; -const validatorResult: ValidatorResult = validator.validate(order, orderSchema); // Contains all errors -const isValid: boolean = validator.isValid(order, orderSchema); // Only returns boolean +```javascript +var schemas = require('@0xproject/json-schemas').schemas; ``` ## Contributing diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json index 1df34a4ef..1266f964b 100644 --- a/packages/json-schemas/package.json +++ b/packages/json-schemas/package.json @@ -13,7 +13,20 @@ "test:circleci": "yarn test:coverage", "run_mocha": "mocha lib/test/**/*_test.js", "clean": "shx rm -rf lib test_temp scripts", - "build": "tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts" + "build": "tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", + "docs:stage": "yarn build && node ./scripts/stage_docs.js", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" + }, + "config": { + "postpublish": { + "assets": [], + "docPublishConfigs": { + "extraFileIncludes": ["../types/src/index.ts"], + "s3BucketPath": "s3://doc-jsons/json-schemas/", + "s3StagingBucketPath": "s3://staging-doc-jsons/json-schemas/" + } + } }, "repository": { "type": "git", @@ -46,6 +59,7 @@ "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.8.0", + "typedoc": "0xProject/typedoc", "typescript": "2.7.1" }, "publishConfig": { diff --git a/packages/json-schemas/src/monorepo_scripts/stage_docs.ts b/packages/json-schemas/src/monorepo_scripts/stage_docs.ts new file mode 100644 index 000000000..e732ac8eb --- /dev/null +++ b/packages/json-schemas/src/monorepo_scripts/stage_docs.ts @@ -0,0 +1,8 @@ +import { postpublishUtils } from '@0xproject/monorepo-scripts'; + +import * as packageJSON from '../package.json'; +import * as tsConfigJSON from '../tsconfig.json'; + +const cwd = `${__dirname}/..`; +// tslint:disable-next-line:no-floating-promises +postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/json-schemas/src/schema_validator.ts b/packages/json-schemas/src/schema_validator.ts index e13326d2a..38ae766e6 100644 --- a/packages/json-schemas/src/schema_validator.ts +++ b/packages/json-schemas/src/schema_validator.ts @@ -3,14 +3,26 @@ import values = require('lodash.values'); import { schemas } from './schemas'; +/** + * A validator for [JSON-schemas](http://json-schema.org/) + */ export class SchemaValidator { private _validator: Validator; + /** + * Instantiates a SchemaValidator instance + */ constructor() { this._validator = new Validator(); for (const schema of values(schemas)) { this._validator.addSchema(schema, schema.id); } } + /** + * Add a schema to the validator. All schemas and sub-schemas must be added to + * the validator before the `validate` and `isValid` methods can be called with + * instances of that schema. + * @param schema The schema to add + */ public addSchema(schema: Schema) { this._validator.addSchema(schema, schema.id); } @@ -18,10 +30,22 @@ export class SchemaValidator { // sub-types (e.g BigNumber) with a simpler string representation. Since BigNumber and other // complex types implement the `toString` method, we can stringify the object and // then parse it. The resultant object can then be checked using jsonschema. + /** + * Validate the JS object conforms to a specific JSON schema + * @param instance JS object in question + * @param schema Schema to check against + * @returns The results of the validation + */ public validate(instance: any, schema: Schema): ValidatorResult { const jsonSchemaCompatibleObject = JSON.parse(JSON.stringify(instance)); return this._validator.validate(jsonSchemaCompatibleObject, schema); } + /** + * Check whether an instance properly adheres to a JSON schema + * @param instance JS object in question + * @param schema Schema to check against + * @returns Whether or not the instance adheres to the schema + */ public isValid(instance: any, schema: Schema): boolean { const isValid = this.validate(instance, schema).errors.length === 0; return isValid; diff --git a/packages/react-docs/CHANGELOG.md b/packages/react-docs/CHANGELOG.md index 7c46013b1..8ce3ef68c 100644 --- a/packages/react-docs/CHANGELOG.md +++ b/packages/react-docs/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## v0.0.4 - _TBD_ + + * Handle `reflection` type rendering so that anonymous function type declarations render properly (#465) + * Rename `MethodSignature` to `Signature` and change it's props so that it can be used to render method and function signatures. (#465) + * Rename `MethodBlock` to `SignatureBlock` since it is not used to render method and function signature blocks. (#465) + * Add support for documenting exported functions. (#465) + ## v0.0.3 - _March 18, 2018_ * Move TS typings from devDependencies to dependencies since they are needed by the package user. diff --git a/packages/react-docs/src/components/documentation.tsx b/packages/react-docs/src/components/documentation.tsx index b46358159..14fe175cf 100644 --- a/packages/react-docs/src/components/documentation.tsx +++ b/packages/react-docs/src/components/documentation.tsx @@ -25,6 +25,7 @@ import { SolidityMethod, SupportedDocJson, TypeDefinitionByName, + TypescriptFunction, TypescriptMethod, } from '../types'; import { constants } from '../utils/constants'; @@ -33,7 +34,7 @@ import { utils } from '../utils/utils'; import { Badge } from './badge'; import { Comment } from './comment'; import { EventDefinition } from './event_definition'; -import { MethodBlock } from './method_block'; +import { SignatureBlock } from './signature_block'; import { SourceLink } from './source_link'; import { Type } from './type'; import { TypeDefinition } from './type_definition'; @@ -216,8 +217,12 @@ export class Documentation extends React.Component<DocumentationProps, Documenta const sortedMethods = _.sortBy(docSection.methods, 'name'); const methodDefs = _.map(sortedMethods, method => { - const isConstructor = false; - return this._renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); + return this._renderSignatureBlocks(method, sectionName, typeDefinitionByName); + }); + + const sortedFunctions = _.sortBy(docSection.functions, 'name'); + const functionDefs = _.map(sortedFunctions, func => { + return this._renderSignatureBlocks(func, sectionName, typeDefinitionByName); }); const sortedEvents = _.sortBy(docSection.events, 'name'); @@ -243,25 +248,31 @@ export class Documentation extends React.Component<DocumentationProps, Documenta {this._renderNetworkBadgesIfExists(sectionName)} </div> {docSection.comment && <Comment comment={docSection.comment} />} - {docSection.constructors.length > 0 && + {!_.isEmpty(docSection.constructors) && this.props.docsInfo.isVisibleConstructor(sectionName) && ( <div> <h2 style={headerStyle}>Constructor</h2> {this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)} </div> )} - {docSection.properties.length > 0 && ( + {!_.isEmpty(docSection.properties) && ( <div> <h2 style={headerStyle}>Properties</h2> <div>{propertyDefs}</div> </div> )} - {docSection.methods.length > 0 && ( + {!_.isEmpty(docSection.methods) && ( <div> <h2 style={headerStyle}>Methods</h2> <div>{methodDefs}</div> </div> )} + {!_.isEmpty(docSection.functions) && ( + <div> + <h2 style={headerStyle}>Functions</h2> + <div>{functionDefs}</div> + </div> + )} {!_.isUndefined(docSection.events) && docSection.events.length > 0 && ( <div> @@ -318,7 +329,7 @@ export class Documentation extends React.Component<DocumentationProps, Documenta typeDefinitionByName: TypeDefinitionByName, ): React.ReactNode { const constructorDefs = _.map(constructors, constructor => { - return this._renderMethodBlocks(constructor, sectionName, constructor.isConstructor, typeDefinitionByName); + return this._renderSignatureBlocks(constructor, sectionName, typeDefinitionByName); }); return <div>{constructorDefs}</div>; } @@ -340,14 +351,13 @@ export class Documentation extends React.Component<DocumentationProps, Documenta </div> ); } - private _renderMethodBlocks( - method: SolidityMethod | TypescriptMethod, + private _renderSignatureBlocks( + method: SolidityMethod | TypescriptFunction | TypescriptMethod, sectionName: string, - isConstructor: boolean, typeDefinitionByName: TypeDefinitionByName, ): React.ReactNode { return ( - <MethodBlock + <SignatureBlock key={`method-${method.name}-${sectionName}`} sectionName={sectionName} method={method} diff --git a/packages/react-docs/src/components/interface.tsx b/packages/react-docs/src/components/interface.tsx index 01f4942ef..541e164e3 100644 --- a/packages/react-docs/src/components/interface.tsx +++ b/packages/react-docs/src/components/interface.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { DocsInfo } from '../docs_info'; import { CustomType, TypeDocTypes } from '../types'; -import { MethodSignature } from './method_signature'; +import { Signature } from './signature'; import { Type } from './type'; export interface InterfaceProps { @@ -22,8 +22,12 @@ export function Interface(props: InterfaceProps) { {property.type.typeDocType !== TypeDocTypes.Reflection ? ( <Type type={property.type} sectionName={props.sectionName} docsInfo={props.docsInfo} /> ) : ( - <MethodSignature - method={property.type.method} + <Signature + name={property.type.method.name} + returnType={property.type.method.returnType} + parameters={property.type.method.parameters} + typeParameter={property.type.method.typeParameter} + callPath={property.type.method.callPath} sectionName={props.sectionName} shouldHideMethodName={true} shouldUseArrowSyntax={true} diff --git a/packages/react-docs/src/components/method_signature.tsx b/packages/react-docs/src/components/signature.tsx index 1400182ea..83fb1e246 100644 --- a/packages/react-docs/src/components/method_signature.tsx +++ b/packages/react-docs/src/components/signature.tsx @@ -3,28 +3,33 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { DocsInfo } from '../docs_info'; -import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; +import { Parameter, Type as TypeDef, TypeDefinitionByName, TypeParameter } from '../types'; import { constants } from '../utils/constants'; import { Type } from './type'; -export interface MethodSignatureProps { - method: TypescriptMethod | SolidityMethod; +export interface SignatureProps { + name: string; + returnType: TypeDef; + parameters: Parameter[]; sectionName: string; shouldHideMethodName?: boolean; shouldUseArrowSyntax?: boolean; typeDefinitionByName?: TypeDefinitionByName; + typeParameter?: TypeParameter; + callPath?: string; docsInfo: DocsInfo; } const defaultProps = { shouldHideMethodName: false, shouldUseArrowSyntax: false, + callPath: '', }; -export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSignatureProps) => { +export const Signature: React.SFC<SignatureProps> = (props: SignatureProps) => { const sectionName = constants.TYPES_SECTION_NAME; - const parameters = renderParameters(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); + const parameters = renderParameters(props.parameters, props.docsInfo, sectionName, props.typeDefinitionByName); const paramStringArray: any[] = []; // HACK: For now we don't put params on newlines if there are less then 2 of them. // Ideally we would check the character length of the resulting method signature and @@ -51,21 +56,21 @@ export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSi if (!hasMoreThenTwoParams) { paramStringArray.pop(); } - const methodName = props.shouldHideMethodName ? '' : props.method.name; - const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) + const methodName = props.shouldHideMethodName ? '' : props.name; + const typeParameterIfExists = _.isUndefined(props.typeParameter) ? undefined - : renderTypeParameter(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); + : renderTypeParameter(props.typeParameter, props.docsInfo, sectionName, props.typeDefinitionByName); return ( <span style={{ fontSize: 15 }}> - {props.method.callPath} + {props.callPath} {methodName} {typeParameterIfExists}({hasMoreThenTwoParams && <br />} {paramStringArray}) - {props.method.returnType && ( + {props.returnType && ( <span> {props.shouldUseArrowSyntax ? ' => ' : ': '}{' '} <Type - type={props.method.returnType} + type={props.returnType} sectionName={sectionName} typeDefinitionByName={props.typeDefinitionByName} docsInfo={props.docsInfo} @@ -76,15 +81,14 @@ export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSi ); }; -MethodSignature.defaultProps = defaultProps; +Signature.defaultProps = defaultProps; function renderParameters( - method: TypescriptMethod | SolidityMethod, + parameters: Parameter[], docsInfo: DocsInfo, sectionName: string, typeDefinitionByName?: TypeDefinitionByName, ) { - const parameters = method.parameters; const params = _.map(parameters, (p: Parameter) => { const isOptional = p.isOptional; const type = ( @@ -106,12 +110,11 @@ function renderParameters( } function renderTypeParameter( - method: TypescriptMethod, + typeParameter: TypeParameter, docsInfo: DocsInfo, sectionName: string, typeDefinitionByName?: TypeDefinitionByName, ) { - const typeParameter = method.typeParameter; const typeParam = ( <span> {`<${typeParameter.name} extends `} diff --git a/packages/react-docs/src/components/method_block.tsx b/packages/react-docs/src/components/signature_block.tsx index 44a1db8af..6475d3995 100644 --- a/packages/react-docs/src/components/method_block.tsx +++ b/packages/react-docs/src/components/signature_block.tsx @@ -3,16 +3,16 @@ import * as _ from 'lodash'; import * as React from 'react'; import { DocsInfo } from '../docs_info'; -import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; +import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptFunction, TypescriptMethod } from '../types'; import { constants } from '../utils/constants'; import { typeDocUtils } from '../utils/typedoc_utils'; import { Comment } from './comment'; -import { MethodSignature } from './method_signature'; +import { Signature } from './signature'; import { SourceLink } from './source_link'; -export interface MethodBlockProps { - method: SolidityMethod | TypescriptMethod; +export interface SignatureBlockProps { + method: SolidityMethod | TypescriptFunction | TypescriptMethod; sectionName: string; libraryVersion: string; typeDefinitionByName: TypeDefinitionByName; @@ -20,7 +20,7 @@ export interface MethodBlockProps { sourceUrl: string; } -export interface MethodBlockState { +export interface SignatureBlockState { shouldShowAnchor: boolean; } @@ -35,8 +35,8 @@ const styles: Styles = { }, }; -export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockState> { - constructor(props: MethodBlockProps) { +export class SignatureBlock extends React.Component<SignatureBlockProps, SignatureBlockState> { + constructor(props: SignatureBlockProps) { super(props); this.state = { shouldShowAnchor: false, @@ -56,7 +56,7 @@ export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockSt onMouseOver={this._setAnchorVisibility.bind(this, true)} onMouseOut={this._setAnchorVisibility.bind(this, false)} > - {!method.isConstructor && ( + {!(method as TypescriptMethod).isConstructor && ( <div className="flex pb2 pt2"> {(method as TypescriptMethod).isStatic && this._renderChip('Static')} {(method as SolidityMethod).isConstant && this._renderChip('Constant')} @@ -72,8 +72,12 @@ export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockSt </div> )} <code className={`hljs ${constants.TYPE_TO_SYNTAX[this.props.docsInfo.type]}`}> - <MethodSignature - method={method} + <Signature + name={method.name} + returnType={method.returnType} + parameters={method.parameters} + typeParameter={(method as TypescriptMethod).typeParameter} + callPath={(method as TypescriptMethod).callPath} sectionName={this.props.sectionName} typeDefinitionByName={this.props.typeDefinitionByName} docsInfo={this.props.docsInfo} diff --git a/packages/react-docs/src/components/type.tsx b/packages/react-docs/src/components/type.tsx index 56425a5df..fd4562ce3 100644 --- a/packages/react-docs/src/components/type.tsx +++ b/packages/react-docs/src/components/type.tsx @@ -9,6 +9,7 @@ import { Type as TypeDef, TypeDefinitionByName, TypeDocTypes } from '../types'; import { constants } from '../utils/constants'; import { utils } from '../utils/utils'; +import { Signature } from './signature'; import { TypeDefinition } from './type_definition'; const typeToSection: { [typeName: string]: string } = { @@ -101,6 +102,23 @@ export function Type(props: TypeProps): any { }); break; + case TypeDocTypes.Reflection: + typeName = ( + <Signature + name={type.method.name} + returnType={type.method.returnType} + parameters={type.method.parameters} + typeParameter={type.method.typeParameter} + callPath={type.method.callPath} + sectionName={props.sectionName} + shouldHideMethodName={true} + shouldUseArrowSyntax={true} + docsInfo={props.docsInfo} + typeDefinitionByName={props.typeDefinitionByName} + /> + ); + break; + case TypeDocTypes.TypeParameter: typeName = type.name; break; diff --git a/packages/react-docs/src/components/type_definition.tsx b/packages/react-docs/src/components/type_definition.tsx index 68ef4c465..7a1c86da5 100644 --- a/packages/react-docs/src/components/type_definition.tsx +++ b/packages/react-docs/src/components/type_definition.tsx @@ -11,7 +11,7 @@ import { Comment } from './comment'; import { CustomEnum } from './custom_enum'; import { Enum } from './enum'; import { Interface } from './interface'; -import { MethodSignature } from './method_signature'; +import { Signature } from './signature'; import { Type } from './type'; export interface TypeDefinitionProps { @@ -79,8 +79,12 @@ export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDef docsInfo={this.props.docsInfo} /> ) : ( - <MethodSignature - method={customType.type.method} + <Signature + name={customType.type.method.name} + returnType={customType.type.method.returnType} + parameters={customType.type.method.parameters} + typeParameter={customType.type.method.typeParameter} + callPath={customType.type.method.callPath} sectionName={this.props.sectionName} shouldHideMethodName={true} shouldUseArrowSyntax={true} diff --git a/packages/react-docs/src/docs_info.ts b/packages/react-docs/src/docs_info.ts index 68bddef06..0030568a1 100644 --- a/packages/react-docs/src/docs_info.ts +++ b/packages/react-docs/src/docs_info.ts @@ -97,6 +97,9 @@ export class DocsInfo { const sortedMethodNames = _.sortBy(docSection.methods, 'name'); const methodNames = _.map(sortedMethodNames, m => m.name); menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames]; + const sortedFunctionNames = _.sortBy(docSection.functions, 'name'); + const functionNames = _.map(sortedFunctionNames, m => m.name); + menuSubsectionsBySection[sectionName] = [...eventNames, ...functionNames, ...methodNames]; } }); return menuSubsectionsBySection; diff --git a/packages/react-docs/src/index.ts b/packages/react-docs/src/index.ts index a62c91376..30f5011b7 100644 --- a/packages/react-docs/src/index.ts +++ b/packages/react-docs/src/index.ts @@ -7,8 +7,8 @@ export { Documentation } from './components/documentation'; export { Enum } from './components/enum'; export { EventDefinition } from './components/event_definition'; export { Interface } from './components/interface'; -export { MethodBlock } from './components/method_block'; -export { MethodSignature } from './components/method_signature'; +export { SignatureBlock } from './components/signature_block'; +export { Signature } from './components/signature'; export { SourceLink } from './components/source_link'; export { TypeDefinition } from './components/type_definition'; export { Type } from './components/type'; diff --git a/packages/react-docs/src/types.ts b/packages/react-docs/src/types.ts index d192af313..2a300c164 100644 --- a/packages/react-docs/src/types.ts +++ b/packages/react-docs/src/types.ts @@ -42,6 +42,7 @@ export interface TypeDocFlags { isStatic?: boolean; isOptional?: boolean; isPublic?: boolean; + isExported?: boolean; } export interface TypeDocGroup { @@ -96,6 +97,7 @@ export enum KindString { Variable = 'Variable', Function = 'Function', Enumeration = 'Enumeration', + Class = 'Class', } export interface DocAgnosticFormat { @@ -108,6 +110,7 @@ export interface DocSection { methods: Array<TypescriptMethod | SolidityMethod>; properties: Property[]; types: CustomType[]; + functions?: TypescriptFunction[]; events?: Event[]; } @@ -117,6 +120,11 @@ export interface TypescriptMethod extends BaseMethod { typeParameter?: TypeParameter; } +export interface TypescriptFunction extends BaseFunction { + source?: Source; + typeParameter?: TypeParameter; +} + export interface SolidityMethod extends BaseMethod { isConstant?: boolean; isPayable?: boolean; @@ -205,6 +213,14 @@ export interface BaseMethod { comment?: string; } +export interface BaseFunction { + name: string; + returnComment?: string | undefined; + parameters: Parameter[]; + returnType: Type; + comment?: string; +} + export interface TypeDefinitionByName { [typeName: string]: CustomType; } diff --git a/packages/react-docs/src/utils/doxity_utils.ts b/packages/react-docs/src/utils/doxity_utils.ts index 26dea6966..1b91690e0 100644 --- a/packages/react-docs/src/utils/doxity_utils.ts +++ b/packages/react-docs/src/utils/doxity_utils.ts @@ -116,6 +116,7 @@ export const doxityUtils = { methods, properties, types: [], + functions: [], events, }; docAgnosticFormat[contractName] = docSection; diff --git a/packages/react-docs/src/utils/typedoc_utils.ts b/packages/react-docs/src/utils/typedoc_utils.ts index e4cea1e40..02f5b4049 100644 --- a/packages/react-docs/src/utils/typedoc_utils.ts +++ b/packages/react-docs/src/utils/typedoc_utils.ts @@ -15,6 +15,7 @@ import { TypeDocNode, TypeDocType, TypeParameter, + TypescriptFunction, TypescriptMethod, } from '../types'; import { utils } from '../utils/utils'; @@ -81,17 +82,22 @@ export const typeDocUtils = { } } - // Since the `types.ts` file is the only file that does not export a module/class but - // instead has each type export itself, we do not need to go down two levels of nesting - // for it. let entities; let packageComment = ''; - if (sectionName === docsInfo.sections.types) { - entities = packageDefinitionWithMergedChildren.children; - } else { + // HACK: We assume 1 exported class per file + const classChildren = _.filter(packageDefinitionWithMergedChildren.children, (child: TypeDocNode) => { + return child.kindString === KindString.Class; + }); + if (classChildren.length > 1 && sectionName !== 'types') { + throw new Error('`react-docs` only supports projects with 1 exported class per file'); + } + const isClassExport = packageDefinitionWithMergedChildren.children[0].kindString === KindString.Class; + if (isClassExport) { entities = packageDefinitionWithMergedChildren.children[0].children; const commentObj = packageDefinitionWithMergedChildren.children[0].comment; packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment; + } else { + entities = packageDefinitionWithMergedChildren.children; } const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName); @@ -105,6 +111,7 @@ export const typeDocUtils = { comment: '', constructors: [], methods: [], + functions: [], properties: [], types: [], }; @@ -124,6 +131,13 @@ export const typeDocUtils = { docSection.constructors.push(constructor); break; + case KindString.Function: + if (entity.flags.isExported) { + const func = typeDocUtils._convertFunction(entity, docsInfo.sections, sectionName, docsInfo.id); + docSection.functions.push(func); + } + break; + case KindString.Method: if (entity.flags.isPublic) { isConstructor = false; @@ -151,7 +165,6 @@ export const typeDocUtils = { break; case KindString.Interface: - case KindString.Function: case KindString.Variable: case KindString.Enumeration: case KindString.TypeAlias: @@ -162,10 +175,20 @@ export const typeDocUtils = { sectionName, docsInfo.id, ); - docSection.types.push(customType); + const seenTypeNames = _.map(docSection.types, t => t.name); + const isUnseen = !_.includes(seenTypeNames, customType.name); + if (isUnseen) { + docSection.types.push(customType); + } } break; + case KindString.Class: + // We currently do not support more then a single class per file + // except for the types section, where we ignore classes since we + // only want to render type definitions. + break; + default: throw utils.spawnSwitchErr('kindString', entity.kindString); } @@ -303,6 +326,38 @@ export const typeDocUtils = { }; return method; }, + _convertFunction( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + docId: string, + ): TypescriptFunction { + const signature = entity.signatures[0]; + const source = entity.sources[0]; + const hasComment = !_.isUndefined(signature.comment); + + const parameters = _.map(signature.parameters, param => { + return typeDocUtils._convertParameter(param, sections, sectionName, docId); + }); + const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, docId); + const typeParameter = _.isUndefined(signature.typeParameter) + ? undefined + : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, docId); + + const func = { + name: signature.name, + comment: hasComment ? signature.comment.shortText : undefined, + returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, + source: { + fileName: source.fileName, + line: source.line, + }, + parameters, + returnType, + typeParameter, + }; + return func; + }, _convertTypeParameter( entity: TypeDocNode, sections: SectionsMap, diff --git a/packages/react-shared/CHANGELOG.md b/packages/react-shared/CHANGELOG.md index 43b92d58a..51fb8e4b6 100644 --- a/packages/react-shared/CHANGELOG.md +++ b/packages/react-shared/CHANGELOG.md @@ -1,3 +1,6 @@ # CHANGELOG -## v0.0.1 - _March 8, 2018_ +## v0.1.0 - _TBD, 2018_ + + * Added new colors (#468) + * Fix section and menuItem text display to replace dashes with spaces. diff --git a/packages/react-shared/src/components/markdown_section.tsx b/packages/react-shared/src/components/markdown_section.tsx index d24a43dcb..449e8a045 100644 --- a/packages/react-shared/src/components/markdown_section.tsx +++ b/packages/react-shared/src/components/markdown_section.tsx @@ -43,6 +43,7 @@ export class MarkdownSection extends React.Component<MarkdownSectionProps, Markd const { sectionName, markdownContent, headerSize, githubLink } = this.props as PropsWithDefaults; const id = utils.getIdFromName(sectionName); + const finalSectionName = utils.convertDashesToSpaces(sectionName); return ( <div className="md-px1 sm-px2 overflow-hidden" @@ -55,7 +56,7 @@ export class MarkdownSection extends React.Component<MarkdownSectionProps, Markd <span style={{ textTransform: 'capitalize', color: colors.grey700 }}> <AnchorTitle headerSize={headerSize} - title={sectionName} + title={finalSectionName} id={id} shouldShowAnchor={this.state.shouldShowAnchor} /> diff --git a/packages/react-shared/src/components/nested_sidebar_menu.tsx b/packages/react-shared/src/components/nested_sidebar_menu.tsx index 2225bd197..f49d6fcf7 100644 --- a/packages/react-shared/src/components/nested_sidebar_menu.tsx +++ b/packages/react-shared/src/components/nested_sidebar_menu.tsx @@ -45,7 +45,7 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N }; public render() { const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => { - const finalSectionName = sectionName.replace(/-/g, ' '); + const finalSectionName = utils.convertDashesToSpaces(sectionName); if (this.props.shouldDisplaySectionHeaders) { const id = utils.getIdFromName(sectionName); return ( @@ -85,6 +85,7 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N : styles.menuItemWithoutHeaders; const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemInnerDivWithHeaders : {}; const menuItems = _.map(menuItemNames, menuItemName => { + const finalMenuItemName = utils.convertDashesToSpaces(menuItemName); const id = utils.getIdFromName(menuItemName); return ( <div key={menuItemName}> @@ -96,11 +97,11 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N containerId={constants.DOCS_CONTAINER_ID} > <MenuItem - onTouchTap={this._onMenuItemClick.bind(this, menuItemName)} + onTouchTap={this._onMenuItemClick.bind(this, finalMenuItemName)} style={menuItemStyles} innerDivStyle={menuItemInnerDivStyles} > - <span style={{ textTransform: 'capitalize' }}>{menuItemName}</span> + <span style={{ textTransform: 'capitalize' }}>{finalMenuItemName}</span> </MenuItem> </ScrollLink> {this._renderMenuItemSubsections(menuItemName)} @@ -122,7 +123,7 @@ export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, N const name = `${menuItemName}-${entityName}`; const id = utils.getIdFromName(name); return ( - <li key={`menuItem-${entityName}`}> + <li key={`menuSubsectionItem-${name}`}> <ScrollLink to={id} offset={0} diff --git a/packages/react-shared/src/components/section_header.tsx b/packages/react-shared/src/components/section_header.tsx index ee34a6c09..0224c2127 100644 --- a/packages/react-shared/src/components/section_header.tsx +++ b/packages/react-shared/src/components/section_header.tsx @@ -35,7 +35,7 @@ export class SectionHeader extends React.Component<SectionHeaderProps, SectionHe public render() { const { sectionName, headerSize } = this.props as PropsWithDefaults; - const finalSectionName = this.props.sectionName.replace(/-/g, ' '); + const finalSectionName = utils.convertDashesToSpaces(this.props.sectionName); const id = utils.getIdFromName(finalSectionName); return ( <div @@ -48,7 +48,7 @@ export class SectionHeader extends React.Component<SectionHeaderProps, SectionHe title={ <span style={{ - textTransform: 'uppercase', + textTransform: 'capitalize', color: colors.grey, fontFamily: 'Roboto Mono', fontWeight: 300, diff --git a/packages/react-shared/src/utils/colors.ts b/packages/react-shared/src/utils/colors.ts index 2eead95c7..ea0165305 100644 --- a/packages/react-shared/src/utils/colors.ts +++ b/packages/react-shared/src/utils/colors.ts @@ -45,4 +45,7 @@ export const colors = { orange: '#E69D00', amber800: '#FF8F00', darkYellow: '#caca03', + walletBoxShadow: 'rgba(56, 59, 137, 0.2)', + walletBorder: '#f5f5f6', + walletDefaultItemBackground: '#fbfbfc', }; diff --git a/packages/react-shared/src/utils/utils.ts b/packages/react-shared/src/utils/utils.ts index b3acb081e..1538dd087 100644 --- a/packages/react-shared/src/utils/utils.ts +++ b/packages/react-shared/src/utils/utils.ts @@ -30,6 +30,9 @@ export const utils = { const id = name.replace(/ /g, '-'); return id; }, + convertDashesToSpaces(text: string) { + return text.replace(/-/g, ' '); + }, getEtherScanLinkIfExists( addressOrTxHash: string, networkId: number, diff --git a/packages/sol-cov/README.md b/packages/sol-cov/README.md index 6a98346f9..5591b9731 100644 --- a/packages/sol-cov/README.md +++ b/packages/sol-cov/README.md @@ -2,41 +2,26 @@ A Solidity code coverage tool. +### Read the [Documentation](0xproject.com/docs/sol-cov). + ## Installation ```bash -yarn add -D @0xproject/sol-cov +yarn add @0xproject/sol-cov ``` -## Usage - -Sol-cov uses transaction traces in order to figure out which lines of Solidity source code have been covered by your tests. In order for it to gather these traces, you must add the `CoverageSubprovider` to the [ProviderEngine](https://github.com/MetaMask/provider-engine) instance you use when running your Solidity tests. If you're unfamiliar with ProviderEngine, please read the [Web3 Provider explained](https://0xproject.com/wiki#Web3-Provider-Explained) wiki article. +**Import** -The CoverageSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot. - -```typescript +```javascript import { CoverageSubprovider } from '@0xproject/sol-cov'; - -const provider = new ProviderEngine(); - -const artifactsPath = 'src/artifacts'; -const contractsPath = 'src/contracts'; -const networkId = 50; -// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from. -const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; -const coverageSubprovider = new CoverageSubprovider(artifactsPath, contractsPath, networkId, defaultFromAddress); - -provider.addProvider(coverageSubprovider); ``` -After your test suite is complete (e.g global `after` hook), you'll need to call: +or -```typescript -await coverageSubprovider.writeCoverageAsync(); +```javascript +var CoverageSubprovider = require('@0xproject/sol-cov').CoverageSubprovider; ``` -This will create a `coverage.json` file in the `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use any of the existing Instanbul reporters. - ## Contributing We strongly encourage the community to help us make improvements. To report bugs within this package, please create an issue in this repository. diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index 3292921fb..106c47d13 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -13,7 +13,20 @@ "test:circleci": "yarn test:coverage", "run_mocha": "mocha lib/test/**/*_test.js", "clean": "shx rm -rf lib scripts", - "build": "copyfiles 'test/fixtures/**/*' ./lib && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts" + "build": "copyfiles 'test/fixtures/**/*' ./lib && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", + "docs:stage": "yarn build && node ./scripts/stage_docs.js", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" + }, + "config": { + "postpublish": { + "assets": [], + "docPublishConfigs": { + "extraFileIncludes": ["../subproviders/src/types.ts"], + "s3BucketPath": "s3://doc-jsons/sol-cov/", + "s3StagingBucketPath": "s3://staging-doc-jsons/sol-cov/" + } + } }, "repository": { "type": "git", @@ -53,6 +66,7 @@ "shx": "^0.2.2", "sinon": "^4.0.0", "tslint": "5.8.0", + "typedoc": "0xProject/typedoc", "typescript": "2.7.1", "web3-typescript-typings": "^0.9.11" }, diff --git a/packages/sol-cov/src/coverage_subprovider.ts b/packages/sol-cov/src/coverage_subprovider.ts index 71d90bba7..37682c45f 100644 --- a/packages/sol-cov/src/coverage_subprovider.ts +++ b/packages/sol-cov/src/coverage_subprovider.ts @@ -1,4 +1,4 @@ -import { Callback, NextCallback, Subprovider } from '@0xproject/subproviders'; +import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0xproject/subproviders'; import { promisify } from '@0xproject/utils'; import * as _ from 'lodash'; import { Lock } from 'semaphore-async-await'; @@ -12,18 +12,26 @@ interface MaybeFakeTxData extends Web3.TxData { isFakeTransaction?: boolean; } -/* - * This class implements the web3-provider-engine subprovider interface and collects traces of all transactions that were sent and all calls that were executed. - * Because there is no notion of call trace in the rpc - we collect them in rather non-obvious/hacky way. - * On each call - we create a snapshot, execute the call as a transaction, get the trace, revert the snapshot. - * That allows us to not influence the test behaviour. - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +// Because there is no notion of a call trace in the Ethereum rpc - we collect them in a rather non-obvious/hacky way. +// On each call - we create a snapshot, execute the call as a transaction, get the trace, revert the snapshot. +// That allows us to avoid influencing test behaviour. + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. */ export class CoverageSubprovider extends Subprovider { // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise private _lock: Lock; private _coverageManager: CoverageManager; private _defaultFromAddress: string; + /** + * Instantiates a CoverageSubprovider instance + * @param artifactsPath Path to the smart contract artifacts + * @param sourcesPath Path to the smart contract source files + * @param networkId network id + * @param defaultFromAddress default from address to use when sending transactions + */ constructor(artifactsPath: string, sourcesPath: string, networkId: number, defaultFromAddress: string) { super(); this._lock = new Lock(); @@ -35,11 +43,22 @@ export class CoverageSubprovider extends Subprovider { this._getContractCodeAsync.bind(this), ); } - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: NextCallback, - end: (err: Error | null, result: any) => void, - ) { + /** + * Write the test coverage results to a file in Istanbul format. + */ + public async writeCoverageAsync(): Promise<void> { + await this._coverageManager.writeCoverageAsync(); + } + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:prefer-function-over-method + public handleRequest(payload: Web3.JSONRPCRequestPayload, next: NextCallback, end: ErrorCallback) { switch (payload.method) { case 'eth_sendTransaction': const txData = payload.params[0]; @@ -57,9 +76,6 @@ export class CoverageSubprovider extends Subprovider { return; } } - public async writeCoverageAsync(): Promise<void> { - await this._coverageManager.writeCoverageAsync(); - } private async _onTransactionSentAsync( txData: MaybeFakeTxData, err: Error | null, diff --git a/packages/sol-cov/src/monorepo_scripts/stage_docs.ts b/packages/sol-cov/src/monorepo_scripts/stage_docs.ts new file mode 100644 index 000000000..e732ac8eb --- /dev/null +++ b/packages/sol-cov/src/monorepo_scripts/stage_docs.ts @@ -0,0 +1,8 @@ +import { postpublishUtils } from '@0xproject/monorepo-scripts'; + +import * as packageJSON from '../package.json'; +import * as tsConfigJSON from '../tsconfig.json'; + +const cwd = `${__dirname}/..`; +// tslint:disable-next-line:no-floating-promises +postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/subproviders/CHANGELOG.md b/packages/subproviders/CHANGELOG.md index b7c5545ea..cdf98cba3 100644 --- a/packages/subproviders/CHANGELOG.md +++ b/packages/subproviders/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## v0.8.1 - _TBD_ + + * Introduce `JSONRPCRequestPayloadWithMethod` type (#465) + * Export `ErrorCallback` type. (#465) + ## v0.8.0 - _March 18, 2018_ * Export `GanacheSubprovider` and `Subprovider` (#426) diff --git a/packages/subproviders/README.md b/packages/subproviders/README.md index 4614342b2..a2bf75768 100644 --- a/packages/subproviders/README.md +++ b/packages/subproviders/README.md @@ -4,6 +4,8 @@ A few useful web3 subproviders including a LedgerSubprovider useful for adding L We have written up a [Wiki](https://0xproject.com/wiki#Web3-Provider-Examples) article detailing some use cases of this subprovider package. +### Read the [Documentation](0xproject.com/docs/subproviders). + ## Installation ``` @@ -18,60 +20,6 @@ If your project is in [TypeScript](https://www.typescriptlang.org/), add the fol ] ``` -## Usage - -Simply import the subprovider you are interested in using: - -```javascript -import { - ledgerEthereumBrowserClientFactoryAsync as ledgerEthereumClientFactoryAsync, - LedgerSubprovider, -} from '@0xproject/subproviders'; - -const ledgerSubprovider = new LedgerSubprovider({ - networkId, - ledgerEthereumClientFactoryAsync, -}); - -const accounts = await ledgerSubprovider.getAccountsAsync(); -``` - -### Subproviders - -#### Ledger Nano S subprovider - -A subprovider that enables your dApp to send signing requests to a user's Ledger Nano S hardware wallet. These can be requests to sign transactions or messages. - -Ledger Nano (and this library) by default uses a derivation path of `44'/60'/0'`. This is different to TestRPC which by default uses `m/44'/60'/0'/0`. This is a configuration option in the Ledger Subprovider package. - -##### Ledger Nano S + Node-hid (usb) - -By default, node-hid transport support is an optional dependency. This is due to the requirement of native usb developer packages on the host system. If these aren't installed the entire `npm install` fails. We also no longer export node-hid transport client factories. To re-create this see our integration tests or follow the example below: - -```typescript -import Eth from '@ledgerhq/hw-app-eth'; -import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; -async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> { - const ledgerConnection = await TransportNodeHid.create(); - const ledgerEthClient = new Eth(ledgerConnection); - return ledgerEthClient; -} - -// Create a LedgerSubprovider with the node-hid transport -ledgerSubprovider = new LedgerSubprovider({ - networkId, - ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, -}); -``` - -#### Redundant RPC subprovider - -A subprovider which attempts to send an RPC call to a list of RPC endpoints sequentially, until one of them returns a successful response. - -#### Injected Web3 subprovider - -A subprovider that relays all signing related requests to a particular provider (in our case the provider injected onto the web page), while sending all other requests to a different provider (perhaps your own backing Ethereum node or Infura). - ## Contributing We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index f40148b5e..96d3e7f00 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -17,7 +17,20 @@ "test:circleci": "npm run test:unit:coverage", "test:all": "run-s test:unit test:integration", "test:unit": "run-s clean build run_mocha_unit", - "test:integration": "run-s clean build run_mocha_integration" + "test:integration": "run-s clean build run_mocha_integration", + "docs:stage": "yarn build && node ./scripts/stage_docs.js", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" + }, + "config": { + "postpublish": { + "assets": [], + "docPublishConfigs": { + "extraFileIncludes": ["../types/src/index.ts"], + "s3BucketPath": "s3://doc-jsons/subproviders/", + "s3StagingBucketPath": "s3://staging-doc-jsons/subproviders/" + } + } }, "dependencies": { "@0xproject/assert": "^0.2.3", @@ -57,6 +70,7 @@ "tslint": "5.8.0", "types-bn": "^0.0.1", "types-ethereumjs-util": "0xProject/types-ethereumjs-util", + "typedoc": "0xProject/typedoc", "typescript": "2.7.1", "webpack": "^3.1.0" }, diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts index cafb50fe5..d88792fd0 100644 --- a/packages/subproviders/src/index.ts +++ b/packages/subproviders/src/index.ts @@ -2,7 +2,6 @@ import Eth from '@ledgerhq/hw-app-eth'; import TransportU2F from '@ledgerhq/hw-transport-u2f'; import { LedgerEthereumClient } from './types'; -export { Callback, NextCallback } from './types'; export { EmptyWalletSubprovider } from './subproviders/empty_wallet_subprovider'; export { FakeGasEstimateSubprovider } from './subproviders/fake_gas_estimate_subprovider'; @@ -12,11 +11,20 @@ export { LedgerSubprovider } from './subproviders/ledger'; export { GanacheSubprovider } from './subproviders/ganache'; export { Subprovider } from './subproviders/subprovider'; export { NonceTrackerSubprovider } from './subproviders/nonce_tracker'; -export { ECSignature, LedgerWalletSubprovider, LedgerCommunicationClient, NonceSubproviderErrors } from './types'; +export { + Callback, + ErrorCallback, + NextCallback, + ECSignature, + LedgerWalletSubprovider, + LedgerCommunicationClient, + NonceSubproviderErrors, + LedgerSubproviderConfigs, +} from './types'; /** * A factory method for creating a LedgerEthereumClient usable in a browser context. - * @return LedgerEthereumClient A browser client + * @return LedgerEthereumClient A browser client for the LedgerSubprovider */ export async function ledgerEthereumBrowserClientFactoryAsync(): Promise<LedgerEthereumClient> { const ledgerConnection = await TransportU2F.create(); diff --git a/packages/subproviders/src/monorepo_scripts/stage_docs.ts b/packages/subproviders/src/monorepo_scripts/stage_docs.ts new file mode 100644 index 000000000..e732ac8eb --- /dev/null +++ b/packages/subproviders/src/monorepo_scripts/stage_docs.ts @@ -0,0 +1,8 @@ +import { postpublishUtils } from '@0xproject/monorepo-scripts'; + +import * as packageJSON from '../package.json'; +import * as tsConfigJSON from '../tsconfig.json'; + +const cwd = `${__dirname}/..`; +// tslint:disable-next-line:no-floating-promises +postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/subproviders/src/subproviders/empty_wallet_subprovider.ts b/packages/subproviders/src/subproviders/empty_wallet_subprovider.ts index f5983dd9b..dc570b152 100644 --- a/packages/subproviders/src/subproviders/empty_wallet_subprovider.ts +++ b/packages/subproviders/src/subproviders/empty_wallet_subprovider.ts @@ -1,20 +1,24 @@ import * as Web3 from 'web3'; +import { Callback, ErrorCallback } from '../types'; + import { Subprovider } from './subprovider'; -/* - * This class implements the web3-provider-engine subprovider interface and returns - * that the provider has no addresses when queried. - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It intercepts the `eth_accounts` JSON RPC requests and never returns any addresses when queried. */ export class EmptyWalletSubprovider extends Subprovider { - // This method needs to be here to satisfy the interface but linter wants it to be static. + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ // tslint:disable-next-line:prefer-function-over-method - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { + public handleRequest(payload: Web3.JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { switch (payload.method) { case 'eth_accounts': end(null, []); diff --git a/packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts b/packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts index 2421dcd02..a6f978db1 100644 --- a/packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts +++ b/packages/subproviders/src/subproviders/fake_gas_estimate_subprovider.ts @@ -1,28 +1,38 @@ import * as Web3 from 'web3'; +import { Callback, ErrorCallback } from '../types'; + import { Subprovider } from './subprovider'; -/* - * This class implements the web3-provider-engine subprovider interface and returns - * the constant gas estimate when queried. - * HACK: We need this so that our tests don't use testrpc gas estimation which sometimes kills the node. - * Source: https://github.com/trufflesuite/ganache-cli/issues/417 - * Source: https://github.com/trufflesuite/ganache-cli/issues/437 - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +// HACK: We need this so that our tests don't use testrpc gas estimation which sometimes kills the node. +// Source: https://github.com/trufflesuite/ganache-cli/issues/417 +// Source: https://github.com/trufflesuite/ganache-cli/issues/437 +// Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It intercepts the `eth_estimateGas` JSON RPC call and always returns a constant gas amount when queried. */ export class FakeGasEstimateSubprovider extends Subprovider { private _constantGasAmount: number; + /** + * Instantiates an instance of the FakeGasEstimateSubprovider + * @param constantGasAmount The constant gas amount you want returned + */ constructor(constantGasAmount: number) { super(); this._constantGasAmount = constantGasAmount; } - // This method needs to be here to satisfy the interface but linter wants it to be static. + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ // tslint:disable-next-line:prefer-function-over-method - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { + public handleRequest(payload: Web3.JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { switch (payload.method) { case 'eth_estimateGas': end(null, this._constantGasAmount); diff --git a/packages/subproviders/src/subproviders/ganache.ts b/packages/subproviders/src/subproviders/ganache.ts index a979aecf4..fc0b9c3d2 100644 --- a/packages/subproviders/src/subproviders/ganache.ts +++ b/packages/subproviders/src/subproviders/ganache.ts @@ -1,26 +1,34 @@ import * as Ganache from 'ganache-core'; import * as Web3 from 'web3'; +import { Callback, ErrorCallback } from '../types'; + import { Subprovider } from './subprovider'; -/* - * This class implements the web3-provider-engine subprovider interface and returns - * the provider connected to a in-process ganache. - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It intercepts all JSON RPC requests and relays them to an in-process ganache instance. */ export class GanacheSubprovider extends Subprovider { private _ganacheProvider: Web3.Provider; + /** + * Instantiates a GanacheSubprovider + * @param opts The desired opts with which to instantiate the Ganache provider + */ constructor(opts: any) { super(); this._ganacheProvider = Ganache.provider(opts); } - // This method needs to be here to satisfy the interface but linter wants it to be static. + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ // tslint:disable-next-line:prefer-function-over-method - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { + public handleRequest(payload: Web3.JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { this._ganacheProvider.sendAsync(payload, (err: Error | null, result: any) => { end(err, result && result.result); }); diff --git a/packages/subproviders/src/subproviders/injected_web3.ts b/packages/subproviders/src/subproviders/injected_web3.ts index 6d4e2b27b..cd9b0b603 100644 --- a/packages/subproviders/src/subproviders/injected_web3.ts +++ b/packages/subproviders/src/subproviders/injected_web3.ts @@ -1,25 +1,36 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; +import { Callback, ErrorCallback } from '../types'; + import { Subprovider } from './subprovider'; -/* - * This class implements the web3-provider-engine subprovider interface and forwards - * requests involving user accounts (getAccounts, sendTransaction, etc...) to the injected - * provider instance in their browser. - * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) + * subprovider interface. It forwards JSON RPC requests involving user accounts (getAccounts, + * sendTransaction, etc...) to the provider instance supplied at instantiation. All other requests + * are passed onwards for subsequent subproviders to handle. */ export class InjectedWeb3Subprovider extends Subprovider { private _injectedWeb3: Web3; - constructor(subprovider: Web3.Provider) { + /** + * Instantiates a new InjectedWeb3Subprovider + * @param provider Web3 provider that should handle all user account related requests + */ + constructor(provider: Web3.Provider) { super(); - this._injectedWeb3 = new Web3(subprovider); + this._injectedWeb3 = new Web3(provider); } - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:prefer-function-over-method + public handleRequest(payload: Web3.JSONRPCRequestPayload, next: Callback, end: ErrorCallback) { switch (payload.method) { case 'web3_clientVersion': this._injectedWeb3.version.getNode(end); diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index b67b49bee..8bc70d8b6 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -8,6 +8,7 @@ import { Lock } from 'semaphore-async-await'; import * as Web3 from 'web3'; import { + Callback, LedgerEthereumClient, LedgerEthereumClientFactoryAsync, LedgerSubproviderConfigs, @@ -23,6 +24,11 @@ const DEFAULT_NUM_ADDRESSES_TO_FETCH = 10; const ASK_FOR_ON_DEVICE_CONFIRMATION = false; const SHOULD_GET_CHAIN_CODE = true; +/** + * Subprovider for interfacing with a user's [Ledger Nano S](https://www.ledgerwallet.com/products/ledger-nano-s). + * This subprovider intercepts all account related RPC requests (e.g message/transaction signing, etc...) and + * re-routes them to a Ledger device plugged into the users computer. + */ export class LedgerSubprovider extends Subprovider { private _nonceLock = new Lock(); private _connectionLock = new Lock(); @@ -37,6 +43,12 @@ export class LedgerSubprovider extends Subprovider { throw new Error(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); } } + /** + * Instantiates a LedgerSubprovider. Defaults to derivationPath set to `44'/60'/0'`. + * TestRPC/Ganache defaults to `m/44'/60'/0'/0`, so set this in the configs if desired. + * @param config Several available configurations + * @return LedgerSubprovider instance + */ constructor(config: LedgerSubproviderConfigs) { super(); this._networkId = config.networkId; @@ -49,84 +61,38 @@ export class LedgerSubprovider extends Subprovider { : ASK_FOR_ON_DEVICE_CONFIRMATION; this._derivationPathIndex = 0; } + /** + * Retrieve the set derivation path + * @returns derivation path + */ public getPath(): string { return this._derivationPath; } + /** + * Set a desired derivation path when computing the available user addresses + * @param derivationPath The desired derivation path (e.g `44'/60'/0'`) + */ public setPath(derivationPath: string) { this._derivationPath = derivationPath; } + /** + * Set the final derivation path index. If a user wishes to sign a message with the + * 6th address in a derivation path, before calling `signPersonalMessageAsync`, you must + * call this method with pathIndex `6`. + * @param pathIndex Desired derivation path index + */ public setPathIndex(pathIndex: number) { this._derivationPathIndex = pathIndex; } - // Required to implement this public interface which doesn't conform to our linting rule. - // tslint:disable-next-line:async-suffix - public async handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result?: any) => void, - ) { - let accounts; - let txParams; - switch (payload.method) { - case 'eth_coinbase': - try { - accounts = await this.getAccountsAsync(); - end(null, accounts[0]); - } catch (err) { - end(err); - } - return; - - case 'eth_accounts': - try { - accounts = await this.getAccountsAsync(); - end(null, accounts); - } catch (err) { - end(err); - } - return; - - case 'eth_sendTransaction': - txParams = payload.params[0]; - try { - LedgerSubprovider._validateSender(txParams.from); - const result = await this._sendTransactionAsync(txParams); - end(null, result); - } catch (err) { - end(err); - } - return; - - case 'eth_signTransaction': - txParams = payload.params[0]; - try { - const result = await this._signTransactionWithoutSendingAsync(txParams); - end(null, result); - } catch (err) { - end(err); - } - return; - - case 'eth_sign': - case 'personal_sign': - const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; - try { - if (_.isUndefined(data)) { - throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage); - } - assert.isHexString('data', data); - const ecSignatureHex = await this.signPersonalMessageAsync(data); - end(null, ecSignatureHex); - } catch (err) { - end(err); - } - return; - - default: - next(); - return; - } - } + /** + * Retrieve a users Ledger accounts. The accounts are derived from the derivationPath, + * master public key and chainCode. Because of this, you can request as many accounts + * as you wish and it only requires a single request to the Ledger device. This method + * is automatically called when issuing a `eth_accounts` JSON RPC request via your providerEngine + * instance. + * @param numberOfAccounts Number of accounts to retrieve (default: 10) + * @return An array of accounts + */ public async getAccountsAsync(numberOfAccounts: number = DEFAULT_NUM_ADDRESSES_TO_FETCH): Promise<string[]> { this._ledgerClientIfExists = await this._createLedgerClientAsync(); @@ -158,6 +124,14 @@ export class LedgerSubprovider extends Subprovider { } return accounts; } + /** + * Sign a transaction with the Ledger. If you've added the LedgerSubprovider to your + * app's provider, you can simply send an `eth_sendTransaction` JSON RPC request, and + * this method will be called auto-magically. If you are not using this via a ProviderEngine + * instance, you can call it directly. + * @param txParams Parameters of the transaction to sign + * @return Signed transaction hex string + */ public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { this._ledgerClientIfExists = await this._createLedgerClientAsync(); @@ -193,6 +167,16 @@ export class LedgerSubprovider extends Subprovider { throw err; } } + /** + * Sign a personal Ethereum signed message. The signing address will be to one + * retrieved given a derivationPath and pathIndex set on the subprovider. + * The Ledger adds the Ethereum signed message prefix on-device. If you've added + * the LedgerSubprovider to your app's provider, you can simply send an `eth_sign` + * or `personal_sign` JSON RPC request, and this method will be called auto-magically. + * If you are not using this via a ProviderEngine instance, you can call it directly. + * @param data Message to sign + * @return Signature hex string (order: rsv) + */ public async signPersonalMessageAsync(data: string): Promise<string> { this._ledgerClientIfExists = await this._createLedgerClientAsync(); try { @@ -214,6 +198,82 @@ export class LedgerSubprovider extends Subprovider { throw err; } } + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:async-suffix + public async handleRequest( + payload: Web3.JSONRPCRequestPayload, + next: Callback, + end: (err: Error | null, result?: any) => void, + ) { + let accounts; + let txParams; + switch (payload.method) { + case 'eth_coinbase': + try { + accounts = await this.getAccountsAsync(); + end(null, accounts[0]); + } catch (err) { + end(err); + } + return; + + case 'eth_accounts': + try { + accounts = await this.getAccountsAsync(); + end(null, accounts); + } catch (err) { + end(err); + } + return; + + case 'eth_sendTransaction': + txParams = payload.params[0]; + try { + LedgerSubprovider._validateSender(txParams.from); + const result = await this._sendTransactionAsync(txParams); + end(null, result); + } catch (err) { + end(err); + } + return; + + case 'eth_signTransaction': + txParams = payload.params[0]; + try { + const result = await this._signTransactionWithoutSendingAsync(txParams); + end(null, result); + } catch (err) { + end(err); + } + return; + + case 'eth_sign': + case 'personal_sign': + const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; + try { + if (_.isUndefined(data)) { + throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage); + } + assert.isHexString('data', data); + const ecSignatureHex = await this.signPersonalMessageAsync(data); + end(null, ecSignatureHex); + } catch (err) { + end(err); + } + return; + + default: + next(); + return; + } + } private _getDerivationPath() { const derivationPath = `${this.getPath()}/${this._derivationPathIndex}`; return derivationPath; diff --git a/packages/subproviders/src/subproviders/nonce_tracker.ts b/packages/subproviders/src/subproviders/nonce_tracker.ts index 82eab4a9a..249f16199 100644 --- a/packages/subproviders/src/subproviders/nonce_tracker.ts +++ b/packages/subproviders/src/subproviders/nonce_tracker.ts @@ -10,13 +10,13 @@ import { Callback, ErrorCallback, NextCallback, NonceSubproviderErrors } from '. import { Subprovider } from './subprovider'; -// We do not export this since this is not our error, and we do not throw this error const NONCE_TOO_LOW_ERROR_MESSAGE = 'Transaction nonce is too low'; -/* - This class is heavily inspiried by the Web3ProviderEngine NonceSubprovider - We have added the additional feature of clearing any nonce balues when an error message - describes a nonce value being too low. -*/ + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It is heavily inspired by the [NonceSubprovider](https://github.com/MetaMask/provider-engine/blob/master/subproviders/nonce-tracker.js). + * We added the additional feature of clearing the cached nonce value when a `nonce value too low` error occurs. + */ export class NonceTrackerSubprovider extends Subprovider { private _nonceCache: { [address: string]: string } = {}; private static _reconstructTransaction(payload: Web3.JSONRPCRequestPayload): EthereumTx { @@ -46,7 +46,14 @@ export class NonceTrackerSubprovider extends Subprovider { throw new Error(NonceSubproviderErrors.CannotDetermineAddressFromPayload); } } - // Required to implement this public interface which doesn't conform to our linting rule. + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ // tslint:disable-next-line:async-suffix public async handleRequest( payload: Web3.JSONRPCRequestPayload, diff --git a/packages/subproviders/src/subproviders/redundant_rpc.ts b/packages/subproviders/src/subproviders/redundant_rpc.ts index 0df2f91f4..ace2ed3c8 100644 --- a/packages/subproviders/src/subproviders/redundant_rpc.ts +++ b/packages/subproviders/src/subproviders/redundant_rpc.ts @@ -3,14 +3,21 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); +import { Callback } from '../types'; + import { Subprovider } from './subprovider'; +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It attempts to handle each JSON RPC request by sequentially attempting to receive a valid response from one of a + * set of JSON RPC endpoints. + */ export class RedundantRPCSubprovider extends Subprovider { private _rpcs: RpcSubprovider[]; private static async _firstSuccessAsync( rpcs: RpcSubprovider[], payload: Web3.JSONRPCRequestPayload, - next: () => void, + next: Callback, ): Promise<any> { let lastErr: Error | undefined; for (const rpc of rpcs) { @@ -26,6 +33,10 @@ export class RedundantRPCSubprovider extends Subprovider { throw lastErr; } } + /** + * Instantiates a new RedundantRPCSubprovider + * @param endpoints JSON RPC endpoints to attempt. Attempts are made in the order of the endpoints. + */ constructor(endpoints: string[]) { super(); this._rpcs = _.map(endpoints, endpoint => { @@ -34,11 +45,18 @@ export class RedundantRPCSubprovider extends Subprovider { }); }); } - // Required to implement this public interface which doesn't conform to our linting rule. + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ // tslint:disable-next-line:async-suffix public async handleRequest( payload: Web3.JSONRPCRequestPayload, - next: () => void, + next: Callback, end: (err: Error | null, data?: any) => void, ): Promise<void> { const rpcsCopy = this._rpcs.slice(); diff --git a/packages/subproviders/src/subproviders/subprovider.ts b/packages/subproviders/src/subproviders/subprovider.ts index d4e0de67a..4fa351e11 100644 --- a/packages/subproviders/src/subproviders/subprovider.ts +++ b/packages/subproviders/src/subproviders/subprovider.ts @@ -1,9 +1,10 @@ import promisify = require('es6-promisify'); import * as Web3 from 'web3'; -/* - * A version of the base class Subprovider found in providerEngine + +import { JSONRPCRequestPayloadWithMethod } from '../types'; +/** + * A altered version of the base class Subprovider found in [web3-provider-engine](https://github.com/MetaMask/provider-engine). * This one has an async/await `emitPayloadAsync` and also defined types. - * Altered version of: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class Subprovider { private _engine: any; @@ -18,8 +19,8 @@ export class Subprovider { return datePart + extraPart; } private static _createFinalPayload( - payload: Partial<Web3.JSONRPCRequestPayload> & { method: string }, - ): Web3.JSONRPCRequestPayload { + payload: Partial<JSONRPCRequestPayloadWithMethod>, + ): Partial<JSONRPCRequestPayloadWithMethod> { const finalPayload = { // defaults id: Subprovider._getRandomId(), @@ -29,14 +30,26 @@ export class Subprovider { }; return finalPayload; } - public setEngine(engine: any): void { - this._engine = engine; - } + /** + * Emits a JSON RPC payload that will then be handled by the ProviderEngine instance + * this subprovider is a part of. The payload will cascade down the subprovider middleware + * stack until finding the responsible entity for handling the request. + * @param payload JSON RPC payload + * @returns JSON RPC response payload + */ public async emitPayloadAsync( - payload: Partial<Web3.JSONRPCRequestPayload> & { method: string }, + payload: Partial<JSONRPCRequestPayloadWithMethod>, ): Promise<Web3.JSONRPCResponsePayload> { const finalPayload = Subprovider._createFinalPayload(payload); const response = await promisify(this._engine.sendAsync, this._engine)(finalPayload); return response; } + /** + * Set's the subprovider's engine to the ProviderEngine it is added to. + * This is only called within the ProviderEngine source code, do not call + * directly. + */ + public setEngine(engine: any): void { + this._engine = engine; + } } diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts index 543da5947..9bb9ff696 100644 --- a/packages/subproviders/src/types.ts +++ b/packages/subproviders/src/types.ts @@ -1,4 +1,8 @@ +import { ECSignature } from '@0xproject/types'; import * as _ from 'lodash'; +import * as Web3 from 'web3'; + +export { ECSignature } from '@0xproject/types'; export interface LedgerCommunicationClient { close: () => Promise<void>; @@ -28,12 +32,6 @@ export interface ECSignatureString { s: string; } -export interface ECSignature { - v: number; - r: string; - s: string; -} - export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClient>; /* @@ -117,3 +115,7 @@ export type ErrorCallback = (err: Error | null, data?: any) => void; export type Callback = () => void; export type OnNextCompleted = (err: Error | null, result: any, cb: Callback) => void; export type NextCallback = (callback?: OnNextCompleted) => void; + +export interface JSONRPCRequestPayloadWithMethod extends Web3.JSONRPCRequestPayload { + method: string; +} diff --git a/packages/tslint-config/tslint.json b/packages/tslint-config/tslint.json index 3266b022f..aacfb6d59 100644 --- a/packages/tslint-config/tslint.json +++ b/packages/tslint-config/tslint.json @@ -96,5 +96,5 @@ "jsx-wrap-multiline": false, "jsx-no-bind": false }, - "rulesDirectory": "lib" + "rulesDirectory": "lib/rules" } diff --git a/packages/web3-wrapper/CHANGELOG.md b/packages/web3-wrapper/CHANGELOG.md index 5f6523209..9fbc9021c 100644 --- a/packages/web3-wrapper/CHANGELOG.md +++ b/packages/web3-wrapper/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.0 _TBD_ + + * Rename `signTransactionAsync` to `signMessageAsync` for clarity (#465) + ## v0.3.0 _March 18, 2018_ * Add `web3Wrapper.takeSnapshotAsync`, `web3Wrapper.revertSnapshotAsync`, `web3Wrapper.mineBlockAsync`, `web3Wrapper.increaseTimeAsync` (#426) diff --git a/packages/web3-wrapper/README.md b/packages/web3-wrapper/README.md index 0d5defb1e..1fed11407 100644 --- a/packages/web3-wrapper/README.md +++ b/packages/web3-wrapper/README.md @@ -1,6 +1,8 @@ ## @0xproject/web3-wrapper -Wrapped version of web3 with a nicer interface that is used across 0x projects and packages +Wrapped version of web3 with a nicer interface that is used across 0x projects and packages. + +### Read the [Documentation](0xproject.com/docs/web3_wrapper). ## Installation @@ -16,16 +18,6 @@ If your project is in [TypeScript](https://www.typescriptlang.org/), add the fol ] ``` -## Usage - -```typescript -import {Web3Wrapper} from '@0xproject/web3-wrapper'; - -const web3 = ...; -const web3Wrapper = new Web3Wrapper(web3.currentProvider); -const availableAddresses = await web3Wrapper.getAvailableAddressesAsync(); -``` - ## Contributing We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 3e0a7fd42..176f796c8 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -8,7 +8,19 @@ "build:watch": "tsc -w", "build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts", "clean": "shx rm -rf lib scripts", - "lint": "tslint --project . 'src/**/*.ts'" + "lint": "tslint --project . 'src/**/*.ts'", + "docs:stage": "yarn build && node ./scripts/stage_docs.js", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES", + "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json" + }, + "config": { + "postpublish": { + "docPublishConfigs": { + "extraFileIncludes": ["../types/src/index.ts"], + "s3BucketPath": "s3://doc-jsons/web3-wrapper/", + "s3StagingBucketPath": "s3://staging-doc-jsons/web3-wrapper/" + } + } }, "license": "Apache-2.0", "repository": { @@ -27,6 +39,7 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.8.0", + "typedoc": "0xProject/typedoc", "typescript": "2.7.1" }, "dependencies": { diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts index 02d5e4f7b..2ce2580ee 100644 --- a/packages/web3-wrapper/src/index.ts +++ b/packages/web3-wrapper/src/index.ts @@ -3,12 +3,24 @@ import { BigNumber, promisify } from '@0xproject/utils'; import * as _ from 'lodash'; import * as Web3 from 'web3'; +/** + * A wrapper around the Web3.js 0.x library that provides a consistent, clean promise-based interface. + */ export class Web3Wrapper { - // This is here purely to reliably distinguish it from other objects in runtime (like BigNumber.isBigNumber) + /** + * Flag to check if this instance is of type Web3Wrapper + */ public isZeroExWeb3Wrapper = true; private _web3: Web3; private _defaults: Partial<TxData>; private _jsonRpcRequestId: number; + /** + * Instantiates a new Web3Wrapper. + * @param provider The Web3 provider instance you would like the Web3Wrapper to use for interacting with + * the backing Ethereum node. + * @param defaults Override TxData defaults sent with RPC requests to the backing Ethereum node. + * @return An instance of the Web3Wrapper class. + */ constructor(provider: Web3.Provider, defaults?: Partial<TxData>) { if (_.isUndefined((provider as any).sendAsync)) { // Web3@1.0 provider doesn't support synchronous http requests, @@ -21,32 +33,69 @@ export class Web3Wrapper { this._defaults = defaults || {}; this._jsonRpcRequestId = 0; } + /** + * Get the contract defaults set to the Web3Wrapper instance + * @return TxData defaults (e.g gas, gasPrice, nonce, etc...) + */ public getContractDefaults(): Partial<TxData> { return this._defaults; } + /** + * Retrieve the Web3 provider + * @return Web3 provider instance + */ public getProvider(): Web3.Provider { return this._web3.currentProvider; } + /** + * Update the used Web3 provider + * @param provider The new Web3 provider to be set + */ public setProvider(provider: Web3.Provider) { this._web3.setProvider(provider); } + /** + * Check if an address is a valid Ethereum address + * @param address Address to check + * @returns Whether the address is a valid Ethereum address + */ public isAddress(address: string): boolean { return this._web3.isAddress(address); } + /** + * Check whether an address is available through the backing provider. This can be + * useful if you want to know whether a user can sign messages or transactions from + * a given Ethereum address. + * @param senderAddress Address to check availability for + * @returns Whether the address is available through the provider. + */ public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> { const addresses = await this.getAvailableAddressesAsync(); const normalizedAddress = senderAddress.toLowerCase(); return _.includes(addresses, normalizedAddress); } + /** + * Fetch the backing Ethereum node's version string (e.g `MetaMask/v4.2.0`) + * @returns Ethereum node's version string + */ public async getNodeVersionAsync(): Promise<string> { const nodeVersion = await promisify<string>(this._web3.version.getNode)(); return nodeVersion; } + /** + * Fetches the networkId of the backing Ethereum node + * @returns The network id + */ public async getNetworkIdAsync(): Promise<number> { const networkIdStr = await promisify<string>(this._web3.version.getNetwork)(); const networkId = _.parseInt(networkIdStr); return networkId; } + /** + * Retrieves the transaction receipt for a given transaction hash + * @param txHash Transaction hash + * @returns The transaction receipt, including it's status (0: failed, 1: succeeded or undefined: not found) + */ public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> { const transactionReceipt = await promisify<TransactionReceipt>(this._web3.eth.getTransactionReceipt)(txHash); if (!_.isNull(transactionReceipt)) { @@ -54,60 +103,117 @@ export class Web3Wrapper { } return transactionReceipt; } - public getCurrentProvider(): Web3.Provider { - return this._web3.currentProvider; - } + /** + * Convert an Ether amount from ETH to Wei + * @param ethAmount Amount of Ether to convert to wei + * @returns Amount in wei + */ public toWei(ethAmount: BigNumber): BigNumber { const balanceWei = this._web3.toWei(ethAmount, 'ether'); return balanceWei; } + /** + * Retrieves an accounts Ether balance in wei + * @param owner Account whose balance you wish to check + * @returns Balance in wei + */ public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> { let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); // Rewrap in a new BigNumber balanceInWei = new BigNumber(balanceInWei); return balanceInWei; } + /** + * Check if a contract exists at a given address + * @param address Address to which to check + * @returns Whether or not contract code was found at the supplied address + */ public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { const code = await promisify<string>(this._web3.eth.getCode)(address); // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients const codeIsEmpty = /^0x0{0,40}$/i.test(code); return !codeIsEmpty; } - public async signTransactionAsync(address: string, message: string): Promise<string> { + /** + * Sign a message with a specific address's private key (`eth_sign`) + * @param address Address of signer + * @param message Message to sign + * @returns Signature string (might be VRS or RSV depending on the Signer) + */ + public async signMessageAsync(address: string, message: string): Promise<string> { const signData = await promisify<string>(this._web3.eth.sign)(address, message); return signData; } + /** + * Fetches the latest block number + * @returns Block number + */ public async getBlockNumberAsync(): Promise<number> { const blockNumber = await promisify<number>(this._web3.eth.getBlockNumber)(); return blockNumber; } + /** + * Fetch a specific Ethereum block + * @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral) + * @returns The requested block without transaction data + */ public async getBlockAsync(blockParam: string | Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> { const block = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockParam); return block; } + /** + * Fetch a block's timestamp + * @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral) + * @returns The block's timestamp + */ public async getBlockTimestampAsync(blockParam: string | Web3.BlockParam): Promise<number> { const { timestamp } = await this.getBlockAsync(blockParam); return timestamp; } + /** + * Retrieve the user addresses available through the backing provider + * @returns Available user addresses + */ public async getAvailableAddressesAsync(): Promise<string[]> { const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); const normalizedAddresses = _.map(addresses, address => address.toLowerCase()); return normalizedAddresses; } + /** + * Take a snapshot of the blockchain state on a TestRPC/Ganache local node + * @returns The snapshot id. This can be used to revert to this snapshot + */ public async takeSnapshotAsync(): Promise<number> { const snapshotId = Number(await this._sendRawPayloadAsync<string>({ method: 'evm_snapshot', params: [] })); return snapshotId; } + /** + * Revert the blockchain state to a previous snapshot state on TestRPC/Ganache local node + * @param snapshotId snapshot id to revert to + * @returns Whether the revert was successful + */ public async revertSnapshotAsync(snapshotId: number): Promise<boolean> { const didRevert = await this._sendRawPayloadAsync<boolean>({ method: 'evm_revert', params: [snapshotId] }); return didRevert; } + /** + * Mine a block on a TestRPC/Ganache local node + */ public async mineBlockAsync(): Promise<void> { await this._sendRawPayloadAsync<string>({ method: 'evm_mine', params: [] }); } + /** + * Increase the next blocks timestamp on TestRPC/Ganache local node + * @param timeDelta Amount of time to add in seconds + */ public async increaseTimeAsync(timeDelta: number): Promise<void> { await this._sendRawPayloadAsync<string>({ method: 'evm_increaseTime', params: [timeDelta] }); } + /** + * Retrieve smart contract logs for a given filter + * @param filter Parameters by which to filter which logs to retrieve + * @returns The corresponding log entries + */ public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> { let fromBlock = filter.fromBlock; if (_.isNumber(fromBlock)) { @@ -132,18 +238,39 @@ export class Web3Wrapper { const formattedLogs = _.map(rawLogs, this._formatLog.bind(this)); return formattedLogs; } + /** + * Get a Web3 contract factory instance for a given ABI + * @param abi Smart contract ABI + * @returns Web3 contract factory which can create Web3 Contract instances from the supplied ABI + */ public getContractFromAbi(abi: Web3.ContractAbi): Web3.Contract<any> { const web3Contract = this._web3.eth.contract(abi); return web3Contract; } + /** + * Calculate the estimated gas cost for a given transaction + * @param txData Transaction data + * @returns Estimated gas cost + */ public async estimateGasAsync(txData: Partial<Web3.TxData>): Promise<number> { const gas = await promisify<number>(this._web3.eth.estimateGas)(txData); return gas; } + /** + * Call a smart contract method at a given block height + * @param callData Call data + * @param defaultBlock Block height at which to make the call. Defaults to `latest` + * @returns The raw call result + */ public async callAsync(callData: Web3.CallData, defaultBlock?: Web3.BlockParam): Promise<string> { - const rawCalllResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock); - return rawCalllResult; + const rawCallResult = await promisify<string>(this._web3.eth.call)(callData, defaultBlock); + return rawCallResult; } + /** + * Send a transaction + * @param txData Transaction data + * @returns Transaction hash + */ public async sendTransactionAsync(txData: Web3.TxData): Promise<string> { const txHash = await promisify<string>(this._web3.eth.sendTransaction)(txData); return txHash; diff --git a/packages/web3-wrapper/src/monorepo_scripts/stage_docs.ts b/packages/web3-wrapper/src/monorepo_scripts/stage_docs.ts new file mode 100644 index 000000000..e732ac8eb --- /dev/null +++ b/packages/web3-wrapper/src/monorepo_scripts/stage_docs.ts @@ -0,0 +1,8 @@ +import { postpublishUtils } from '@0xproject/monorepo-scripts'; + +import * as packageJSON from '../package.json'; +import * as tsConfigJSON from '../tsconfig.json'; + +const cwd = `${__dirname}/..`; +// tslint:disable-next-line:no-floating-promises +postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/website/md/docs/deployer/installation.md b/packages/website/md/docs/deployer/installation.md new file mode 100644 index 000000000..c02dbadc6 --- /dev/null +++ b/packages/website/md/docs/deployer/installation.md @@ -0,0 +1,24 @@ +#### CLI Installation + +```bash +yarn global add @0xproject/deployer +``` + +#### API Installation + +```bash +yarn add @0xproject/deployer +``` + +**Import** + +```typescript +import { Deployer, Compiler } from '@0xproject/deployer'; +``` + +or + +```javascript +var Deployer = require('@0xproject/deployer').Deployer; +var Compiler = require('@0xproject/deployer').Compiler; +``` diff --git a/packages/website/md/docs/deployer/introduction.md b/packages/website/md/docs/deployer/introduction.md new file mode 100644 index 000000000..7ebd26a3c --- /dev/null +++ b/packages/website/md/docs/deployer/introduction.md @@ -0,0 +1,18 @@ +Welcome to the [Deployer](https://github.com/0xProject/0x-monorepo/tree/development/packages/deployer) documentation! Deployer is a tool for compiling and deploying Solidity smart contracts with ease. + +It serves a similar purpose as parts of the [Truffle framework](http://truffleframework.com/), but with the UNIX philosophy in mind: Make each program do one thing well. This tool is for intermediate to advanced Solidity developers that require greater configurability and reliability. + +Deployer has the following advantages over Truffle: + +* Deploy each smart contract with a specific version of Solidity. +* Improved artifact files: + * Properly segregated artifacts to support storing different versions of smart contract deployed on different networks. + * Storage of constructor args, source maps and paths to all requisite source files. + * An easy to maintain codebase: TypeScript + Single repo. + * Allows you to specify the deployer RPC address. + * Supports Solidity version ranges - contract compiled with latest Solidity version that satisfies the range. + * Migrations that work with `async/await`. + * Migrations that can be written synchronously in order to guarentee deterministic contract addresses. + * No race conditions when running migrations. + +Deployer can be used as a command-line tool or as an imported module. diff --git a/packages/website/md/docs/deployer/usage.md b/packages/website/md/docs/deployer/usage.md new file mode 100644 index 000000000..295af55e1 --- /dev/null +++ b/packages/website/md/docs/deployer/usage.md @@ -0,0 +1,56 @@ +#### CLI Usage + +```bash +$ 0x-deployer --help +0x-deployer [command] + +Commands: + 0x-deployer compile compile contracts + 0x-deployer deploy deploy a single contract with provided arguments + +Options: + --version Show version number [boolean] + --contracts-dir path of contracts directory to compile [string] [default: + "/path/to/contracts"] + --network-id mainnet=1, kovan=42, testrpc=50 [number] [default: 50] + --should-optimize enable optimizer [boolean] [default: false] + --artifacts-dir path to write contracts artifacts to [string] [default: + "/path/to/artifacts"] + --jsonrpc-port port connected to JSON RPC [number] [default: 8545] + --gas-price gasPrice to be used for transactions + [string] [default: "2000000000"] + --account account to use for deploying contracts [string] + --contracts comma separated list of contracts to compile + [string] [default: "*"] + --help Show help [boolean] +``` + +#### API Usage + +##### Migrations + +You can write migration scripts (similar to `truffle migrate`), that deploys multiple contracts and configures them. Below you'll find a simple example of such a script to help you get started. + +```typescript +import { Deployer } from '@0xproject/deployer'; +import * as path from 'path'; + +const deployerOpts = { + artifactsDir: path.resolve('src', 'artifacts'), + jsonrpcUrl: 'http://localhost:8545', + networkId: 50, + defaults: { + gas: 1000000, + }, +}; + +const deployer = new Deployer(deployerOpts); + +(async () => { + const etherToken = await deployer.deployAndSaveAsync('WETH9'); +})().catch(console.log); +``` + +**Tip:** Be sure to start an Ethereum node at the supplied `jsonrpcUrl`. We recommend testing with [Ganache-cli](https://github.com/trufflesuite/ganache-cli) + +A more sophisticated example can be found [here](https://github.com/0xProject/0x-monorepo/tree/development/packages/contracts/migrations) diff --git a/packages/website/md/docs/json_schemas/installation.md b/packages/website/md/docs/json_schemas/installation.md new file mode 100644 index 000000000..50a37bae1 --- /dev/null +++ b/packages/website/md/docs/json_schemas/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0xproject/json-schemas +``` + +**Import** + +```javascript +import { schemas } from '@0xproject/json-schemas'; +``` + +or + +```javascript +var schemas = require('@0xproject/json-schemas').schemas; +``` diff --git a/packages/website/md/docs/json_schemas/introduction.md b/packages/website/md/docs/json_schemas/introduction.md new file mode 100644 index 000000000..a27f4b521 --- /dev/null +++ b/packages/website/md/docs/json_schemas/introduction.md @@ -0,0 +1,3 @@ +Welcome to the [@0xproject/json-schemas](https://github.com/0xProject/0x-monorepo/tree/development/packages/json-schemas) documentation! This package provides JSON schemas for validating 0x Protocol & Standard Relayer API data structures. It provides both the raw JSON schemas and a schema validator class to interact with them from a JS project. + +If you are not using a Javascript-based language for your project, you can copy-paste the JSON schemas within this package and use them together with a [JSON Schema](http://json-schema.org/) implementation in your [language of choice](http://json-schema.org/implementations.html) (e.g Python, Haskell, Go, C, C++, Rust, Ruby, Scala, etc...). diff --git a/packages/website/md/docs/json_schemas/schemas.md b/packages/website/md/docs/json_schemas/schemas.md new file mode 100644 index 000000000..fcf5d8df6 --- /dev/null +++ b/packages/website/md/docs/json_schemas/schemas.md @@ -0,0 +1,28 @@ +0x Protocol Schemas + +* [Basic types](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/basic_type_schemas.ts) (e.g Ethereum address, number) +* [ECSignature](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/ec_signature_schema.ts) +* [Order/SignedOrder](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/order_schemas.ts) +* [OrderHash](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/order_hash_schema.ts) + +0x.js Schemas + +* [BlockRange](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/block_range_schema.ts) +* [IndexFilter Values](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/index_filter_values_schema.ts) +* [OrderFillRequests](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/order_fill_requests_schema.ts) +* [OrderCancellationRequests](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/order_cancel_schema.ts) +* [OrderFillOrKillRequests](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.ts) +* [SignedOrders](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/signed_orders_schema.ts) +* [Token](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/token_schema.ts) +* [TxData](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/tx_data_schema.ts) + +Standard Relayer API Schemas + +* [Error response](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/relayer_api_error_response_schema.ts) +* [Fees payload](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/relayer_api_fees_payload_schema.ts) +* [Fees response](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/relayer_api_fees_response_schema.ts) +* [Orderbook channel subscribe](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts) +* [Orderbook channel snapshot](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts) +* [Orderbook channel update](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts) +* [Orderbook response](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.ts) +* [Token pairs response](https://github.com/0xProject/0x-monorepo/blob/d4c1b3b0bd26e730ce6687469cdf7283877543e1/packages/json-schemas/schemas/relayer_api_token_pairs_response_schema.ts) diff --git a/packages/website/md/docs/json_schemas/usage.md b/packages/website/md/docs/json_schemas/usage.md new file mode 100644 index 000000000..68b801153 --- /dev/null +++ b/packages/website/md/docs/json_schemas/usage.md @@ -0,0 +1,14 @@ +The following example shows you how to validate a 0x order using the `@0xproject/json-schemas` package. + +```javascript +import {SchemaValidator, ValidatorResult, schemas} from '@0xproject/json-schemas'; + +const {orderSchema} = schemas; +const validator = new SchemaValidator(); + +const order = { + ... +}; +const validatorResult: ValidatorResult = validator.validate(order, orderSchema); // Contains all errors +const isValid: boolean = validator.isValid(order, orderSchema); // Only returns boolean +``` diff --git a/packages/website/md/docs/sol_cov/installation.md b/packages/website/md/docs/sol_cov/installation.md new file mode 100644 index 000000000..b9ce25a5f --- /dev/null +++ b/packages/website/md/docs/sol_cov/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0xproject/sol-cov +``` + +**Import** + +```javascript +import { CoverageSubprovider } from '@0xproject/sol-cov'; +``` + +or + +```javascript +var CoverageSubprovider = require('@0xproject/sol-cov').CoverageSubprovider; +``` diff --git a/packages/website/md/docs/sol_cov/introduction.md b/packages/website/md/docs/sol_cov/introduction.md new file mode 100644 index 000000000..7064a3554 --- /dev/null +++ b/packages/website/md/docs/sol_cov/introduction.md @@ -0,0 +1 @@ +Welcome to the [@0xproject/sol-cov](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-cov) documentation! Sol-cov is a Solidity coverage tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_cov/usage.md b/packages/website/md/docs/sol_cov/usage.md new file mode 100644 index 000000000..ea1982d97 --- /dev/null +++ b/packages/website/md/docs/sol_cov/usage.md @@ -0,0 +1,26 @@ +Sol-cov uses transaction traces in order to figure out which lines of Solidity source code have been covered by your tests. In order for it to gather these traces, you must add the `CoverageSubprovider` to the [ProviderEngine](https://github.com/MetaMask/provider-engine) instance you use when running your Solidity tests. If you're unfamiliar with ProviderEngine, please read the [Web3 Provider explained](https://0xproject.com/wiki#Web3-Provider-Explained) wiki article. + +The CoverageSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot. + +```typescript +import { CoverageSubprovider } from '@0xproject/sol-cov'; + +const provider = new ProviderEngine(); + +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const networkId = 50; +// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from. +const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; +const coverageSubprovider = new CoverageSubprovider(artifactsPath, contractsPath, networkId, defaultFromAddress); + +provider.addProvider(coverageSubprovider); +``` + +After your test suite is complete (e.g global `after` hook), you'll need to call: + +```typescript +await coverageSubprovider.writeCoverageAsync(); +``` + +This will create a `coverage.json` file in the `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use any of the existing Instanbul reporters. diff --git a/packages/website/md/docs/subproviders/installation.md b/packages/website/md/docs/subproviders/installation.md new file mode 100644 index 000000000..a049ff0ec --- /dev/null +++ b/packages/website/md/docs/subproviders/installation.md @@ -0,0 +1,15 @@ +```bash +npm install @0xproject/subproviders --save +``` + +**Import** + +```typescript +import { LedgerSubprovider } from '@0xproject/subproviders'; +``` + +or + +```javascript +var LedgerSubprovider = require('@0xproject/subproviders').LedgerSubprovider; +``` diff --git a/packages/website/md/docs/subproviders/introduction.md b/packages/website/md/docs/subproviders/introduction.md new file mode 100644 index 000000000..835201064 --- /dev/null +++ b/packages/website/md/docs/subproviders/introduction.md @@ -0,0 +1 @@ +Welcome to the [Subproviders](https://github.com/0xProject/0x-monorepo/tree/development/packages/subproviders) documentation! Subproviders is a package containing useful [subproviders](https://0xproject.com/wiki#Web3-Provider-Explained) that can be used with the [Web3 Provider Engine](https://github.com/MetaMask/provider-engine) library. diff --git a/packages/website/md/docs/subproviders/ledger_node_hid.md b/packages/website/md/docs/subproviders/ledger_node_hid.md new file mode 100644 index 000000000..3089817af --- /dev/null +++ b/packages/website/md/docs/subproviders/ledger_node_hid.md @@ -0,0 +1,17 @@ +By default, node-hid transport support is an optional dependency. This is due to the requirement of native usb developer packages on the host system. If these aren't installed the entire `npm install` fails. We also no longer export node-hid transport client factories. To re-create this see our integration tests or follow the example below: + +```typescript +import Eth from '@ledgerhq/hw-app-eth'; +import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'; +async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> { + const ledgerConnection = await TransportNodeHid.create(); + const ledgerEthClient = new Eth(ledgerConnection); + return ledgerEthClient; +} + +// Create a LedgerSubprovider with the node-hid transport +ledgerSubprovider = new LedgerSubprovider({ + networkId, + ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, +}); +``` diff --git a/packages/website/md/docs/web3_wrapper/installation.md b/packages/website/md/docs/web3_wrapper/installation.md new file mode 100644 index 000000000..92794d9b0 --- /dev/null +++ b/packages/website/md/docs/web3_wrapper/installation.md @@ -0,0 +1,25 @@ +**Install** + +```bash +npm install @0xproject/web3-wrapper --save +``` + +**Import** + +```javascript +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +``` + +or + +```javascript +var Web3Wrapper = require('@0xproject/web3-wrapper').Web3Wrapper; +``` + +If your project is in [TypeScript](https://www.typescriptlang.org/), add the following to your `tsconfig.json`: + +``` +"include": [ + "./node_modules/web3-typescript-typings/index.d.ts", +] +``` diff --git a/packages/website/md/docs/web3_wrapper/introduction.md b/packages/website/md/docs/web3_wrapper/introduction.md new file mode 100644 index 000000000..ea2f4cf0d --- /dev/null +++ b/packages/website/md/docs/web3_wrapper/introduction.md @@ -0,0 +1 @@ +Welcome to the [Web3Wrapper](https://github.com/0xProject/0x-monorepo/tree/development/packages/web3-wrapper) documentation! Web3Wrapper is a convenience library that wraps Web3 v0.x, providing promise-based endpoints and a consistent API. diff --git a/packages/website/public/images/doc_icons/docs.png b/packages/website/public/images/doc_icons/docs.png Binary files differnew file mode 100644 index 000000000..17df04310 --- /dev/null +++ b/packages/website/public/images/doc_icons/docs.png diff --git a/packages/website/translations/chinese.json b/packages/website/translations/chinese.json index f5f906d6c..f610bf56c 100644 --- a/packages/website/translations/chinese.json +++ b/packages/website/translations/chinese.json @@ -56,11 +56,16 @@ "ABOUT": "关于我们", "CAREERS": "人才招聘", "CONTACT": "联系方式", + "DEPLOYER": "Deployer", + "JSON_SCHEMAS": "JSON Schemas", + "SOL_COV": "Solidity Coverage", + "SUBPROVIDERS": "Subproviders", "BLOG": "博客", "FORUM": "论坛", "CONNECT": "0x 连接", "WHITEPAPER": "白皮书", "WIKI": "维基", + "WEB3_WRAPPER": "Web3Wrapper", "FAQ": "FAQ", "SMART_CONTRACTS": "0x 智能合约", "STANDARD_RELAYER_API": "中继方标准API", diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index 9a1c9b2c8..122d445cb 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -57,11 +57,16 @@ "ABOUT": "about", "CAREERS": "careers", "CONTACT": "contact", + "DEPLOYER": "Deployer", + "JSON_SCHEMAS": "JSON Schemas", + "SOL_COV": "Solidity Coverage", + "SUBPROVIDERS": "Subproviders", "BLOG": "blog", "FORUM": "forum", "CONNECT": "0x Connect", "WHITEPAPER": "whitepaper", "WIKI": "wiki", + "WEB3_WRAPPER": "Web3Wrapper", "FAQ": "FAQ", "SMART_CONTRACTS": "0x smart contracts", "STANDARD_RELAYER_API": "standard relayer API", diff --git a/packages/website/translations/korean.json b/packages/website/translations/korean.json index b9be664e2..dd5f19b16 100644 --- a/packages/website/translations/korean.json +++ b/packages/website/translations/korean.json @@ -56,11 +56,16 @@ "ABOUT": "기업 정보", "CAREERS": "채용", "CONTACT": "문의", + "DEPLOYER": "Deployer", + "JSON_SCHEMAS": "JSON Schemas", + "SOL_COV": "Solidity Coverage", + "SUBPROVIDERS": "Subproviders", "BLOG": "블로그", "FORUM": "포럼", "CONNECT": "0x Connect", "WHITEPAPER": "백서", "WIKI": "위키", + "WEB3_WRAPPER": "Web3Wrapper", "FAQ": "FAQ", "SMART_CONTRACTS": "0x 스마트 계약", "STANDARD_RELAYER_API": "Standard Relayer API", diff --git a/packages/website/translations/russian.json b/packages/website/translations/russian.json index c103960c0..5a8e2c539 100644 --- a/packages/website/translations/russian.json +++ b/packages/website/translations/russian.json @@ -56,11 +56,16 @@ "ABOUT": "Kоманда", "CAREERS": "Карьера", "CONTACT": "Связаться с нами", + "DEPLOYER": "Deployer", + "JSON_SCHEMAS": "JSON Schemas", + "SOL_COV": "Solidity Coverage", + "SUBPROVIDERS": "Subproviders", "BLOG": "Блог", "FORUM": "Форум", "CONNECT": "0x Connect", "WHITEPAPER": "Whitepaper", "WIKI": "Вики", + "WEB3_WRAPPER": "Web3Wrapper", "FAQ": "Документация", "SMART_CONTRACTS": "0x Смарт-контракты ", "STANDARD_RELAYER_API": "standard relayer API", diff --git a/packages/website/translations/spanish.json b/packages/website/translations/spanish.json index c2aaf384c..dd34e805c 100644 --- a/packages/website/translations/spanish.json +++ b/packages/website/translations/spanish.json @@ -57,11 +57,16 @@ "ABOUT": "equipo", "CAREERS": "empleo", "CONTACT": "contacto", + "DEPLOYER": "Deployer", + "JSON_SCHEMAS": "JSON Schemas", + "SOL_COV": "Solidity Coverage", + "SUBPROVIDERS": "Subproviders", "BLOG": "blog", "FORUM": "foro", "CONNECT": "0x Connect", "WHITEPAPER": "documento técnico", "WIKI": "wiki", + "WEB3_WRAPPER": "Web3Wrapper", "FAQ": "preguntas frecuentes", "SMART_CONTRACTS": "0x contratos inteligentes", "STANDARD_RELAYER_API": "API de transmisión estándar", diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index b12c637e5..59afeb50b 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -10,7 +10,14 @@ import ReactTooltip = require('react-tooltip'); import { Blockchain } from 'ts/blockchain'; import { EthWethConversionButton } from 'ts/components/eth_weth_conversion_button'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { OutdatedWrappedEtherByNetworkId, Side, Token, TokenByAddress, TokenState } from 'ts/types'; +import { + OutdatedWrappedEtherByNetworkId, + Side, + Token, + TokenByAddress, + TokenState, + TokenStateByAddress, +} from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; @@ -20,13 +27,6 @@ const ICON_DIMENSION = 40; const ETHER_ICON_PATH = '/images/ether.png'; const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png'; -interface OutdatedWETHAddressToIsStateLoaded { - [address: string]: boolean; -} -interface OutdatedWETHStateByAddress { - [address: string]: TokenState; -} - interface EthWrappersProps { networkId: number; blockchain: Blockchain; @@ -39,9 +39,7 @@ interface EthWrappersProps { interface EthWrappersState { ethTokenState: TokenState; - isWethStateLoaded: boolean; - outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; - outdatedWETHStateByAddress: OutdatedWETHStateByAddress; + outdatedWETHStateByAddress: TokenStateByAddress; } export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> { @@ -50,22 +48,20 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt super(props); this._isUnmounted = false; const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); - const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; - const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; + const outdatedWETHStateByAddress: TokenStateByAddress = {}; _.each(outdatedWETHAddresses, outdatedWETHAddress => { - outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false; outdatedWETHStateByAddress[outdatedWETHAddress] = { balance: new BigNumber(0), allowance: new BigNumber(0), + isLoaded: false, }; }); this.state = { - outdatedWETHAddressToIsStateLoaded, outdatedWETHStateByAddress, - isWethStateLoaded: false, ethTokenState: { balance: new BigNumber(0), allowance: new BigNumber(0), + isLoaded: false, }, }; } @@ -169,7 +165,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt {this._renderTokenLink(tokenLabel, etherscanUrl)} </TableRowColumn> <TableRowColumn> - {this.state.isWethStateLoaded ? ( + {this.state.ethTokenState.isLoaded ? ( `${wethBalance.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} WETH` ) : ( <i className="zmdi zmdi-spinner zmdi-hc-spin" /> @@ -183,7 +179,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt networkId={this.props.networkId} isOutdatedWrappedEther={false} direction={Side.Receive} - isDisabled={!this.state.isWethStateLoaded} + isDisabled={!this.state.ethTokenState.isLoaded} ethToken={etherToken} dispatcher={this.props.dispatcher} blockchain={this.props.blockchain} @@ -266,8 +262,8 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ...etherToken, address: outdatedWETHIfExists.address, }; - const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETHIfExists.address]; const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; + const isStateLoaded = outdatedEtherTokenState.isLoaded; const balanceInEthIfExists = isStateLoaded ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( configs.AMOUNT_DISPLAY_PRECSION, @@ -345,10 +341,15 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ); } private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { + const currentOutdatedWETHState = this.state.outdatedWETHStateByAddress[outdatedWETHAddress]; this.setState({ - outdatedWETHAddressToIsStateLoaded: { - ...this.state.outdatedWETHAddressToIsStateLoaded, - [outdatedWETHAddress]: false, + outdatedWETHStateByAddress: { + ...this.state.outdatedWETHStateByAddress, + [outdatedWETHAddress]: { + balance: currentOutdatedWETHState.balance, + allowance: currentOutdatedWETHState.allowance, + isLoaded: false, + }, }, }); const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; @@ -357,15 +358,12 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt outdatedWETHAddress, ); this.setState({ - outdatedWETHAddressToIsStateLoaded: { - ...this.state.outdatedWETHAddressToIsStateLoaded, - [outdatedWETHAddress]: true, - }, outdatedWETHStateByAddress: { ...this.state.outdatedWETHStateByAddress, [outdatedWETHAddress]: { balance, allowance, + isLoaded: true, }, }, }); @@ -380,8 +378,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ); const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); - const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; - const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; + const outdatedWETHStateByAddress: TokenStateByAddress = {}; for (const address of outdatedWETHAddresses) { const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( userAddressIfExists, @@ -390,18 +387,17 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt outdatedWETHStateByAddress[address] = { balance, allowance, + isLoaded: true, }; - outdatedWETHAddressToIsStateLoaded[address] = true; } if (!this._isUnmounted) { this.setState({ outdatedWETHStateByAddress, - outdatedWETHAddressToIsStateLoaded, ethTokenState: { balance: wethBalance, allowance: wethAllowance, + isLoaded: true, }, - isWethStateLoaded: true, }); } } @@ -434,6 +430,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ethTokenState: { balance, allowance, + isLoaded: true, }, }); } diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index 5bdb5bde9..59eaca67e 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -19,12 +19,22 @@ import { TokenBalances } from 'ts/components/token_balances'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { FlashMessage } from 'ts/components/ui/flash_message'; +import { Wallet } from 'ts/components/wallet'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; import { localStorage } from 'ts/local_storage/local_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { portalOrderSchema } from 'ts/schemas/portal_order_schema'; import { validator } from 'ts/schemas/validator'; -import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types'; +import { + BlockchainErrs, + Environments, + HashData, + Order, + ProviderType, + ScreenWidths, + TokenByAddress, + WebsitePaths, +} from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; @@ -194,6 +204,12 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { <div className="py2" style={{ backgroundColor: colors.grey50 }}> {this.props.blockchainIsLoaded ? ( <Switch> + {configs.ENVIRONMENT === Environments.DEVELOPMENT && ( + <Route + path={`${WebsitePaths.Portal}/wallet`} + render={this._renderWallet.bind(this)} + /> + )} <Route path={`${WebsitePaths.Portal}/weth`} render={this._renderEthWrapper.bind(this)} @@ -272,6 +288,28 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { isLedgerDialogOpen: !this.state.isLedgerDialogOpen, }); } + private _renderWallet() { + const allTokens = _.values(this.props.tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); + return ( + <div className="flex flex-center"> + <div className="mx-auto"> + <Wallet + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this._blockchain} + blockchainIsLoaded={this.props.blockchainIsLoaded} + blockchainErr={this.props.blockchainErr} + dispatcher={this.props.dispatcher} + tokenByAddress={this.props.tokenByAddress} + trackedTokens={trackedTokens} + userEtherBalanceInWei={this.props.userEtherBalanceInWei} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + /> + </div> + </div> + ); + } private _renderEthWrapper() { return ( <EthWrappers diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/portal_menu.tsx index a2f9340c8..9ab611556 100644 --- a/packages/website/ts/components/portal_menu.tsx +++ b/packages/website/ts/components/portal_menu.tsx @@ -1,7 +1,8 @@ import * as _ from 'lodash'; import * as React from 'react'; import { MenuItem } from 'ts/components/ui/menu_item'; -import { WebsitePaths } from 'ts/types'; +import { Environments, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; export interface PortalMenuProps { menuItemStyle: React.CSSProperties; @@ -57,6 +58,16 @@ export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState > {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')} </MenuItem> + {configs.ENVIRONMENT === Environments.DEVELOPMENT && ( + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/wallet`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Wallet', 'zmdi-balance-wallet')} + </MenuItem> + )} </div> ); } diff --git a/packages/website/ts/components/sidebar_header.tsx b/packages/website/ts/components/sidebar_header.tsx index 519b23d80..a0ea869fb 100644 --- a/packages/website/ts/components/sidebar_header.tsx +++ b/packages/website/ts/components/sidebar_header.tsx @@ -4,15 +4,9 @@ import * as React from 'react'; const SHOW_DURATION_MS = 4000; -const titleToIcon: { [title: string]: string } = { - '0x.js': 'zeroExJs.png', - '0x Connect': 'connect.png', - '0x Smart Contracts': 'contracts.png', - Wiki: 'wiki.png', -}; - interface SidebarHeaderProps { title: string; + iconUrl: string; } interface SidebarHeaderState {} @@ -32,7 +26,7 @@ export class SidebarHeader extends React.Component<SidebarHeaderProps, SidebarHe </div> <div className="flex"> <div> - <img src={`/images/doc_icons/${titleToIcon[this.props.title]}`} width="22" /> + <img src={this.props.iconUrl} width="22" /> </div> <div className="pl1" style={{ fontWeight: 600, fontSize: 20, lineHeight: 1.2 }}> {this.props.title} diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 186393c4f..b4a710ef4 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -37,6 +37,7 @@ import { ScreenWidths, Token, TokenByAddress, + TokenStateByAddress, TokenVisibility, } from 'ts/types'; import { configs } from 'ts/utils/configs'; @@ -61,14 +62,6 @@ const styles: Styles = { }, }; -interface TokenStateByAddress { - [address: string]: { - balance: BigNumber; - allowance: BigNumber; - isLoaded: boolean; - }; -} - interface TokenBalancesProps { blockchain: Blockchain; blockchainErr: BlockchainErrs; diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 29c68524c..4b2e82119 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -39,6 +39,7 @@ interface TopBarProps { style?: React.CSSProperties; isNightVersion?: boolean; onVersionSelected?: (semver: string) => void; + sidebarHeader?: React.ReactNode; } interface TopBarState { @@ -120,14 +121,36 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { primaryText={this.props.translate.get(Key.StandardRelayerApi, Deco.CapWords)} /> </a>, - <a - key="subMenuItem-github" - target="_blank" - className="text-decoration-none" - href={constants.URL_GITHUB_ORG} - > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" /> - </a>, + <Link key="subMenuItem-jsonSchema" to={WebsitePaths.JSONSchemas} className="text-decoration-none"> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.JsonSchemas, Deco.CapWords)} + /> + </Link>, + <Link key="subMenuItem-subproviders" to={WebsitePaths.Subproviders} className="text-decoration-none"> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.Subproviders, Deco.CapWords)} + /> + </Link>, + <Link key="subMenuItem-web3Wrapper" to={WebsitePaths.Web3Wrapper} className="text-decoration-none"> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.Web3Wrapper, Deco.CapWords)} + /> + </Link>, + <Link key="subMenuItem-deployer" to={WebsitePaths.Deployer} className="text-decoration-none"> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.Deployer, Deco.CapWords)} + /> + </Link>, + <Link key="subMenuItem-sol-cov" to={WebsitePaths.SolCov} className="text-decoration-none"> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.SolCov, Deco.CapWords)} + /> + </Link>, <a key="subMenuItem-whitePaper" target="_blank" @@ -139,6 +162,14 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { primaryText={this.props.translate.get(Key.Whitepaper, Deco.CapWords)} /> </a>, + <a + key="subMenuItem-github" + target="_blank" + className="text-decoration-none" + href={constants.URL_GITHUB_ORG} + > + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" /> + </a>, ]; const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; const fullWidthClasses = isFullWidthPage ? 'pr4' : ''; @@ -277,6 +308,46 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </MenuItem> </Link> )} + {!this._isViewingWeb3WrapperDocs() && ( + <Link to={WebsitePaths.Web3Wrapper} className="text-decoration-none"> + <MenuItem className="py2"> + {this.props.translate.get(Key.Web3Wrapper, Deco.Cap)}{' '} + {this.props.translate.get(Key.Docs, Deco.Cap)} + </MenuItem> + </Link> + )} + {!this._isViewingDeployerDocs() && ( + <Link to={WebsitePaths.Deployer} className="text-decoration-none"> + <MenuItem className="py2"> + {this.props.translate.get(Key.Deployer, Deco.Cap)}{' '} + {this.props.translate.get(Key.Docs, Deco.Cap)} + </MenuItem> + </Link> + )} + {!this._isViewingJsonSchemasDocs() && ( + <Link to={WebsitePaths.JSONSchemas} className="text-decoration-none"> + <MenuItem className="py2"> + {this.props.translate.get(Key.JsonSchemas, Deco.Cap)}{' '} + {this.props.translate.get(Key.Docs, Deco.Cap)} + </MenuItem> + </Link> + )} + {!this._isViewingSolCovDocs() && ( + <Link to={WebsitePaths.SolCov} className="text-decoration-none"> + <MenuItem className="py2"> + {this.props.translate.get(Key.SolCov, Deco.Cap)}{' '} + {this.props.translate.get(Key.Docs, Deco.Cap)} + </MenuItem> + </Link> + )} + {!this._isViewingSubprovidersDocs() && ( + <Link to={WebsitePaths.Subproviders} className="text-decoration-none"> + <MenuItem className="py2"> + {this.props.translate.get(Key.Subproviders, Deco.Cap)}{' '} + {this.props.translate.get(Key.Docs, Deco.Cap)} + </MenuItem> + </Link> + )} {!this._isViewingPortal() && ( <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none"> <MenuItem className="py2"> @@ -304,7 +375,14 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { } private _renderDocsMenu(): React.ReactNode { if ( - (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingConnectDocs()) || + (!this._isViewing0xjsDocs() && + !this._isViewingSmartContractsDocs() && + !this._isViewingWeb3WrapperDocs() && + !this._isViewingDeployerDocs() && + !this._isViewingJsonSchemasDocs() && + !this._isViewingSolCovDocs() && + !this._isViewingSubprovidersDocs() && + !this._isViewingConnectDocs()) || _.isUndefined(this.props.menu) ) { return undefined; @@ -316,7 +394,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { <NestedSidebarMenu topLevelMenu={this.props.menu} menuSubsectionsBySection={this.props.menuSubsectionsBySection} - sidebarHeader={<SidebarHeader title={this.props.docsInfo.displayName} />} + sidebarHeader={this.props.sidebarHeader} shouldDisplaySectionHeaders={false} onMenuItemClick={this._onMenuButtonClick.bind(this)} selectedVersion={this.props.docsVersion} @@ -336,7 +414,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { <NestedSidebarMenu topLevelMenu={this.props.menuSubsectionsBySection} menuSubsectionsBySection={this.props.menuSubsectionsBySection} - sidebarHeader={<SidebarHeader title="Wiki" />} + sidebarHeader={this.props.sidebarHeader} shouldDisplaySectionHeaders={false} onMenuItemClick={this._onMenuButtonClick.bind(this)} /> @@ -392,6 +470,21 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { private _isViewingSmartContractsDocs() { return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); } + private _isViewingWeb3WrapperDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.Web3Wrapper); + } + private _isViewingDeployerDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.Deployer); + } + private _isViewingJsonSchemasDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.JSONSchemas); + } + private _isViewingSolCovDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.SolCov); + } + private _isViewingSubprovidersDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.Subproviders); + } private _isViewingWiki() { return _.includes(this.props.location.pathname, WebsitePaths.Wiki); } @@ -401,7 +494,12 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { this._isViewing0xjsDocs() || this._isViewingFAQ() || this._isViewingSmartContractsDocs() || + this._isViewingWeb3WrapperDocs() || + this._isViewingDeployerDocs() || + this._isViewingJsonSchemasDocs() || + this._isViewingSolCovDocs() || + this._isViewingSubprovidersDocs() || this._isViewingConnectDocs() ); } -} +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/wallet.tsx b/packages/website/ts/components/wallet.tsx new file mode 100644 index 000000000..8c6ef9cad --- /dev/null +++ b/packages/website/ts/components/wallet.tsx @@ -0,0 +1,373 @@ +import { ZeroEx } from '0x.js'; +import { + colors, + constants as sharedConstants, + EtherscanLinkSuffixes, + Styles, + utils as sharedUtils, +} from '@0xproject/react-shared'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; +import FlatButton from 'material-ui/FlatButton'; +import { List, ListItem } from 'material-ui/List'; +import NavigationArrowDownward from 'material-ui/svg-icons/navigation/arrow-downward'; +import NavigationArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward'; +import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); +import firstBy = require('thenby'); + +import { Blockchain } from 'ts/blockchain'; +import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; +import { Identicon } from 'ts/components/ui/identicon'; +import { TokenIcon } from 'ts/components/ui/token_icon'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { BalanceErrs, BlockchainErrs, Token, TokenByAddress, TokenState, TokenStateByAddress } from 'ts/types'; +import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; + +export interface WalletProps { + userAddress?: string; + networkId?: number; + blockchain: Blockchain; + blockchainIsLoaded: boolean; + blockchainErr: BlockchainErrs; + dispatcher: Dispatcher; + tokenByAddress: TokenByAddress; + trackedTokens: Token[]; + userEtherBalanceInWei: BigNumber; + lastForceTokenStateRefetch: number; +} + +interface WalletState { + trackedTokenStateByAddress: TokenStateByAddress; +} + +enum WrappedEtherAction { + Wrap, + Unwrap, +} + +interface AllowanceToggleConfig { + token: Token; + tokenState: TokenState; +} + +interface AccessoryItemConfig { + wrappedEtherAction?: WrappedEtherAction; + allowanceToggleConfig?: AllowanceToggleConfig; +} + +const styles: Styles = { + wallet: { + width: 346, + backgroundColor: colors.white, + borderBottomRightRadius: 10, + borderBottomLeftRadius: 10, + borderTopRightRadius: 10, + borderTopLeftRadius: 10, + boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`, + overflow: 'hidden', + }, + list: { + padding: 0, + }, + tokenItemInnerDiv: { + paddingLeft: 60, + }, + headerItemInnerDiv: { + paddingLeft: 65, + }, + footerItemInnerDiv: { + paddingLeft: 24, + }, + borderedItem: { + borderBottomColor: colors.walletBorder, + borderBottomStyle: 'solid', + borderWidth: 1, + }, + tokenItem: { + backgroundColor: colors.walletDefaultItemBackground, + paddingTop: 8, + paddingBottom: 8, + }, + headerItem: { + paddingTop: 8, + paddingBottom: 8, + }, + wrappedEtherButtonLabel: { + fontSize: 12, + }, + amountLabel: { + fontWeight: 'bold', + color: colors.black, + }, +}; + +const ETHER_ICON_PATH = '/images/ether.png'; +const ETHER_TOKEN_SYMBOL = 'WETH'; +const ZRX_TOKEN_SYMBOL = 'ZRX'; +const ETHER_SYMBOL = 'ETH'; +const ICON_DIMENSION = 24; +const TOKEN_AMOUNT_DISPLAY_PRECISION = 3; + +export class Wallet extends React.Component<WalletProps, WalletState> { + private _isUnmounted: boolean; + constructor(props: WalletProps) { + super(props); + this._isUnmounted = false; + const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens); + this.state = { + trackedTokenStateByAddress: initialTrackedTokenStateByAddress, + }; + } + public componentWillMount() { + const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); + // tslint:disable-next-line:no-floating-promises + this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); + } + public componentWillUnmount() { + this._isUnmounted = true; + } + public componentWillReceiveProps(nextProps: WalletProps) { + if ( + nextProps.userAddress !== this.props.userAddress || + nextProps.networkId !== this.props.networkId || + nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch + ) { + const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); + // tslint:disable-next-line:no-floating-promises + this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); + } + if (!_.isEqual(nextProps.trackedTokens, this.props.trackedTokens)) { + const newTokens = _.difference(nextProps.trackedTokens, this.props.trackedTokens); + const newTokenAddresses = _.map(newTokens, token => token.address); + // Add placeholder entry for this token to the state, since fetching the + // balance/allowance is asynchronous + const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + _.each(newTokenAddresses, (tokenAddress: string) => { + trackedTokenStateByAddress[tokenAddress] = { + balance: new BigNumber(0), + allowance: new BigNumber(0), + isLoaded: false, + }; + }); + this.setState({ + trackedTokenStateByAddress, + }); + // Fetch the actual balance/allowance. + // tslint:disable-next-line:no-floating-promises + this._fetchBalancesAndAllowancesAsync(newTokenAddresses); + } + } + public render() { + const isReadyToRender = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError; + return <div style={styles.wallet}>{isReadyToRender && this._renderRows()}</div>; + } + private _renderRows() { + return ( + <List style={styles.list}> + {_.concat( + this._renderHeaderRows(), + this._renderEthRows(), + this._renderTokenRows(), + this._renderFooterRows(), + )} + </List> + ); + } + private _renderHeaderRows() { + const userAddress = this.props.userAddress; + const primaryText = utils.getAddressBeginAndEnd(userAddress); + return ( + <ListItem + primaryText={primaryText} + leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />} + style={{ ...styles.headerItem, ...styles.borderedItem }} + innerDivStyle={styles.headerItemInnerDiv} + /> + ); + } + private _renderFooterRows() { + const primaryText = '+ other tokens'; + return ( + <ListItem primaryText={primaryText} style={styles.borderedItem} innerDivStyle={styles.footerItemInnerDiv} /> + ); + } + private _renderEthRows() { + const primaryText = this._renderAmount( + this.props.userEtherBalanceInWei, + constants.DECIMAL_PLACES_ETH, + ETHER_SYMBOL, + ); + const accessoryItemConfig = { + wrappedEtherAction: WrappedEtherAction.Wrap, + }; + return ( + <ListItem + primaryText={primaryText} + leftIcon={<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />} + rightAvatar={this._renderAccessoryItems(accessoryItemConfig)} + style={{ ...styles.tokenItem, ...styles.borderedItem }} + innerDivStyle={styles.tokenItemInnerDiv} + /> + ); + } + private _renderTokenRows() { + const trackedTokens = this.props.trackedTokens; + const trackedTokensStartingWithEtherToken = trackedTokens.sort( + firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL) + .thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL) + .thenBy('address'), + ); + return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this)); + } + private _renderTokenRow(token: Token) { + const tokenState = this.state.trackedTokenStateByAddress[token.address]; + const tokenLink = sharedUtils.getEtherScanLinkIfExists( + token.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const amount = this._renderAmount(tokenState.balance, token.decimals, token.symbol); + const wrappedEtherAction = token.symbol === ETHER_TOKEN_SYMBOL ? WrappedEtherAction.Unwrap : undefined; + const accessoryItemConfig: AccessoryItemConfig = { + wrappedEtherAction, + allowanceToggleConfig: { + token, + tokenState, + }, + }; + return ( + <ListItem + primaryText={amount} + leftIcon={this._renderTokenIcon(token, tokenLink)} + rightAvatar={this._renderAccessoryItems(accessoryItemConfig)} + style={{ ...styles.tokenItem, ...styles.borderedItem }} + innerDivStyle={styles.tokenItemInnerDiv} + /> + ); + } + private _renderAccessoryItems(config: AccessoryItemConfig) { + const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherAction); + const shouldShowToggle = !_.isUndefined(config.allowanceToggleConfig); + return ( + <div style={{ width: 160 }}> + <div className="flex"> + <div className="flex-auto"> + {shouldShowWrappedEtherAction && this._renderWrappedEtherButton(config.wrappedEtherAction)} + </div> + <div className="flex-last py1"> + {shouldShowToggle && this._renderAllowanceToggle(config.allowanceToggleConfig)} + </div> + </div> + </div> + ); + } + private _renderAllowanceToggle(config: AllowanceToggleConfig) { + return ( + <AllowanceToggle + networkId={this.props.networkId} + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + token={config.token} + tokenState={config.tokenState} + onErrorOccurred={_.noop} // TODO: Error handling + userAddress={this.props.userAddress} + isDisabled={!config.tokenState.isLoaded} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, config.token.address)} + /> + ); + } + private _renderAmount(amount: BigNumber, decimals: number, symbol: string) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + const formattedAmount = unitAmount.toPrecision(TOKEN_AMOUNT_DISPLAY_PRECISION); + const result = `${formattedAmount} ${symbol}`; + return <div style={styles.amountLabel}>{result}</div>; + } + private _renderTokenIcon(token: Token, tokenLink?: string) { + const tooltipId = `tooltip-${token.address}`; + const tokenIcon = <TokenIcon token={token} diameter={ICON_DIMENSION} />; + if (_.isUndefined(tokenLink)) { + return tokenIcon; + } else { + return ( + <a href={tokenLink} target="_blank" style={{ textDecoration: 'none' }}> + {tokenIcon} + </a> + ); + } + } + private _renderWrappedEtherButton(action: WrappedEtherAction) { + let buttonLabel; + let buttonIcon; + switch (action) { + case WrappedEtherAction.Wrap: + buttonLabel = 'wrap'; + buttonIcon = <NavigationArrowDownward />; + break; + case WrappedEtherAction.Unwrap: + buttonLabel = 'unwrap'; + buttonIcon = <NavigationArrowUpward />; + break; + default: + throw utils.spawnSwitchErr('wrappedEtherAction', action); + } + return ( + <FlatButton + label={buttonLabel} + labelPosition="after" + primary={true} + icon={buttonIcon} + labelStyle={styles.wrappedEtherButtonLabel} + /> + ); + } + private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]) { + const trackedTokenStateByAddress: TokenStateByAddress = {}; + _.each(trackedTokens, token => { + trackedTokenStateByAddress[token.address] = { + balance: new BigNumber(0), + allowance: new BigNumber(0), + isLoaded: false, + }; + }); + return trackedTokenStateByAddress; + } + private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) { + const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; + for (const tokenAddress of tokenAddresses) { + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + userAddressIfExists, + tokenAddress, + ); + trackedTokenStateByAddress[tokenAddress] = { + balance, + allowance, + isLoaded: true, + }; + } + if (!this._isUnmounted) { + this.setState({ + trackedTokenStateByAddress, + }); + } + } + private async _refetchTokenStateAsync(tokenAddress: string) { + const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + userAddressIfExists, + tokenAddress, + ); + this.setState({ + trackedTokenStateByAddress: { + ...this.state.trackedTokenStateByAddress, + [tokenAddress]: { + balance, + allowance, + isLoaded: true, + }, + }, + }); + } +} diff --git a/packages/website/ts/containers/deployer_documentation.ts b/packages/website/ts/containers/deployer_documentation.ts new file mode 100644 index 000000000..4e1c41f8c --- /dev/null +++ b/packages/website/ts/containers/deployer_documentation.ts @@ -0,0 +1,102 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { DocPackages, Environments, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/deployer/introduction'); +const InstallationMarkdown = require('md/docs/deployer/installation'); +const UsageMarkdown = require('md/docs/deployer/usage'); +/* tslint:enable:no-var-requires */ + +const docSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', + compiler: 'compiler', + deployer: 'deployer', + types: docConstants.TYPES_SECTION_NAME, +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.Deployer, + type: SupportedDocJson.TypeDoc, + displayName: 'Deployer', + packageUrl: 'https://github.com/0xProject/0x-monorepo', + menu: { + introduction: [docSections.introduction], + install: [docSections.installation], + usage: [docSections.usage], + compiler: [docSections.compiler], + deployer: [docSections.deployer], + types: [docSections.types], + }, + sectionNameToMarkdown: { + [docSections.introduction]: IntroMarkdown, + [docSections.installation]: InstallationMarkdown, + [docSections.usage]: UsageMarkdown, + }, + sectionNameToModulePath: { + [docSections.compiler]: ['"deployer/src/compiler"'], + [docSections.deployer]: ['"deployer/src/deployer"'], + [docSections.types]: ['"deployer/src/utils/types"', '"types/src/index"'], + }, + menuSubsectionToVersionWhenIntroduced: {}, + sections: docSections, + visibleConstructors: [docSections.compiler, docSections.deployer], + typeConfigs: { + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: [ + 'CompilerOptions', + 'DeployerOptions', + 'BaseDeployerOptions', + 'UrlDeployerOptions', + 'ProviderDeployerOptions', + 'TxData', + ], + typeNameToExternalLink: { + Web3: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', + BigNumber: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', + ContractInstance: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L98', + }, + typeNameToPrefix: { + ContractInstance: 'Web3', + }, + }, +}; +const docsInfo = new DocsInfo(docsInfoConfig); + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/json_schemas_documentation.ts b/packages/website/ts/containers/json_schemas_documentation.ts new file mode 100644 index 000000000..154c65ffc --- /dev/null +++ b/packages/website/ts/containers/json_schemas_documentation.ts @@ -0,0 +1,89 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { DocPackages, Environments, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/json_schemas/introduction'); +const InstallationMarkdown = require('md/docs/json_schemas/installation'); +const UsageMarkdown = require('md/docs/json_schemas/usage'); +const SchemasMarkdown = require('md/docs/json_schemas/schemas'); +/* tslint:enable:no-var-requires */ + +const docSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', + schemaValidator: 'schemaValidator', + schemas: 'schemas', +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.JSONSchemas, + type: SupportedDocJson.TypeDoc, + displayName: 'JSON Schemas', + packageUrl: 'https://github.com/0xProject/0x-monorepo', + menu: { + introduction: [docSections.introduction], + install: [docSections.installation], + usage: [docSections.usage], + schemaValidator: [docSections.schemaValidator], + schemas: [docSections.schemas], + }, + sectionNameToMarkdown: { + [docSections.introduction]: IntroMarkdown, + [docSections.installation]: InstallationMarkdown, + [docSections.schemas]: SchemasMarkdown, + [docSections.usage]: UsageMarkdown, + }, + sectionNameToModulePath: { + [docSections.schemaValidator]: ['"json-schemas/src/schema_validator"'], + }, + menuSubsectionToVersionWhenIntroduced: {}, + sections: docSections, + visibleConstructors: [docSections.schemaValidator], + typeConfigs: { + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: [], + typeNameToExternalLink: { + Schema: + 'https://github.com/tdegrunt/jsonschema/blob/5c2edd4baba149964aec0f23c87ad12c25a50dfb/lib/index.d.ts#L49', + }, + }, +}; +const docsInfo = new DocsInfo(docsInfoConfig); + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/sol_cov_documentation.ts b/packages/website/ts/containers/sol_cov_documentation.ts new file mode 100644 index 000000000..2b901ec6f --- /dev/null +++ b/packages/website/ts/containers/sol_cov_documentation.ts @@ -0,0 +1,89 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { DocPackages, Environments, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/sol_cov/introduction'); +const InstallationMarkdown = require('md/docs/sol_cov/installation'); +const UsageMarkdown = require('md/docs/sol_cov/usage'); +/* tslint:enable:no-var-requires */ + +const docSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', + coverageSubprovider: 'coverageSubprovider', + types: docConstants.TYPES_SECTION_NAME, +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.SolCov, + type: SupportedDocJson.TypeDoc, + displayName: 'Sol-cov', + packageUrl: 'https://github.com/0xProject/0x-monorepo', + menu: { + introduction: [docSections.introduction], + install: [docSections.installation], + usage: [docSections.usage], + coverageSubprovider: [docSections.coverageSubprovider], + types: [docSections.types], + }, + sectionNameToMarkdown: { + [docSections.introduction]: IntroMarkdown, + [docSections.installation]: InstallationMarkdown, + [docSections.usage]: UsageMarkdown, + }, + sectionNameToModulePath: { + [docSections.coverageSubprovider]: ['"sol-cov/src/coverage_subprovider"'], + [docSections.types]: ['"subproviders/src/types"'], + }, + menuSubsectionToVersionWhenIntroduced: {}, + sections: docSections, + visibleConstructors: [docSections.coverageSubprovider], + typeConfigs: { + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: ['NextCallback', 'OnNextCompleted', 'ErrorCallback'], + typeNameToExternalLink: {}, + typeNameToPrefix: { + JSONRPCRequestPayload: 'Web3', + }, + typeNameToDocSection: {}, + }, +}; +const docsInfo = new DocsInfo(docsInfoConfig); + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/subproviders_documentation.ts b/packages/website/ts/containers/subproviders_documentation.ts new file mode 100644 index 000000000..7aa05f9a5 --- /dev/null +++ b/packages/website/ts/containers/subproviders_documentation.ts @@ -0,0 +1,141 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { DocPackages, Environments, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/subproviders/introduction'); +const InstallationMarkdown = require('md/docs/subproviders/installation'); +const LedgerNodeHidMarkdown = require('md/docs/subproviders/ledger_node_hid'); +/* tslint:enable:no-var-requires */ + +const docSections = { + introduction: 'introduction', + installation: 'installation', + subprovider: 'subprovider', + ledgerSubprovider: 'ledgerSubprovider', + ledgerNodeHid: 'ledger-node-hid-issue', + factoryMethods: 'factory-methods', + emptyWalletSubprovider: 'emptyWalletSubprovider', + fakeGasEstimateSubprovider: 'fakeGasEstimateSubprovider', + injectedWeb3Subprovider: 'injectedWeb3Subprovider', + redundantRPCSubprovider: 'redundantRPCSubprovider', + ganacheSubprovider: 'ganacheSubprovider', + nonceTrackerSubprovider: 'nonceTrackerSubprovider', + types: docConstants.TYPES_SECTION_NAME, +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.Subproviders, + type: SupportedDocJson.TypeDoc, + displayName: 'Subproviders', + packageUrl: 'https://github.com/0xProject/0x-monorepo', + menu: { + introduction: [docSections.introduction], + install: [docSections.installation], + subprovider: [docSections.subprovider], + ['ledger-subprovider']: [docSections.ledgerSubprovider], + ['ledger-node-hid-issue']: [docSections.ledgerNodeHid], + ['factory-methods']: [docSections.factoryMethods], + ['emptyWallet-subprovider']: [docSections.emptyWalletSubprovider], + ['fakeGasEstimate-subprovider']: [docSections.fakeGasEstimateSubprovider], + ['injectedWeb3-subprovider']: [docSections.injectedWeb3Subprovider], + ['redundantRPC-subprovider']: [docSections.redundantRPCSubprovider], + ['ganache-subprovider']: [docSections.ganacheSubprovider], + ['nonceTracker-subprovider']: [docSections.nonceTrackerSubprovider], + types: [docSections.types], + }, + sectionNameToMarkdown: { + [docSections.introduction]: IntroMarkdown, + [docSections.installation]: InstallationMarkdown, + [docSections.ledgerNodeHid]: LedgerNodeHidMarkdown, + }, + sectionNameToModulePath: { + [docSections.subprovider]: ['"subproviders/src/subproviders/subprovider"'], + [docSections.ledgerSubprovider]: ['"subproviders/src/subproviders/ledger"'], + [docSections.factoryMethods]: ['"subproviders/src/index"'], + [docSections.emptyWalletSubprovider]: ['"subproviders/src/subproviders/empty_wallet_subprovider"'], + [docSections.fakeGasEstimateSubprovider]: ['"subproviders/src/subproviders/fake_gas_estimate_subprovider"'], + [docSections.injectedWeb3Subprovider]: ['"subproviders/src/subproviders/injected_web3"'], + [docSections.redundantRPCSubprovider]: ['"subproviders/src/subproviders/redundant_rpc"'], + [docSections.ganacheSubprovider]: ['"subproviders/src/subproviders/ganache"'], + [docSections.nonceTrackerSubprovider]: ['"subproviders/src/subproviders/nonce_tracker"'], + [docSections.types]: ['"deployer/src/utils/types"', '"types/src/index"', '"subproviders/src/types"'], + }, + menuSubsectionToVersionWhenIntroduced: {}, + sections: docSections, + visibleConstructors: [ + docSections.subprovider, + docSections.ledgerSubprovider, + docSections.emptyWalletSubprovider, + docSections.fakeGasEstimateSubprovider, + docSections.injectedWeb3Subprovider, + docSections.redundantRPCSubprovider, + docSections.ganacheSubprovider, + docSections.nonceTrackerSubprovider, + ], + typeConfigs: { + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: [ + 'Callback', + 'NextCallback', + 'ErrorCallback', + 'ECSignature', + 'JSONRPCRequestPayloadWithMethod', + 'JSONRPCResponsePayload', + 'AccountFetchingConfigs', + 'LedgerEthereumClientFactoryAsync', + 'PartialTxParams', + 'LedgerEthereumClient', + 'LedgerSubproviderConfigs', + ], + typeNameToExternalLink: { + Web3: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', + BigNumber: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', + JSONRPCRequestPayload: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L137', + JSONRPCResponsePayload: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L144', + Provider: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', + }, + typeNameToPrefix: { + JSONRPCRequestPayload: 'Web3', + JSONRPCResponsePayload: 'Web3', + Provider: 'Web3', + }, + }, +}; +const docsInfo = new DocsInfo(docsInfoConfig); + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/web3_wrapper_documentation.ts b/packages/website/ts/containers/web3_wrapper_documentation.ts new file mode 100644 index 000000000..0a0911b80 --- /dev/null +++ b/packages/website/ts/containers/web3_wrapper_documentation.ts @@ -0,0 +1,103 @@ +import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0xproject/react-docs'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { DocPackages, Environments, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/web3_wrapper/introduction'); +const InstallationMarkdown = require('md/docs/web3_wrapper/installation'); +/* tslint:enable:no-var-requires */ + +const docSections = { + introduction: 'introduction', + installation: 'installation', + web3Wrapper: 'web3Wrapper', + types: docConstants.TYPES_SECTION_NAME, +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.Web3Wrapper, + type: SupportedDocJson.TypeDoc, + displayName: 'Web3Wrapper', + packageUrl: 'https://github.com/0xProject/0x-monorepo', + menu: { + introduction: [docSections.introduction], + install: [docSections.installation], + web3Wrapper: [docSections.web3Wrapper], + types: [docSections.types], + }, + sectionNameToMarkdown: { + [docSections.introduction]: IntroMarkdown, + [docSections.installation]: InstallationMarkdown, + }, + sectionNameToModulePath: { + [docSections.web3Wrapper]: ['"web3-wrapper/src/index"'], + [docSections.types]: ['"types/src/index"'], + }, + menuSubsectionToVersionWhenIntroduced: {}, + sections: docSections, + visibleConstructors: [docSections.web3Wrapper], + typeConfigs: { + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: ['TxData', 'TransactionReceipt', 'RawLogEntry'], + typeNameToExternalLink: { + Web3: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', + Provider: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', + BigNumber: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', + LogEntryEvent: 'http://mikemcl.github.io/bignumber.js', + CallData: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L348', + BlockWithoutTransactionData: + 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L314', + LogEntry: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L366', + FilterObject: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L109', + ['Web3.BlockParam']: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L278', + ['Web3.ContractAbi']: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L47', + }, + typeNameToPrefix: { + Provider: 'Web3', + CallData: 'Web3', + BlockWithoutTransactionData: 'Web3', + LogEntry: 'Web3', + FilterObject: 'Web3', + }, + typeNameToDocSection: { + Web3Wrapper: docSections.web3Wrapper, + }, + }, +}; +const docsInfo = new DocsInfo(docsInfoConfig); + +interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Documentation: React.ComponentClass<DocPageProps> = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/zero_ex_js_documentation.ts b/packages/website/ts/containers/zero_ex_js_documentation.ts index ec30780c3..6e893133f 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.ts +++ b/packages/website/ts/containers/zero_ex_js_documentation.ts @@ -90,7 +90,14 @@ const docsInfoConfig: DocsInfoConfig = { '"0x.js/src/order_watcher/order_state_watcher"', '"src/order_watcher/order_state_watcher"', ], - [zeroExJsDocSections.types]: ['"0x.js/src/types"', '"src/types"', '"types/src/index"'], + [zeroExJsDocSections.types]: [ + '"0x.js/src/types"', + '"src/types"', + '"types/src/index"', + '"0x.js/src/contract_wrappers/generated/ether_token"', + '"0x.js/src/contract_wrappers/generated/token"', + '"0x.js/src/contract_wrappers/generated/exchange"', + ], }, menuSubsectionToVersionWhenIntroduced: { [zeroExJsDocSections.etherToken]: '0.7.1', @@ -151,7 +158,6 @@ const docsInfoConfig: DocsInfoConfig = { 'TransactionOpts', 'ContractEventArg', 'LogEvent', - 'LogEntry', 'DecodedLogEvent', 'EventWatcherCallback', 'OnOrderStateChangeCallback', @@ -167,6 +173,7 @@ const docsInfoConfig: DocsInfoConfig = { DecodedLogEntryEvent: 'Web3', LogEntryEvent: 'Web3', CallData: 'Web3', + LogEntry: 'Web3', }, typeNameToExternalLink: { Web3: constants.URL_WEB3_DOCS, @@ -174,6 +181,7 @@ const docsInfoConfig: DocsInfoConfig = { BigNumber: constants.URL_BIGNUMBERJS_GITHUB, DecodedLogEntryEvent: constants.URL_WEB3_DECODED_LOG_ENTRY_EVENT, LogEntryEvent: constants.URL_WEB3_LOG_ENTRY_EVENT, + LogEntry: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L366', }, typeNameToDocSection: { ExchangeWrapper: 'exchange', diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index b24517e39..d542bd804 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -45,6 +45,21 @@ const LazySmartContractsDocumentation = createLazyComponent('Documentation', asy const LazyConnectDocumentation = createLazyComponent('Documentation', async () => System.import<any>(/* webpackChunkName: "connectDocs" */ 'ts/containers/connect_documentation'), ); +const LazyWeb3WrapperDocumentation = createLazyComponent('Documentation', async () => + System.import<any>(/* webpackChunkName: "web3WrapperDocs" */ 'ts/containers/web3_wrapper_documentation'), +); +const LazyDeployerDocumentation = createLazyComponent('Documentation', async () => + System.import<any>(/* webpackChunkName: "deployerDocs" */ 'ts/containers/deployer_documentation'), +); +const LazyJSONSchemasDocumentation = createLazyComponent('Documentation', async () => + System.import<any>(/* webpackChunkName: "jsonSchemasDocs" */ 'ts/containers/json_schemas_documentation'), +); +const LazySolCovDocumentation = createLazyComponent('Documentation', async () => + System.import<any>(/* webpackChunkName: "solCovDocs" */ 'ts/containers/sol_cov_documentation'), +); +const LazySubprovidersDocumentation = createLazyComponent('Documentation', async () => + System.import<any>(/* webpackChunkName: "subproviderDocs" */ 'ts/containers/subproviders_documentation'), +); analytics.init(); // tslint:disable-next-line:no-floating-promises @@ -65,6 +80,20 @@ render( <Route path={`${WebsitePaths.Wiki}`} component={Wiki as any} /> <Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} /> <Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} /> + <Route path={`${WebsitePaths.Deployer}/:version?`} component={LazyDeployerDocumentation} /> + <Route path={`${WebsitePaths.SolCov}/:version?`} component={LazySolCovDocumentation} /> + <Route + path={`${WebsitePaths.JSONSchemas}/:version?`} + component={LazyJSONSchemasDocumentation} + /> + <Route + path={`${WebsitePaths.Subproviders}/:version?`} + component={LazySubprovidersDocumentation} + /> + <Route + path={`${WebsitePaths.Web3Wrapper}/:version?`} + component={LazyWeb3WrapperDocumentation} + /> <Route path={`${WebsitePaths.SmartContracts}/:version?`} component={LazySmartContractsDocumentation} diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx index 1281219c6..8ec1a023d 100644 --- a/packages/website/ts/pages/documentation/doc_page.tsx +++ b/packages/website/ts/pages/documentation/doc_page.tsx @@ -15,19 +15,25 @@ import { docUtils } from 'ts/utils/doc_utils'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; +const isDevelopment = configs.ENVIRONMENT === Environments.DEVELOPMENT; +const DEFAULT_ICON = 'docs.png'; const ZERO_EX_JS_VERSION_MISSING_TOPLEVEL_PATH = '0.32.4'; -const isDevelopment = configs.ENVIRONMENT === Environments.DEVELOPMENT; -const docIdToS3BucketName: { [id: string]: string } = { - [DocPackages.ZeroExJs]: isDevelopment ? 'staging-0xjs-docs-jsons' : '0xjs-docs-jsons', - [DocPackages.SmartContracts]: 'smart-contracts-docs-json', - [DocPackages.Connect]: isDevelopment ? 'staging-connect-docs-jsons' : 'connect-docs-jsons', +const idToIcon: { [id: string]: string } = { + [DocPackages.ZeroExJs]: 'zeroExJs.png', + [DocPackages.Connect]: 'connect.png', + [DocPackages.SmartContracts]: 'contracts.png', }; const docIdToSubpackageName: { [id: string]: string } = { [DocPackages.ZeroExJs]: '0x.js', [DocPackages.Connect]: 'connect', [DocPackages.SmartContracts]: 'contracts', + [DocPackages.Web3Wrapper]: 'web3-wrapper', + [DocPackages.Deployer]: 'deployer', + [DocPackages.JSONSchemas]: 'json-schemas', + [DocPackages.SolCov]: 'sol-cov', + [DocPackages.Subproviders]: 'subproviders', }; export interface DocPageProps { @@ -63,12 +69,13 @@ export class DocPage extends React.Component<DocPageProps, DocPageState> { public componentWillUnmount() { this._isUnmounted = true; } - public render() { const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) ? {} : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); const sourceUrl = this._getSourceUrl(); + const iconFileName = idToIcon[this.props.docsInfo.id] || DEFAULT_ICON; + const iconUrl = `/images/doc_icons/${iconFileName}`; return ( <div> <DocumentTitle title={`${this.props.docsInfo.displayName} Documentation`} /> @@ -82,13 +89,14 @@ export class DocPage extends React.Component<DocPageProps, DocPageState> { docsInfo={this.props.docsInfo} translate={this.props.translate} onVersionSelected={this._onVersionSelected.bind(this)} + sidebarHeader={<SidebarHeader title={this.props.docsInfo.displayName} iconUrl={iconUrl} />} /> <Documentation selectedVersion={this.props.docsVersion} availableVersions={this.props.availableDocVersions} docsInfo={this.props.docsInfo} docAgnosticFormat={this.state.docAgnosticFormat} - sidebarHeader={<SidebarHeader title={this.props.docsInfo.displayName} />} + sidebarHeader={<SidebarHeader title={this.props.docsInfo.displayName} iconUrl={iconUrl} />} sourceUrl={sourceUrl} topBarHeight={60} onVersionSelected={this._onVersionSelected.bind(this)} @@ -97,25 +105,25 @@ export class DocPage extends React.Component<DocPageProps, DocPageState> { ); } private async _fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> { - const s3BucketName = docIdToS3BucketName[this.props.docsInfo.id]; - const docsJsonRoot = `${constants.S3_BUCKET_ROOT}/${s3BucketName}`; - const versionToFileName = await docUtils.getVersionToFileNameAsync(docsJsonRoot); - const versions = _.keys(versionToFileName); + const folderName = docIdToSubpackageName[this.props.docsInfo.id]; + const docBucketRoot = isDevelopment ? constants.S3_STAGING_DOC_BUCKET_ROOT : constants.S3_DOC_BUCKET_ROOT; + const versionToFilePath = await docUtils.getVersionToFilePathAsync(docBucketRoot, folderName); + const versions = _.keys(versionToFilePath); this.props.dispatcher.updateAvailableDocVersions(versions); const sortedVersions = semverSort.desc(versions); const latestVersion = sortedVersions[0]; let versionToFetch = latestVersion; if (!_.isUndefined(preferredVersionIfExists)) { - const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; + const preferredVersionFileNameIfExists = versionToFilePath[preferredVersionIfExists]; if (!_.isUndefined(preferredVersionFileNameIfExists)) { versionToFetch = preferredVersionIfExists; } } this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); - const versionFileNameToFetch = versionToFileName[versionToFetch]; - const versionDocObj = await docUtils.getJSONDocFileAsync(versionFileNameToFetch, docsJsonRoot); + const versionFilePathToFetch = versionToFilePath[versionToFetch]; + const versionDocObj = await docUtils.getJSONDocFileAsync(versionFilePathToFetch, docBucketRoot); const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj); if (!this._isUnmounted) { diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index 1be3c3565..701031d44 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -238,8 +238,8 @@ const sections: FAQSection[] = [ answer: <div>1,000,000,000 ZRX. Fixed supply.</div>, }, { - prompt: 'When is the Token Launch? will there be a pre-sale?', - answer: <div>The token launch will be on August 15th, 2017. There will not be a pre-sale.</div>, + prompt: 'When was the token launch? Was there a pre-sale?', + answer: <div>The token launch was on August 15th, 2017. There was no pre-sale.</div>, }, { prompt: 'What will the token launch proceeds be used for?', diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index 6699e4e6f..c943e3d79 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -40,8 +40,8 @@ const THROTTLE_TIMEOUT = 100; const relayersAndDappProjects: Project[] = [ { - logoFileName: 'ethfinex.png', - projectUrl: constants.PROJECT_URL_ETHFINEX, + logoFileName: 'ercdex.png', + projectUrl: constants.PROJECT_URL_ERC_DEX, }, { logoFileName: 'radar_relay.png', @@ -60,6 +60,10 @@ const relayersAndDappProjects: Project[] = [ projectUrl: constants.PROJECT_URL_DYDX, }, { + logoFileName: 'ethfinex.png', + projectUrl: constants.PROJECT_URL_ETHFINEX, + }, + { logoFileName: 'melonport.png', projectUrl: constants.PROJECT_URL_MELONPORT, }, @@ -99,10 +103,6 @@ const relayersAndDappProjects: Project[] = [ logoFileName: 'anx.png', projectUrl: constants.PROJECT_URL_OPEN_ANX, }, - { - logoFileName: 'auctus.png', - projectUrl: constants.PROJECT_URL_AUCTUS, - }, ]; const relayerProjects: Project[] = [ diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index e2dd3a68e..23d1b52fb 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -88,6 +88,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { ...styles.mainContainers, overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden', }; + const sidebarHeader = <SidebarHeader title="Wiki" iconUrl="/images/doc_icons/wiki.png" />; return ( <div> <DocumentTitle title="0x Protocol Wiki" /> @@ -96,6 +97,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { location={this.props.location} menuSubsectionsBySection={menuSubsectionsBySection} translate={this.props.translate} + sidebarHeader={sidebarHeader} /> {_.isUndefined(this.state.articlesBySection) ? ( <div className="col col-12" style={mainContainersStyle}> @@ -134,7 +136,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { <NestedSidebarMenu topLevelMenu={menuSubsectionsBySection} menuSubsectionsBySection={menuSubsectionsBySection} - sidebarHeader={<SidebarHeader title="Wiki" />} + sidebarHeader={sidebarHeader} /> </div> </div> diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index b3e4973fa..104d2e50f 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -21,11 +21,6 @@ export interface TokenByAddress { [address: string]: Token; } -export interface TokenState { - allowance: BigNumber; - balance: BigNumber; -} - export interface AssetToken { address?: string; amount?: BigNumber; @@ -336,7 +331,7 @@ export enum TokenVisibility { TRACKED = 'TRACKED', } -export interface VersionToFileName { +export interface VersionToFilePath { [version: string]: string; } @@ -355,45 +350,22 @@ export enum WebsitePaths { Whitepaper = '/pdfs/0x_white_paper.pdf', SmartContracts = '/docs/contracts', Connect = '/docs/connect', + Web3Wrapper = '/docs/web3_wrapper', + Deployer = '/docs/deployer', + JSONSchemas = '/docs/json-schemas', + SolCov = '/docs/sol-cov', + Subproviders = '/docs/subproviders', } export enum DocPackages { Connect = 'CONNECT', ZeroExJs = 'ZERO_EX_JS', SmartContracts = 'SMART_CONTRACTS', -} - -export interface TimestampMsRange { - startTimestampMs: number; - endTimestampMs: number; -} - -export interface OutdatedWrappedEtherByNetworkId { - [networkId: number]: { - address: string; - timestampMsRange: TimestampMsRange; - }; -} - -export enum SmartContractDocSections { - Introduction = 'Introduction', - Exchange = 'Exchange', - TokenTransferProxy = 'TokenTransferProxy', - TokenRegistry = 'TokenRegistry', - ZRXToken = 'ZRXToken', -} - -export interface MaterialUIPosition { - vertical: 'bottom' | 'top' | 'center'; - horizontal: 'left' | 'middle' | 'right'; -} - -export enum Language { - English = 'EN', - Spanish = 'ES', - Chinese = 'ZH', - Korean = 'KO', - Russian = 'RU', + Web3Wrapper = 'WEB3_WRAPPER', + Deployer = 'DEPLOYER', + JSONSchemas = 'JSON_SCHEMAS', + SolCov = 'SOL_COV', + Subproviders = 'SUBPROVIDERS', } export enum Key { @@ -442,11 +414,16 @@ export enum Key { About = 'ABOUT', Careers = 'CAREERS', Contact = 'CONTACT', + Deployer = 'DEPLOYER', + JsonSchemas = 'JSON_SCHEMAS', + SolCov = 'SOL_COV', + Subproviders = 'SUBPROVIDERS', Blog = 'BLOG', Forum = 'FORUM', Connect = 'CONNECT', Whitepaper = 'WHITEPAPER', Wiki = 'WIKI', + Web3Wrapper = 'WEB3_WRAPPER', And = 'AND', Faq = 'FAQ', SmartContracts = 'SMART_CONTRACTS', @@ -458,15 +435,58 @@ export enum Key { RocketChat = 'ROCKETCHAT', } +export enum SmartContractDocSections { + Introduction = 'Introduction', + Exchange = 'Exchange', + TokenTransferProxy = 'TokenTransferProxy', + TokenRegistry = 'TokenRegistry', + ZRXToken = 'ZRXToken', +} + +export enum Language { + English = 'EN', + Spanish = 'ES', + Chinese = 'ZH', + Korean = 'KO', + Russian = 'RU', +} + export enum Deco { Cap, CapWords, Upper, } +export interface MaterialUIPosition { + vertical: 'bottom' | 'top' | 'center'; + horizontal: 'left' | 'middle' | 'right'; +} + export enum Providers { Parity = 'PARITY', Metamask = 'METAMASK', Mist = 'MIST', } + +export interface TimestampMsRange { + startTimestampMs: number; + endTimestampMs: number; +} + +export interface OutdatedWrappedEtherByNetworkId { + [networkId: number]: { + address: string; + timestampMsRange: TimestampMsRange; + }; +} + +export interface TokenStateByAddress { + [address: string]: TokenState; +} + +export interface TokenState { + balance: BigNumber; + allowance: BigNumber; + isLoaded: boolean; +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index f63e8617e..0584938eb 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -29,12 +29,13 @@ export const constants = { PROVIDER_NAME_GENERIC: 'Injected Web3', PROVIDER_NAME_PUBLIC: '0x Public', ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', - S3_BUCKET_ROOT: 'https://s3.amazonaws.com', + S3_DOC_BUCKET_ROOT: 'https://s3.amazonaws.com/doc-jsons', + S3_STAGING_DOC_BUCKET_ROOT: 'https://s3.amazonaws.com/staging-doc-jsons', SUCCESS_STATUS: 200, UNAVAILABLE_STATUS: 503, TAKER_FEE: new BigNumber(0), TESTNET_NAME: 'Kovan', - PROJECT_URL_ETHFINEX: 'https://www.bitfinex.com/ethfinex', + PROJECT_URL_ETHFINEX: 'https://www.ethfinex.com/', PROJECT_URL_AMADEUS: 'http://amadeusrelay.org', PROJECT_URL_DDEX: 'https://ddex.io', PROJECT_URL_DECENT_EX: 'https://decent.exchange', diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts index 6b3f5f378..2a599bcbe 100644 --- a/packages/website/ts/utils/doc_utils.ts +++ b/packages/website/ts/utils/doc_utils.ts @@ -2,21 +2,21 @@ import { DoxityDocObj, TypeDocNode } from '@0xproject/react-docs'; import { logUtils } from '@0xproject/utils'; import findVersions = require('find-versions'); import * as _ from 'lodash'; -import { S3FileObject, VersionToFileName } from 'ts/types'; +import { S3FileObject, VersionToFilePath } from 'ts/types'; import { utils } from 'ts/utils/utils'; import convert = require('xml-js'); export const docUtils = { - async getVersionToFileNameAsync(s3DocJsonRoot: string): Promise<VersionToFileName> { - const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot); - const versionToFileName: VersionToFileName = {}; - _.each(versionFileNames, fileName => { - const [version] = findVersions(fileName); - versionToFileName[version] = fileName; + async getVersionToFilePathAsync(s3DocJsonRoot: string, folderName: string): Promise<VersionToFilePath> { + const versionFilePaths = await this.getVersionFileNamesAsync(s3DocJsonRoot, folderName); + const versionToFilePath: VersionToFilePath = {}; + _.each(versionFilePaths, filePath => { + const [version] = findVersions(filePath); + versionToFilePath[version] = filePath; }); - return versionToFileName; + return versionToFilePath; }, - async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise<string[]> { + async getVersionFileNamesAsync(s3DocJsonRoot: string, folderName: string): Promise<string[]> { const response = await fetch(s3DocJsonRoot); if (response.status !== 200) { // TODO: Show the user an error message when the docs fail to load @@ -33,13 +33,47 @@ export const docUtils = { ? (responseObj.ListBucketResult.Contents as S3FileObject[]) : [responseObj.ListBucketResult.Contents]; - const versionFileNames = _.map(fileObjs, fileObj => { + /* + * S3 simply pre-fixes files in "folders" with the folder name. Thus, since we + * store docJSONs for multiple packages in a single S3 bucket, we must filter out + * the versionFileNames for a given folder here (ignoring folder entries) + * + * Example S3 response: + * <ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> + * <Name>staging-doc-jsons</Name> + * <Prefix/> + * <Marker/> + * <MaxKeys>1000</MaxKeys> + * <IsTruncated>false</IsTruncated> + * <Contents> + * <Key>0xjs/</Key> + * <LastModified>2018-03-16T13:17:46.000Z</LastModified> + * <ETag>"d41d8cd98f00b204e9800998ecf8427e"</ETag> + * <Size>0</Size> + * <StorageClass>STANDARD</StorageClass> + * </Contents> + * <Contents> + * <Key>0xjs/v0.1.0.json</Key> + * <LastModified>2018-03-16T13:18:23.000Z</LastModified> + * <ETag>"b4f7f74913aab4a5ad1e6a58fcb3b274"</ETag> + * <Size>1039050</Size> + * <StorageClass>STANDARD</StorageClass> + * </Contents> + */ + const relevantObjs = _.filter(fileObjs, fileObj => { + const key = fileObj.Key._text; + const isInFolderOfInterest = _.includes(key, folderName); + const isFileEntry = !_.endsWith(key, '/'); + return isInFolderOfInterest && isFileEntry; + }); + + const versionFilePaths = _.map(relevantObjs, fileObj => { return fileObj.Key._text; }); - return versionFileNames; + return versionFilePaths; }, - async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> { - const endpoint = `${s3DocJsonRoot}/${fileName}`; + async getJSONDocFileAsync(filePath: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> { + const endpoint = `${s3DocJsonRoot}/${filePath}`; const response = await fetch(endpoint); if (response.status !== 200) { // TODO: Show the user an error message when the docs fail to load diff --git a/packages/website/webpack.config.js b/packages/website/webpack.config.js index c436888bd..e28e9e064 100644 --- a/packages/website/webpack.config.js +++ b/packages/website/webpack.config.js @@ -11,10 +11,7 @@ module.exports = { }, devtool: 'source-map', resolve: { - modules: [ - path.join(__dirname, '/ts'), - 'node_modules', - ], + modules: [path.join(__dirname, '/ts'), 'node_modules'], extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.md'], alias: { ts: path.join(__dirname, '/ts'), @@ -62,25 +59,28 @@ module.exports = { from: /^\/docs\/.*$/, to: function() { return 'index.html'; - } - } - ] + }, + }, + ], }, disableHostCheck: true, }, - plugins: process.env.NODE_ENV === 'production' ? [ - // Since we do not use moment's locale feature, we exclude them from the bundle. - // This reduces the bundle size by 0.4MB. - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), - new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': JSON.stringify(process.env.NODE_ENV) - } - }), - new webpack.optimize.UglifyJsPlugin({ - mangle: { - except: ['BigNumber'] - } - }) - ] : [], + plugins: + process.env.NODE_ENV === 'production' + ? [ + // Since we do not use moment's locale feature, we exclude them from the bundle. + // This reduces the bundle size by 0.4MB. + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(process.env.NODE_ENV), + }, + }), + new webpack.optimize.UglifyJsPlugin({ + mangle: { + except: ['BigNumber'], + }, + }), + ] + : [], }; |