aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrandon Millman <brandon.millman@gmail.com>2018-02-01 07:30:09 +0800
committerBrandon Millman <brandon.millman@gmail.com>2018-02-01 07:30:09 +0800
commit03cb7233dc5b8556952b4481f87a292e0fca1acf (patch)
tree4c203211a7ce7b0f44ebc45bb6c40621d4ee5b7e
parent3a1ca32ff172f735e4b69f125fea4237c83643f0 (diff)
parent6682abf89dcdf566f05f8d88cb6af06c4bb1f6a2 (diff)
downloaddexon-0x-contracts-03cb7233dc5b8556952b4481f87a292e0fca1acf.tar.gz
dexon-0x-contracts-03cb7233dc5b8556952b4481f87a292e0fca1acf.tar.zst
dexon-0x-contracts-03cb7233dc5b8556952b4481f87a292e0fca1acf.zip
Merge branch 'development' into feature/testnet-faucets/order-dispenser
* development: (49 commits) Prettier Updated contract generation in 0x to new abi-gen CLI Add PR number to changelog Fix lint errors Removed deprecated CLI options Add protected keyword to underscore lint rule Remove unused prop Fix prettier Uppercase Networks enum values Make default gasPrice more readable Fix prettier mess Fix linter errors Shrink img Fix all setState calls after unmounted errors. Decided to create our own flag rather then using a cancellablePromise since there was little to be gained from this alternative Fix bug where we were return undefined instead of the empty object Default the derivation path to that found in the Ledger subprovider Add browser data to dialog info Add Rinkeby support Pass in whether we want the personal message prefix appended and never append it for Ledger. This fixes signing when Ledger is used and the backing node is not Parity Wholesale replace the tokenByAddress and de-dup properly ...
-rw-r--r--packages/0x.js/contract_templates/contract.handlebars (renamed from packages/0x.js/contract_templates/contract.mustache)2
-rw-r--r--packages/0x.js/contract_templates/partials/call.handlebars (renamed from packages/0x.js/contract_templates/partials/call.mustache)4
-rw-r--r--packages/0x.js/contract_templates/partials/params.handlebars (renamed from packages/0x.js/contract_templates/partials/params.mustache)0
-rw-r--r--packages/0x.js/contract_templates/partials/return_type.handlebars (renamed from packages/0x.js/contract_templates/partials/return_type.mustache)0
-rw-r--r--packages/0x.js/contract_templates/partials/tx.handlebars (renamed from packages/0x.js/contract_templates/partials/tx.mustache)10
-rw-r--r--packages/0x.js/contract_templates/partials/typed_params.handlebars (renamed from packages/0x.js/contract_templates/partials/typed_params.mustache)0
-rw-r--r--packages/0x.js/package.json2
-rw-r--r--packages/0x.js/src/contract_wrappers/contract_wrapper.ts2
-rw-r--r--packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts6
-rw-r--r--packages/0x.js/src/contract_wrappers/exchange_wrapper.ts6
-rw-r--r--packages/0x.js/src/contract_wrappers/generated/base_contract.ts12
-rw-r--r--packages/0x.js/src/contract_wrappers/token_wrapper.ts6
-rw-r--r--packages/0x.js/test/ether_token_wrapper_test.ts2
-rw-r--r--packages/0x.js/test/exchange_wrapper_test.ts2
-rw-r--r--packages/0x.js/test/subscription_test.ts4
-rw-r--r--packages/0x.js/test/token_wrapper_test.ts2
-rw-r--r--packages/abi-gen/CHANGELOG.md4
-rw-r--r--packages/abi-gen/src/index.ts54
-rw-r--r--packages/testnet-faucets/src/ts/ether_request_queue.ts6
-rw-r--r--packages/testnet-faucets/src/ts/request_queue.ts36
-rw-r--r--packages/testnet-faucets/src/ts/zrx_request_queue.ts4
-rw-r--r--packages/tslint-config/CHANGELOG.md4
-rw-r--r--packages/tslint-config/rules/underscorePrivateAndProtectedRule.ts (renamed from packages/tslint-config/rules/underscorePrivatesRule.ts)4
-rw-r--r--packages/tslint-config/tslint.json2
-rw-r--r--packages/website/package.json1
-rw-r--r--packages/website/public/gifs/0xAnimation.gifbin909585 -> 0 bytes
-rw-r--r--packages/website/public/images/ledger_icon.pngbin0 -> 4885 bytes
-rw-r--r--packages/website/public/images/loading_poster.pngbin2505 -> 0 bytes
-rw-r--r--packages/website/public/images/metamask_or_parity.pngbin0 -> 22907 bytes
-rw-r--r--packages/website/public/images/network_icons/kovan.pngbin0 -> 244 bytes
-rw-r--r--packages/website/public/images/network_icons/mainnet.pngbin0 -> 205 bytes
-rw-r--r--packages/website/public/images/network_icons/rinkeby.pngbin0 -> 126 bytes
-rw-r--r--packages/website/public/images/network_icons/ropsten.pngbin0 -> 251 bytes
-rw-r--r--packages/website/public/videos/0xAnimation.mp4bin970342 -> 0 bytes
-rw-r--r--packages/website/ts/blockchain.ts361
-rw-r--r--packages/website/ts/components/dialogs/blockchain_err_dialog.tsx4
-rw-r--r--packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx67
-rw-r--r--packages/website/ts/components/dialogs/ledger_config_dialog.tsx59
-rw-r--r--packages/website/ts/components/dialogs/send_dialog.tsx13
-rw-r--r--packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx10
-rw-r--r--packages/website/ts/components/dropdowns/network_drop_down.tsx40
-rw-r--r--packages/website/ts/components/eth_weth_conversion_button.tsx20
-rw-r--r--packages/website/ts/components/eth_wrappers.tsx101
-rw-r--r--packages/website/ts/components/fill_order.tsx40
-rw-r--r--packages/website/ts/components/generate_order/asset_picker.tsx16
-rw-r--r--packages/website/ts/components/generate_order/generate_order_form.tsx21
-rw-r--r--packages/website/ts/components/generate_order/new_token_form.tsx17
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx7
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx5
-rw-r--r--packages/website/ts/components/inputs/token_amount_input.tsx64
-rw-r--r--packages/website/ts/components/portal.tsx78
-rw-r--r--packages/website/ts/components/send_button.tsx19
-rw-r--r--packages/website/ts/components/token_balances.tsx198
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx148
-rw-r--r--packages/website/ts/components/top_bar/provider_picker.tsx81
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx (renamed from packages/website/ts/components/top_bar.tsx)51
-rw-r--r--packages/website/ts/components/top_bar/top_bar_menu_item.tsx (renamed from packages/website/ts/components/top_bar_menu_item.tsx)0
-rw-r--r--packages/website/ts/components/ui/drop_down.tsx (renamed from packages/website/ts/components/ui/drop_down_menu_item.tsx)56
-rw-r--r--packages/website/ts/components/ui/loading.tsx39
-rw-r--r--packages/website/ts/containers/generate_order_form.tsx13
-rw-r--r--packages/website/ts/containers/portal.tsx12
-rw-r--r--packages/website/ts/globals.d.ts1
-rw-r--r--packages/website/ts/local_storage/tracked_token_storage.ts14
-rw-r--r--packages/website/ts/pages/about/about.tsx2
-rw-r--r--packages/website/ts/pages/documentation/documentation.tsx31
-rw-r--r--packages/website/ts/pages/faq/faq.tsx2
-rw-r--r--packages/website/ts/pages/landing/landing.tsx2
-rw-r--r--packages/website/ts/pages/not_found.tsx2
-rw-r--r--packages/website/ts/pages/wiki/wiki.tsx23
-rw-r--r--packages/website/ts/redux/dispatcher.ts56
-rw-r--r--packages/website/ts/redux/reducer.ts81
-rw-r--r--packages/website/ts/types.ts35
-rw-r--r--packages/website/ts/utils/configs.ts10
-rw-r--r--packages/website/ts/utils/constants.ts20
-rw-r--r--packages/website/ts/utils/mui_theme.ts1
-rw-r--r--packages/website/ts/utils/utils.ts10
-rw-r--r--packages/website/ts/web3_wrapper.ts21
77 files changed, 1306 insertions, 720 deletions
diff --git a/packages/0x.js/contract_templates/contract.mustache b/packages/0x.js/contract_templates/contract.handlebars
index 3e501cce6..d3fe1b8cc 100644
--- a/packages/0x.js/contract_templates/contract.mustache
+++ b/packages/0x.js/contract_templates/contract.handlebars
@@ -20,6 +20,6 @@ export class {{contractName}}Contract extends BaseContract {
{{/each}}
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) {
super(web3ContractInstance, defaults);
- classUtils.bindAll(this, ['web3ContractInstance', 'defaults']);
+ classUtils.bindAll(this, ['_web3ContractInstance', '_defaults']);
}
} // tslint:disable:max-file-line-count
diff --git a/packages/0x.js/contract_templates/partials/call.mustache b/packages/0x.js/contract_templates/partials/call.handlebars
index ef4bda724..0475136f0 100644
--- a/packages/0x.js/contract_templates/partials/call.mustache
+++ b/packages/0x.js/contract_templates/partials/call.handlebars
@@ -5,8 +5,8 @@ public {{this.name}} = {
): Promise<{{> return_type outputs=outputs}}> {
const self = this as {{contractName}}Contract;
const result = await promisify<{{> return_type outputs=outputs}}>(
- self.web3ContractInstance.{{this.name}}.call,
- self.web3ContractInstance,
+ self._web3ContractInstance.{{this.name}}.call,
+ self._web3ContractInstance,
)(
{{> params inputs=inputs}}
);
diff --git a/packages/0x.js/contract_templates/partials/params.mustache b/packages/0x.js/contract_templates/partials/params.handlebars
index ac5d4ae85..ac5d4ae85 100644
--- a/packages/0x.js/contract_templates/partials/params.mustache
+++ b/packages/0x.js/contract_templates/partials/params.handlebars
diff --git a/packages/0x.js/contract_templates/partials/return_type.mustache b/packages/0x.js/contract_templates/partials/return_type.handlebars
index 383961a40..383961a40 100644
--- a/packages/0x.js/contract_templates/partials/return_type.mustache
+++ b/packages/0x.js/contract_templates/partials/return_type.handlebars
diff --git a/packages/0x.js/contract_templates/partials/tx.mustache b/packages/0x.js/contract_templates/partials/tx.handlebars
index 8a43e5319..9df83266a 100644
--- a/packages/0x.js/contract_templates/partials/tx.mustache
+++ b/packages/0x.js/contract_templates/partials/tx.handlebars
@@ -9,7 +9,7 @@ public {{this.name}} = {
{{/this.payable}}
): Promise<string> {
const self = this as {{contractName}}Contract;
- const txDataWithDefaults = await self.applyDefaultsToTxDataAsync(
+ const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
txData,
self.{{this.name}}.estimateGasAsync.bind(
self,
@@ -17,7 +17,7 @@ public {{this.name}} = {
),
);
const txHash = await promisify<string>(
- self.web3ContractInstance.{{this.name}}, self.web3ContractInstance,
+ self._web3ContractInstance.{{this.name}}, self._web3ContractInstance,
)(
{{> params inputs=inputs}}
txDataWithDefaults,
@@ -29,11 +29,11 @@ public {{this.name}} = {
txData: TxData = {},
): Promise<number> {
const self = this as {{contractName}}Contract;
- const txDataWithDefaults = await self.applyDefaultsToTxDataAsync(
+ const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
txData,
);
const gas = await promisify<number>(
- self.web3ContractInstance.{{this.name}}.estimateGas, self.web3ContractInstance,
+ self._web3ContractInstance.{{this.name}}.estimateGas, self._web3ContractInstance,
)(
{{> params inputs=inputs}}
txDataWithDefaults,
@@ -45,7 +45,7 @@ public {{this.name}} = {
txData: TxData = {},
): string {
const self = this as {{contractName}}Contract;
- const abiEncodedTransactionData = self.web3ContractInstance.{{this.name}}.getData();
+ const abiEncodedTransactionData = self._web3ContractInstance.{{this.name}}.getData();
return abiEncodedTransactionData;
},
};
diff --git a/packages/0x.js/contract_templates/partials/typed_params.mustache b/packages/0x.js/contract_templates/partials/typed_params.handlebars
index 3ea4b2e95..3ea4b2e95 100644
--- a/packages/0x.js/contract_templates/partials/typed_params.mustache
+++ b/packages/0x.js/contract_templates/partials/typed_params.handlebars
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json
index 9cce372a5..32063aab2 100644
--- a/packages/0x.js/package.json
+++ b/packages/0x.js/package.json
@@ -12,7 +12,7 @@
"upload_docs_json":
"aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json",
"generate_contract_wrappers":
- "node ../abi-gen/lib/index.js --abiGlob 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --templates contract_templates --output src/contract_wrappers/generated",
+ "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",
"lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'",
"test:circleci": "run-s test:coverage report_test_coverage",
"test": "run-s clean test:commonjs",
diff --git a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts
index 5f11d810a..873489dc9 100644
--- a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts
@@ -52,7 +52,7 @@ export class ContractWrapper {
this._onLogAddedSubscriptionToken = undefined;
this._onLogRemovedSubscriptionToken = undefined;
}
- protected unsubscribeAll(): void {
+ protected _unsubscribeAll(): void {
const filterTokens = _.keys(this._filterCallbacks);
_.each(filterTokens, filterToken => {
this._unsubscribe(filterToken);
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 8fa7aa78b..cbafcfe94 100644
--- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
@@ -159,11 +159,11 @@ export class EtherTokenWrapper extends ContractWrapper {
/**
* Cancels all existing subscriptions
*/
- public unsubscribeAll(): void {
- super.unsubscribeAll();
+ public _unsubscribeAll(): void {
+ super._unsubscribeAll();
}
private _invalidateContractInstance(): void {
- this.unsubscribeAll();
+ this._unsubscribeAll();
this._etherTokenContractsByAddress = {};
}
private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> {
diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
index fceab851a..63c0d073a 100644
--- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
@@ -678,8 +678,8 @@ export class ExchangeWrapper extends ContractWrapper {
/**
* Cancels all existing subscriptions
*/
- public unsubscribeAll(): void {
- super.unsubscribeAll();
+ public _unsubscribeAll(): void {
+ super._unsubscribeAll();
}
/**
* Gets historical logs without creating a subscription
@@ -861,7 +861,7 @@ export class ExchangeWrapper extends ContractWrapper {
return contractAddress;
}
private _invalidateContractInstances(): void {
- this.unsubscribeAll();
+ this._unsubscribeAll();
delete this._exchangeContractIfExists;
}
private async _isValidSignatureUsingContractCallAsync(
diff --git a/packages/0x.js/src/contract_wrappers/generated/base_contract.ts b/packages/0x.js/src/contract_wrappers/generated/base_contract.ts
index 28a7e2f52..d8fac7eea 100644
--- a/packages/0x.js/src/contract_wrappers/generated/base_contract.ts
+++ b/packages/0x.js/src/contract_wrappers/generated/base_contract.ts
@@ -3,9 +3,9 @@ import * as _ from 'lodash';
import * as Web3 from 'web3';
export class BaseContract {
- protected web3ContractInstance: Web3.ContractInstance;
- protected defaults: Partial<TxData>;
- protected async applyDefaultsToTxDataAsync<T extends TxData|TxDataPayable>(
+ protected _web3ContractInstance: Web3.ContractInstance;
+ protected _defaults: Partial<TxData>;
+ protected async _applyDefaultsToTxDataAsync<T extends TxData|TxDataPayable>(
txData: T,
estimateGasAsync?: (txData: T) => Promise<number>,
): Promise<TxData> {
@@ -15,7 +15,7 @@ export class BaseContract {
// 3. Gas estimate calculation + safety margin
const removeUndefinedProperties = _.pickBy;
const txDataWithDefaults = {
- ...removeUndefinedProperties(this.defaults),
+ ...removeUndefinedProperties(this._defaults),
...removeUndefinedProperties(txData as any),
// HACK: TS can't prove that T is spreadable.
// Awaiting https://github.com/Microsoft/TypeScript/pull/13288 to be merged
@@ -27,7 +27,7 @@ export class BaseContract {
return txDataWithDefaults;
}
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) {
- this.web3ContractInstance = web3ContractInstance;
- this.defaults = defaults;
+ this._web3ContractInstance = web3ContractInstance;
+ this._defaults = defaults;
}
}
diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts
index 4216ff462..98c24d059 100644
--- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts
@@ -342,8 +342,8 @@ export class TokenWrapper extends ContractWrapper {
/**
* Cancels all existing subscriptions
*/
- public unsubscribeAll(): void {
- super.unsubscribeAll();
+ public _unsubscribeAll(): void {
+ super._unsubscribeAll();
}
/**
* Gets historical logs without creating a subscription
@@ -374,7 +374,7 @@ export class TokenWrapper extends ContractWrapper {
return logs;
}
private _invalidateContractInstances(): void {
- this.unsubscribeAll();
+ this._unsubscribeAll();
this._tokenContractsByAddress = {};
}
private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
diff --git a/packages/0x.js/test/ether_token_wrapper_test.ts b/packages/0x.js/test/ether_token_wrapper_test.ts
index 6069b42bf..9716abab8 100644
--- a/packages/0x.js/test/ether_token_wrapper_test.ts
+++ b/packages/0x.js/test/ether_token_wrapper_test.ts
@@ -144,7 +144,7 @@ describe('EtherTokenWrapper', () => {
etherTokenAddress = etherToken.address;
});
afterEach(() => {
- zeroEx.etherToken.unsubscribeAll();
+ zeroEx.etherToken._unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribe` callback,
diff --git a/packages/0x.js/test/exchange_wrapper_test.ts b/packages/0x.js/test/exchange_wrapper_test.ts
index c15cd65a9..044298601 100644
--- a/packages/0x.js/test/exchange_wrapper_test.ts
+++ b/packages/0x.js/test/exchange_wrapper_test.ts
@@ -921,7 +921,7 @@ describe('ExchangeWrapper', () => {
);
});
afterEach(async () => {
- zeroEx.exchange.unsubscribeAll();
+ zeroEx.exchange._unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribe` callback,
diff --git a/packages/0x.js/test/subscription_test.ts b/packages/0x.js/test/subscription_test.ts
index f485bf84b..337e2effa 100644
--- a/packages/0x.js/test/subscription_test.ts
+++ b/packages/0x.js/test/subscription_test.ts
@@ -49,7 +49,7 @@ describe('SubscriptionTest', () => {
tokenAddress = token.address;
});
afterEach(() => {
- zeroEx.token.unsubscribeAll();
+ zeroEx.token._unsubscribeAll();
_.each(stubs, s => s.restore());
stubs = [];
});
@@ -76,7 +76,7 @@ describe('SubscriptionTest', () => {
const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop;
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))];
- zeroEx.token.unsubscribeAll();
+ zeroEx.token._unsubscribeAll();
done();
})().catch(done);
});
diff --git a/packages/0x.js/test/token_wrapper_test.ts b/packages/0x.js/test/token_wrapper_test.ts
index 070d6ec47..34ebe30c2 100644
--- a/packages/0x.js/test/token_wrapper_test.ts
+++ b/packages/0x.js/test/token_wrapper_test.ts
@@ -377,7 +377,7 @@ describe('TokenWrapper', () => {
tokenAddress = token.address;
});
afterEach(() => {
- zeroEx.token.unsubscribeAll();
+ zeroEx.token._unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribe` callback,
diff --git a/packages/abi-gen/CHANGELOG.md b/packages/abi-gen/CHANGELOG.md
index fb2c6eb24..ffa8a7a35 100644
--- a/packages/abi-gen/CHANGELOG.md
+++ b/packages/abi-gen/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## v0.2.0 - _???_
+
+* Added CLI options for explicit specifying location of partials and main template (#346)
+
## v0.1.0 - _January 11, 2018_
* Fixed array typings with union types (#295)
diff --git a/packages/abi-gen/src/index.ts b/packages/abi-gen/src/index.ts
index 527af32b1..fe2b56524 100644
--- a/packages/abi-gen/src/index.ts
+++ b/packages/abi-gen/src/index.ts
@@ -17,24 +17,45 @@ import { utils } from './utils';
const ABI_TYPE_CONSTRUCTOR = 'constructor';
const ABI_TYPE_METHOD = 'function';
const ABI_TYPE_EVENT = 'event';
-const MAIN_TEMPLATE_NAME = 'contract.mustache';
const args = yargs
- .option('abiGlob', {
+ .option('abis', {
describe: 'Glob pattern to search for ABI JSON files',
type: 'string',
- demand: true,
- })
- .option('templates', {
- describe: 'Folder where to search for templates',
- type: 'string',
- demand: true,
+ demandOption: true,
})
.option('output', {
+ alias: ['o', 'out'],
describe: 'Folder where to put the output files',
type: 'string',
- demand: true,
- }).argv;
+ normalize: true,
+ demandOption: true,
+ })
+ .option('partials', {
+ describe: 'Glob pattern for the partial template files',
+ type: 'string',
+ implies: 'template',
+ })
+ .option('template', {
+ describe: 'Path for the main template file that will be used to generate each contract',
+ type: 'string',
+ demandOption: true,
+ normalize: true,
+ })
+ .example(
+ "$0 --abis 'src/artifacts/**/*.json' --out 'src/contracts/generated/' --partials 'src/templates/partials/**/*.handlebars' --template 'src/templates/contract.handlebars'",
+ 'Full usage example',
+ ).argv;
+
+function registerPartials(partialsGlob: string) {
+ const partialTemplateFileNames = globSync(partialsGlob);
+ utils.log(`Found ${chalk.green(`${partialTemplateFileNames.length}`)} ${chalk.bold('partial')} templates`);
+ for (const partialTemplateFileName of partialTemplateFileNames) {
+ const namedContent = utils.getNamedContent(partialTemplateFileName);
+ Handlebars.registerPartial(namedContent.name, namedContent.content);
+ }
+ return partialsGlob;
+}
function writeOutputFile(name: string, renderedTsCode: string): void {
const fileName = toSnakeCase(name);
@@ -45,15 +66,14 @@ function writeOutputFile(name: string, renderedTsCode: string): void {
Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input));
Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output));
-const partialTemplateFileNames = globSync(`${args.templates}/partials/**/*.mustache`);
-for (const partialTemplateFileName of partialTemplateFileNames) {
- const namedContent = utils.getNamedContent(partialTemplateFileName);
- Handlebars.registerPartial(namedContent.name, namedContent.content);
-}
-const mainTemplate = utils.getNamedContent(`${args.templates}/${MAIN_TEMPLATE_NAME}`);
+if (args.partials) {
+ registerPartials(args.partials);
+}
+const mainTemplate = utils.getNamedContent(args.template);
const template = Handlebars.compile<ContextData>(mainTemplate.content);
-const abiFileNames = globSync(args.abiGlob);
+const abiFileNames = globSync(args.abis);
+
if (_.isEmpty(abiFileNames)) {
utils.log(`${chalk.red(`No ABI files found.`)}`);
utils.log(`Please make sure you've passed the correct folder name and that the files have
diff --git a/packages/testnet-faucets/src/ts/ether_request_queue.ts b/packages/testnet-faucets/src/ts/ether_request_queue.ts
index 1c4b19ab9..710d49f39 100644
--- a/packages/testnet-faucets/src/ts/ether_request_queue.ts
+++ b/packages/testnet-faucets/src/ts/ether_request_queue.ts
@@ -9,14 +9,14 @@ import { utils } from './utils';
const DISPENSE_AMOUNT_ETHER = 0.1;
export class EtherRequestQueue extends RequestQueue {
- protected async processNextRequestFireAndForgetAsync(recipientAddress: string) {
+ protected async _processNextRequestFireAndForgetAsync(recipientAddress: string) {
utils.consoleLog(`Processing ETH ${recipientAddress}`);
- const sendTransactionAsync = promisify(this.web3.eth.sendTransaction);
+ const sendTransactionAsync = promisify(this._web3.eth.sendTransaction);
try {
const txHash = await sendTransactionAsync({
from: configs.DISPENSER_ADDRESS,
to: recipientAddress,
- value: this.web3.toWei(DISPENSE_AMOUNT_ETHER, 'ether'),
+ value: this._web3.toWei(DISPENSE_AMOUNT_ETHER, 'ether'),
});
utils.consoleLog(`Sent ${DISPENSE_AMOUNT_ETHER} ETH to ${recipientAddress} tx: ${txHash}`);
} catch (err) {
diff --git a/packages/testnet-faucets/src/ts/request_queue.ts b/packages/testnet-faucets/src/ts/request_queue.ts
index 20f2833a1..7d685522a 100644
--- a/packages/testnet-faucets/src/ts/request_queue.ts
+++ b/packages/testnet-faucets/src/ts/request_queue.ts
@@ -11,46 +11,46 @@ const MAX_QUEUE_SIZE = 500;
const DEFAULT_QUEUE_INTERVAL_MS = 1000;
export class RequestQueue {
- protected queueIntervalMs: number;
- protected queue: string[];
- protected queueIntervalId: NodeJS.Timer;
- protected web3: Web3;
+ protected _queueIntervalMs: number;
+ protected _queue: string[];
+ protected _queueIntervalId: NodeJS.Timer;
+ protected _web3: Web3;
constructor(web3: any) {
- this.queueIntervalMs = DEFAULT_QUEUE_INTERVAL_MS;
- this.queue = [];
+ this._queueIntervalMs = DEFAULT_QUEUE_INTERVAL_MS;
+ this._queue = [];
- this.web3 = web3;
+ this._web3 = web3;
- this.start();
+ this._start();
}
public add(recipientAddress: string): boolean {
if (this.isFull()) {
return false;
}
- this.queue.push(recipientAddress);
+ this._queue.push(recipientAddress);
return true;
}
public size(): number {
- return this.queue.length;
+ return this._queue.length;
}
public isFull(): boolean {
return this.size() >= MAX_QUEUE_SIZE;
}
- protected start() {
- this.queueIntervalId = timers.setInterval(() => {
- const recipientAddress = this.queue.shift();
+ protected _start() {
+ this._queueIntervalId = timers.setInterval(() => {
+ const recipientAddress = this._queue.shift();
if (_.isUndefined(recipientAddress)) {
return;
}
// tslint:disable-next-line:no-floating-promises
- this.processNextRequestFireAndForgetAsync(recipientAddress);
- }, this.queueIntervalMs);
+ this._processNextRequestFireAndForgetAsync(recipientAddress);
+ }, this._queueIntervalMs);
}
- protected stop() {
- clearInterval(this.queueIntervalId);
+ protected _stop() {
+ clearInterval(this._queueIntervalId);
}
// tslint:disable-next-line:prefer-function-over-method
- protected async processNextRequestFireAndForgetAsync(recipientAddress: string) {
+ protected async _processNextRequestFireAndForgetAsync(recipientAddress: string) {
throw new Error('Expected processNextRequestFireAndForgetAsync to be implemented by a subclass');
}
}
diff --git a/packages/testnet-faucets/src/ts/zrx_request_queue.ts b/packages/testnet-faucets/src/ts/zrx_request_queue.ts
index 1a8bfe1d8..3659f4856 100644
--- a/packages/testnet-faucets/src/ts/zrx_request_queue.ts
+++ b/packages/testnet-faucets/src/ts/zrx_request_queue.ts
@@ -20,10 +20,10 @@ export class ZRXRequestQueue extends RequestQueue {
private _zeroEx: ZeroEx;
constructor(web3: Web3, zeroEx: ZeroEx) {
super(web3);
- this.queueIntervalMs = QUEUE_INTERVAL_MS;
+ this._queueIntervalMs = QUEUE_INTERVAL_MS;
this._zeroEx = zeroEx;
}
- protected async processNextRequestFireAndForgetAsync(recipientAddress: string) {
+ protected async _processNextRequestFireAndForgetAsync(recipientAddress: string) {
utils.consoleLog(`Processing ZRX ${recipientAddress}`);
const baseUnitAmount = ZeroEx.toBaseUnitAmount(DISPENSE_AMOUNT_ZRX, 18);
try {
diff --git a/packages/tslint-config/CHANGELOG.md b/packages/tslint-config/CHANGELOG.md
index 1d56bca5b..04d705941 100644
--- a/packages/tslint-config/CHANGELOG.md
+++ b/packages/tslint-config/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## v0.5.0 - _TBD, 2018_
+
+ * Modified custom 'underscore-privates' rule, changing it to 'underscore-private-and-protected' requiring underscores to be prepended to both private and protected variable names (#354)
+
## v0.4.0 - _December 28, 2017_
* Added custom 'underscore-privates' rule, requiring underscores to be prepended to private variable names
diff --git a/packages/tslint-config/rules/underscorePrivatesRule.ts b/packages/tslint-config/rules/underscorePrivateAndProtectedRule.ts
index 472ea09ff..2f05b0c18 100644
--- a/packages/tslint-config/rules/underscorePrivatesRule.ts
+++ b/packages/tslint-config/rules/underscorePrivateAndProtectedRule.ts
@@ -12,7 +12,7 @@ type RelevantClassMember =
// Copied from: https://github.com/DanielRosenwasser/underscore-privates-tslint-rule
// The version on github is not published on npm
export class Rule extends Lint.Rules.AbstractRule {
- public static FAILURE_STRING = 'private members must be prefixed with an underscore';
+ public static FAILURE_STRING = 'private and protected members must be prefixed with an underscore';
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
@@ -54,7 +54,7 @@ function nameStartsWithUnderscore(text: string) {
return text.charCodeAt(0) === UNDERSCORE.charCodeAt(0);
}
function memberIsPrivate(node: ts.Declaration) {
- return Lint.hasModifier(node.modifiers, ts.SyntaxKind.PrivateKeyword);
+ return Lint.hasModifier(node.modifiers, ts.SyntaxKind.PrivateKeyword, ts.SyntaxKind.ProtectedKeyword);
}
function nameIsIdentifier(node: ts.Node): node is ts.Identifier {
return node.kind === ts.SyntaxKind.Identifier;
diff --git a/packages/tslint-config/tslint.json b/packages/tslint-config/tslint.json
index 44a8517dc..3266b022f 100644
--- a/packages/tslint-config/tslint.json
+++ b/packages/tslint-config/tslint.json
@@ -73,7 +73,7 @@
],
"space-within-parens": false,
"type-literal-delimiter": true,
- "underscore-privates": true,
+ "underscore-private-and-protected": true,
"variable-name": [true, "ban-keywords", "allow-pascal-case"],
"whitespace": [
true,
diff --git a/packages/website/package.json b/packages/website/package.json
index a5a9eb00a..3d6cc24f7 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -36,7 +36,6 @@
"find-versions": "^2.0.0",
"is-mobile": "^0.2.2",
"jsonschema": "^1.2.0",
- "ledgerco": "0xProject/ledger-node-js-api",
"less": "^2.7.2",
"lodash": "^4.17.4",
"material-ui": "^0.17.1",
diff --git a/packages/website/public/gifs/0xAnimation.gif b/packages/website/public/gifs/0xAnimation.gif
deleted file mode 100644
index b3e32a6ad..000000000
--- a/packages/website/public/gifs/0xAnimation.gif
+++ /dev/null
Binary files differ
diff --git a/packages/website/public/images/ledger_icon.png b/packages/website/public/images/ledger_icon.png
new file mode 100644
index 000000000..29b8df08f
--- /dev/null
+++ b/packages/website/public/images/ledger_icon.png
Binary files differ
diff --git a/packages/website/public/images/loading_poster.png b/packages/website/public/images/loading_poster.png
deleted file mode 100644
index e5618f260..000000000
--- a/packages/website/public/images/loading_poster.png
+++ /dev/null
Binary files differ
diff --git a/packages/website/public/images/metamask_or_parity.png b/packages/website/public/images/metamask_or_parity.png
new file mode 100644
index 000000000..fda646558
--- /dev/null
+++ b/packages/website/public/images/metamask_or_parity.png
Binary files differ
diff --git a/packages/website/public/images/network_icons/kovan.png b/packages/website/public/images/network_icons/kovan.png
new file mode 100644
index 000000000..f47a12e74
--- /dev/null
+++ b/packages/website/public/images/network_icons/kovan.png
Binary files differ
diff --git a/packages/website/public/images/network_icons/mainnet.png b/packages/website/public/images/network_icons/mainnet.png
new file mode 100644
index 000000000..6693635d6
--- /dev/null
+++ b/packages/website/public/images/network_icons/mainnet.png
Binary files differ
diff --git a/packages/website/public/images/network_icons/rinkeby.png b/packages/website/public/images/network_icons/rinkeby.png
new file mode 100644
index 000000000..f9ba18778
--- /dev/null
+++ b/packages/website/public/images/network_icons/rinkeby.png
Binary files differ
diff --git a/packages/website/public/images/network_icons/ropsten.png b/packages/website/public/images/network_icons/ropsten.png
new file mode 100644
index 000000000..894910b34
--- /dev/null
+++ b/packages/website/public/images/network_icons/ropsten.png
Binary files differ
diff --git a/packages/website/public/videos/0xAnimation.mp4 b/packages/website/public/videos/0xAnimation.mp4
deleted file mode 100644
index d78c07d4f..000000000
--- a/packages/website/public/videos/0xAnimation.mp4
+++ /dev/null
Binary files differ
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index 5530701c0..71995e2cd 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -37,10 +37,10 @@ import {
EtherscanLinkSuffixes,
ProviderType,
Side,
+ SideToAssetToken,
SignatureData,
Token,
TokenByAddress,
- TokenStateByAddress,
} from 'ts/types';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
@@ -54,6 +54,7 @@ import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
import * as MintableArtifacts from '../contracts/Mintable.json';
const BLOCK_NUMBER_BACK_TRACK = 50;
+const GWEI_IN_WEI = 1000000000;
export class Blockchain {
public networkId: number;
@@ -64,8 +65,9 @@ export class Blockchain {
private _exchangeAddress: string;
private _userAddress: string;
private _cachedProvider: Web3.Provider;
+ private _cachedProviderNetworkId: number;
private _ledgerSubprovider: LedgerWalletSubprovider;
- private _zrxPollIntervalId: NodeJS.Timer;
+ private _defaultGasPrice: BigNumber;
private static async _onPageLoadAsync(): Promise<void> {
if (document.readyState === 'complete') {
return; // Already loaded
@@ -111,7 +113,7 @@ export class Blockchain {
// injected into their browser.
provider = new ProviderEngine();
provider.addProvider(new FilterSubprovider());
- const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET;
+ const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN;
provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId]));
provider.start();
}
@@ -121,6 +123,10 @@ export class Blockchain {
constructor(dispatcher: Dispatcher, isSalePage: boolean = false) {
this._dispatcher = dispatcher;
this._userAddress = '';
+ const defaultGasPrice = GWEI_IN_WEI * 30;
+ this._defaultGasPrice = new BigNumber(defaultGasPrice);
+ // tslint:disable-next-line:no-floating-promises
+ this._updateDefaultGasPriceAsync();
// tslint:disable-next-line:no-floating-promises
this._onPageLoadInitFireAndForgetAsync();
}
@@ -133,14 +139,14 @@ export class Blockchain {
} else if (this.networkId !== newNetworkId) {
this.networkId = newNetworkId;
this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError);
- await this._fetchTokenInformationAsync();
+ await this.fetchTokenInformationAsync();
await this._rehydrateStoreWithContractEvents();
}
}
public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) {
if (this._userAddress !== newUserAddress) {
this._userAddress = newUserAddress;
- await this._fetchTokenInformationAsync();
+ await this.fetchTokenInformationAsync();
await this._rehydrateStoreWithContractEvents();
}
}
@@ -180,84 +186,96 @@ export class Blockchain {
}
this._ledgerSubprovider.setPathIndex(pathIndex);
}
- public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) {
+ public async updateProviderToLedgerAsync(networkId: number) {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
- // Should actually be Web3.Provider|ProviderEngine union type but it causes issues
- // later on in the logic.
- let provider;
- switch (providerType) {
- case ProviderType.Ledger: {
- const isU2FSupported = await utils.isU2FSupportedAsync();
- if (!isU2FSupported) {
- throw new Error('Cannot update providerType to LEDGER without U2F support');
- }
- // Cache injected provider so that we can switch the user back to it easily
- this._cachedProvider = this._web3Wrapper.getProviderObj();
-
- this._dispatcher.updateUserAddress(''); // Clear old userAddress
-
- provider = new ProviderEngine();
- const ledgerWalletConfigs = {
- networkId: this.networkId,
- ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
- };
- this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
- provider.addProvider(this._ledgerSubprovider);
- provider.addProvider(new FilterSubprovider());
- const networkId = configs.IS_MAINNET_ENABLED
- ? constants.NETWORK_ID_MAINNET
- : constants.NETWORK_ID_TESTNET;
- provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId]));
- provider.start();
- this._web3Wrapper.destroy();
- const shouldPollUserAddress = false;
- this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
- this._zeroEx.setProvider(provider, networkId);
- await this._postInstantiationOrUpdatingProviderZeroExAsync();
- break;
- }
+ const isU2FSupported = await utils.isU2FSupportedAsync();
+ if (!isU2FSupported) {
+ throw new Error('Cannot update providerType to LEDGER without U2F support');
+ }
- case ProviderType.Injected: {
- if (_.isUndefined(this._cachedProvider)) {
- return; // Going from injected to injected, so we noop
- }
- provider = this._cachedProvider;
- const shouldPollUserAddress = true;
- this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
- this._zeroEx.setProvider(provider, this.networkId);
- await this._postInstantiationOrUpdatingProviderZeroExAsync();
- delete this._ledgerSubprovider;
- delete this._cachedProvider;
- break;
- }
+ // Cache injected provider so that we can switch the user back to it easily
+ if (_.isUndefined(this._cachedProvider)) {
+ this._cachedProvider = this._web3Wrapper.getProviderObj();
+ this._cachedProviderNetworkId = this.networkId;
+ }
- default:
- throw utils.spawnSwitchErr('providerType', providerType);
+ this._web3Wrapper.destroy();
+
+ this._userAddress = '';
+ this._dispatcher.updateUserAddress(''); // Clear old userAddress
+
+ const provider = new ProviderEngine();
+ const ledgerWalletConfigs = {
+ networkId,
+ ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
+ };
+ this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
+ provider.addProvider(this._ledgerSubprovider);
+ provider.addProvider(new FilterSubprovider());
+ provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId]));
+ provider.start();
+ this.networkId = networkId;
+ this._dispatcher.updateNetworkId(this.networkId);
+ const shouldPollUserAddress = false;
+ this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
+ this._zeroEx.setProvider(provider, this.networkId);
+ await this._postInstantiationOrUpdatingProviderZeroExAsync();
+ this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState();
+ this._dispatcher.updateProviderType(ProviderType.Ledger);
+ }
+ public async updateProviderToInjectedAsync() {
+ utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
+
+ if (_.isUndefined(this._cachedProvider)) {
+ return; // Going from injected to injected, so we noop
}
- await this._fetchTokenInformationAsync();
+ this._web3Wrapper.destroy();
+
+ const provider = this._cachedProvider;
+ this.networkId = this._cachedProviderNetworkId;
+
+ const shouldPollUserAddress = true;
+ this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
+
+ this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
+
+ this._zeroEx.setProvider(provider, this.networkId);
+ await this._postInstantiationOrUpdatingProviderZeroExAsync();
+
+ await this.fetchTokenInformationAsync();
+ this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState();
+ this._dispatcher.updateProviderType(ProviderType.Injected);
+ delete this._ledgerSubprovider;
+ delete this._cachedProvider;
}
public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> {
utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid);
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
+ this._showFlashMessageIfLedger();
const txHash = await this._zeroEx.token.setProxyAllowanceAsync(
token.address,
this._userAddress,
amountInBaseUnits,
+ {
+ gasPrice: this._defaultGasPrice,
+ },
);
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
- const allowance = amountInBaseUnits;
- this._dispatcher.replaceTokenAllowanceByAddress(token.address, allowance);
}
public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> {
+ this._showFlashMessageIfLedger();
const txHash = await this._zeroEx.token.transferAsync(
token.address,
this._userAddress,
toAddress,
amountInBaseUnits,
+ {
+ gasPrice: this._defaultGasPrice,
+ },
);
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx);
@@ -309,11 +327,15 @@ export class Blockchain {
const shouldThrowOnInsufficientBalanceOrAllowance = true;
+ this._showFlashMessageIfLedger();
const txHash = await this._zeroEx.exchange.fillOrderAsync(
signedOrder,
fillTakerTokenAmount,
shouldThrowOnInsufficientBalanceOrAllowance,
this._userAddress,
+ {
+ gasPrice: this._defaultGasPrice,
+ },
);
const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any;
@@ -324,7 +346,10 @@ export class Blockchain {
return filledTakerTokenAmount;
}
public async cancelOrderAsync(signedOrder: SignedOrder, cancelTakerTokenAmount: BigNumber): Promise<BigNumber> {
- const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount);
+ this._showFlashMessageIfLedger();
+ const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount, {
+ gasPrice: this._defaultGasPrice,
+ });
const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any;
this._zeroEx.exchange.throwLogErrorsAsErrors(logs);
@@ -368,22 +393,25 @@ export class Blockchain {
const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
- this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval(
- async () => {
- const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
- if (!balance.eq(currBalance)) {
- this._dispatcher.replaceTokenBalanceByAddress(token.address, balance);
- intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
- delete this._zrxPollIntervalId;
- }
- },
- 5000,
- (err: Error) => {
- utils.consoleLog(`Polling tokenBalance failed: ${err}`);
- intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
- delete this._zrxPollIntervalId;
- },
- );
+ const newTokenBalancePromise = new Promise((resolve: (balance: BigNumber) => void, reject) => {
+ const tokenPollInterval = intervalUtils.setAsyncExcludingInterval(
+ async () => {
+ const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
+ if (!balance.eq(currBalance)) {
+ intervalUtils.clearAsyncExcludingInterval(tokenPollInterval);
+ resolve(balance);
+ }
+ },
+ 5000,
+ (err: Error) => {
+ utils.consoleLog(`Polling tokenBalance failed: ${err}`);
+ intervalUtils.clearAsyncExcludingInterval(tokenPollInterval);
+ reject(err);
+ },
+ );
+ });
+
+ return newTokenBalancePromise;
}
public async signOrderHashAsync(orderHash: string): Promise<SignatureData> {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
@@ -393,7 +421,21 @@ export class Blockchain {
if (_.isUndefined(makerAddress)) {
throw new Error('Tried to send a sign request but user has no associated addresses');
}
- const ecSignature = await this._zeroEx.signOrderHashAsync(orderHash, makerAddress);
+
+ this._showFlashMessageIfLedger();
+ const nodeVersion = await this._web3Wrapper.getNodeVersionAsync();
+ const isParityNode = utils.isParityNode(nodeVersion);
+ const isTestRpc = utils.isTestRpc(nodeVersion);
+ const isLedgerSigner = !_.isUndefined(this._ledgerSubprovider);
+ let shouldAddPersonalMessagePrefix = true;
+ if ((isParityNode && !isLedgerSigner) || isTestRpc || isLedgerSigner) {
+ shouldAddPersonalMessagePrefix = false;
+ }
+ const ecSignature = await this._zeroEx.signOrderHashAsync(
+ orderHash,
+ makerAddress,
+ shouldAddPersonalMessagePrefix,
+ );
const signatureData = _.extend({}, ecSignature, {
hash: orderHash,
});
@@ -404,11 +446,11 @@ export class Blockchain {
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address);
+ this._showFlashMessageIfLedger();
await mintableContract.mint(constants.MINT_AMOUNT, {
from: this._userAddress,
+ gasPrice: this._defaultGasPrice,
});
- const balanceDelta = constants.MINT_AMOUNT;
- this._dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta);
}
public async getBalanceInEthAsync(owner: string): Promise<BigNumber> {
const balance = await this._web3Wrapper.getBalanceInEthAsync(owner);
@@ -418,14 +460,20 @@ export class Blockchain {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
- const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress);
+ this._showFlashMessageIfLedger();
+ const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress, {
+ gasPrice: this._defaultGasPrice,
+ });
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
}
public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
- const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress);
+ this._showFlashMessageIfLedger();
+ const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress, {
+ gasPrice: this._defaultGasPrice,
+ });
await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
}
public async doesContractExistAtAddressAsync(address: string) {
@@ -451,22 +499,6 @@ export class Blockchain {
}
return [balance, allowance];
}
- public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) {
- const tokenStateByAddress: TokenStateByAddress = {};
- for (const token of tokens) {
- let balance = new BigNumber(0);
- let allowance = new BigNumber(0);
- if (this._doesUserAddressExist()) {
- [balance, allowance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
- }
- const tokenState = {
- balance,
- allowance,
- };
- tokenStateByAddress[token.address] = tokenState;
- }
- this._dispatcher.updateTokenStateByAddress(tokenStateByAddress);
- }
public async getUserAccountsAsync() {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
const userAccountsIfExists = await this._zeroEx.getAvailableAddressesAsync();
@@ -479,10 +511,59 @@ export class Blockchain {
this._web3Wrapper.updatePrevUserAddress(newUserAddress);
}
public destroy() {
- intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
this._web3Wrapper.destroy();
this._stopWatchingExchangeLogFillEvents();
}
+ public async fetchTokenInformationAsync() {
+ utils.assert(
+ !_.isUndefined(this.networkId),
+ 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node',
+ );
+
+ this._dispatcher.updateBlockchainIsLoaded(false);
+
+ const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync();
+
+ const trackedTokensByAddress = trackedTokenStorage.getTrackedTokensByAddress(this._userAddress, this.networkId);
+ const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
+ if (_.isEmpty(trackedTokensByAddress)) {
+ _.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
+ const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
+ token.isTracked = true;
+ trackedTokensByAddress[token.address] = token;
+ });
+ _.each(trackedTokensByAddress, (token: Token, address: string) => {
+ trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token);
+ });
+ } else {
+ // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
+ _.each(trackedTokensByAddress, (trackedToken: Token, address: string) => {
+ if (!_.isUndefined(tokenRegistryTokensByAddress[address])) {
+ tokenRegistryTokensByAddress[address].isTracked = true;
+ }
+ });
+ }
+ const allTokensByAddress = {
+ ...tokenRegistryTokensByAddress,
+ ...trackedTokensByAddress,
+ };
+ const allTokens = _.values(allTokensByAddress);
+ const mostPopularTradingPairTokens: Token[] = [
+ _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }),
+ _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }),
+ ];
+ const sideToAssetToken: SideToAssetToken = {
+ [Side.Deposit]: {
+ address: mostPopularTradingPairTokens[0].address,
+ },
+ [Side.Receive]: {
+ address: mostPopularTradingPairTokens[1].address,
+ },
+ };
+ this._dispatcher.batchDispatch(allTokensByAddress, this.networkId, this._userAddress, sideToAssetToken);
+
+ this._dispatcher.updateBlockchainIsLoaded(true);
+ }
private async _showEtherScanLinkAndAwaitTransactionMinedAsync(
txHash: string,
): Promise<TransactionReceiptWithDecodedLogs> {
@@ -608,7 +689,7 @@ export class Blockchain {
}
}
private _stopWatchingExchangeLogFillEvents(): void {
- this._zeroEx.exchange.unsubscribeAll();
+ this._zeroEx.exchange._unsubscribeAll();
}
private async _getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> {
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
@@ -665,17 +746,23 @@ export class Blockchain {
}
const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists);
- const networkId = !_.isUndefined(networkIdIfExists)
+ this.networkId = !_.isUndefined(networkIdIfExists)
? networkIdIfExists
- : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET;
+ : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN;
+ this._dispatcher.updateNetworkId(this.networkId);
const zeroExConfigs = {
- networkId,
+ networkId: this.networkId,
};
this._zeroEx = new ZeroEx(provider, zeroExConfigs);
this._updateProviderName(injectedWeb3);
const shouldPollUserAddress = true;
- this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, networkId, shouldPollUserAddress);
+ this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress);
await this._postInstantiationOrUpdatingProviderZeroExAsync();
+ this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
+ this._dispatcher.updateUserAddress(this._userAddress);
+ await this.fetchTokenInformationAsync();
+ this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState();
+ await this._rehydrateStoreWithContractEvents();
}
// This method should always be run after instantiating or updating the provider
// of the ZeroEx instance.
@@ -690,60 +777,6 @@ export class Blockchain {
: constants.PROVIDER_NAME_PUBLIC;
this._dispatcher.updateInjectedProviderName(providerName);
}
- private async _fetchTokenInformationAsync() {
- utils.assert(
- !_.isUndefined(this.networkId),
- 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node',
- );
-
- this._dispatcher.updateBlockchainIsLoaded(false);
- this._dispatcher.clearTokenByAddress();
-
- const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync();
-
- // HACK: We need to fetch the userAddress here because otherwise we cannot save the
- // tracked tokens in localStorage under the users address nor fetch the token
- // balances and allowances and we need to do this in order not to trigger the blockchain
- // loading dialog to show up twice. First to load the contracts, and second to load the
- // balances and allowances.
- this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync();
- if (!_.isEmpty(this._userAddress)) {
- this._dispatcher.updateUserAddress(this._userAddress);
- }
-
- let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId);
- const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress);
- if (_.isUndefined(trackedTokensIfExists)) {
- trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
- const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
- token.isTracked = true;
- return token;
- });
- _.each(trackedTokensIfExists, token => {
- trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token);
- });
- } else {
- // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
- _.each(trackedTokensIfExists, trackedToken => {
- if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) {
- tokenRegistryTokensByAddress[trackedToken.address].isTracked = true;
- }
- });
- }
- const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]);
- this._dispatcher.updateTokenByAddress(allTokens);
-
- // Get balance/allowance for tracked tokens
- await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists);
-
- const mostPopularTradingPairTokens: Token[] = [
- _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }),
- _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }),
- ];
- this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address);
- this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address);
- this._dispatcher.updateBlockchainIsLoaded(true);
- }
private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> {
const c = await contract(artifact);
const providerObj = this._web3Wrapper.getProviderObj();
@@ -779,4 +812,20 @@ export class Blockchain {
}
}
}
+ private _showFlashMessageIfLedger() {
+ if (!_.isUndefined(this._ledgerSubprovider)) {
+ this._dispatcher.showFlashMessage('Confirm the transaction on your Ledger Nano S');
+ }
+ }
+ private async _updateDefaultGasPriceAsync() {
+ const endpoint = `${configs.BACKEND_BASE_URL}/eth_gas_station`;
+ const response = await fetch(endpoint);
+ if (response.status !== 200) {
+ return; // noop and we keep hard-coded default
+ }
+ const gasInfo = await response.json();
+ const gasPriceInGwei = new BigNumber(gasInfo.average / 10);
+ const gasPriceInWei = gasPriceInGwei.mul(1000000000);
+ this._defaultGasPrice = gasPriceInWei;
+ }
} // tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx
index f555ca6b1..278e2bbf5 100644
--- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx
+++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx
@@ -3,7 +3,7 @@ import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
-import { BlockchainErrs } from 'ts/types';
+import { BlockchainErrs, Networks } from 'ts/types';
import { colors } from 'ts/utils/colors';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
@@ -129,7 +129,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp
<div>
The 0x smart contracts are not deployed on the Ethereum network you are currently connected to
(network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '}
- {constants.TESTNET_NAME} testnet (network Id: {constants.NETWORK_ID_TESTNET})
+ {Networks.Kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN})
{configs.IS_MAINNET_ENABLED
? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).`
: `.`}
diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
index 661cc1d8c..acd4a7110 100644
--- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
+++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
@@ -2,38 +2,55 @@ import { BigNumber } from '@0xproject/utils';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import * as React from 'react';
+import { Blockchain } from 'ts/blockchain';
import { EthAmountInput } from 'ts/components/inputs/eth_amount_input';
import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
-import { Side, Token, TokenState } from 'ts/types';
+import { Side, Token } from 'ts/types';
import { colors } from 'ts/utils/colors';
interface EthWethConversionDialogProps {
+ blockchain: Blockchain;
+ userAddress: string;
+ networkId: number;
direction: Side;
onComplete: (direction: Side, value: BigNumber) => void;
onCancelled: () => void;
isOpen: boolean;
token: Token;
- tokenState: TokenState;
etherBalance: BigNumber;
+ lastForceTokenStateRefetch: number;
}
interface EthWethConversionDialogState {
value?: BigNumber;
shouldShowIncompleteErrs: boolean;
hasErrors: boolean;
+ isEthTokenBalanceLoaded: boolean;
+ ethTokenBalance: BigNumber;
}
export class EthWethConversionDialog extends React.Component<
EthWethConversionDialogProps,
EthWethConversionDialogState
> {
+ private _isUnmounted: boolean;
constructor() {
super();
+ this._isUnmounted = false;
this.state = {
shouldShowIncompleteErrs: false,
hasErrors: false,
+ isEthTokenBalanceLoaded: false,
+ ethTokenBalance: new BigNumber(0),
};
}
+ public componentWillMount() {
+ // tslint:disable-next-line:no-floating-promises
+ this._fetchEthTokenBalanceAsync();
+ }
+ public componentWillUnmount() {
+ this._isUnmounted = true;
+ }
public render() {
const convertDialogActions = [
<FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />,
@@ -72,8 +89,11 @@ export class EthWethConversionDialog extends React.Component<
<div className="pt2 mx-auto" style={{ width: 245 }}>
{this.props.direction === Side.Receive ? (
<TokenAmountInput
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ blockchain={this.props.blockchain}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
token={this.props.token}
- tokenState={this.props.tokenState}
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
shouldCheckBalance={true}
shouldCheckAllowance={false}
@@ -93,19 +113,20 @@ export class EthWethConversionDialog extends React.Component<
)}
<div className="pt1" style={{ fontSize: 12 }}>
<div className="left">1 ETH = 1 WETH</div>
- {this.props.direction === Side.Receive && (
- <div
- className="right"
- onClick={this._onMaxClick.bind(this)}
- style={{
- color: colors.darkBlue,
- textDecoration: 'underline',
- cursor: 'pointer',
- }}
- >
- Max
- </div>
- )}
+ {this.props.direction === Side.Receive &&
+ this.state.isEthTokenBalanceLoaded && (
+ <div
+ className="right"
+ onClick={this._onMaxClick.bind(this)}
+ style={{
+ color: colors.darkBlue,
+ textDecoration: 'underline',
+ cursor: 'pointer',
+ }}
+ >
+ Max
+ </div>
+ )}
</div>
</div>
</div>
@@ -132,7 +153,7 @@ export class EthWethConversionDialog extends React.Component<
}
private _onMaxClick() {
this.setState({
- value: this.props.tokenState.balance,
+ value: this.state.ethTokenBalance,
});
}
private _onValueChange(isValid: boolean, amount?: BigNumber) {
@@ -160,4 +181,16 @@ export class EthWethConversionDialog extends React.Component<
});
this.props.onCancelled();
}
+ private async _fetchEthTokenBalanceAsync() {
+ const [balance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
+ this.props.userAddress,
+ this.props.token.address,
+ );
+ if (!this._isUnmounted) {
+ this.setState({
+ isEthTokenBalanceLoaded: true,
+ ethTokenBalance: balance,
+ });
+ }
+ }
}
diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
index 60db93c52..bc5f05241 100644
--- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
+++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
@@ -7,8 +7,10 @@ import TextField from 'material-ui/TextField';
import * as React from 'react';
import ReactTooltip = require('react-tooltip');
import { Blockchain } from 'ts/blockchain';
+import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down';
import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button';
import { Dispatcher } from 'ts/redux/dispatcher';
+import { ProviderType } from 'ts/types';
import { colors } from 'ts/utils/colors';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
@@ -27,27 +29,33 @@ interface LedgerConfigDialogProps {
dispatcher: Dispatcher;
blockchain: Blockchain;
networkId: number;
+ providerType: ProviderType;
}
interface LedgerConfigDialogState {
- didConnectFail: boolean;
+ connectionErrMsg: string;
stepIndex: LedgerSteps;
userAddresses: string[];
addressBalances: BigNumber[];
derivationPath: string;
derivationErrMsg: string;
+ preferredNetworkId: number;
}
export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> {
constructor(props: LedgerConfigDialogProps) {
super(props);
+ const derivationPathIfExists = props.blockchain.getLedgerDerivationPathIfExists();
this.state = {
- didConnectFail: false,
+ connectionErrMsg: '',
stepIndex: LedgerSteps.CONNECT,
userAddresses: [],
addressBalances: [],
- derivationPath: configs.DEFAULT_DERIVATION_PATH,
+ derivationPath: _.isUndefined(derivationPathIfExists)
+ ? configs.DEFAULT_DERIVATION_PATH
+ : derivationPathIfExists,
derivationErrMsg: '',
+ preferredNetworkId: props.networkId,
};
}
public render() {
@@ -74,19 +82,28 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
);
}
private _renderConnectStep() {
+ const networkIds = _.values(constants.NETWORK_ID_BY_NAME);
return (
<div>
<div className="h4 pt3">Follow these instructions before proceeding:</div>
- <ol>
+ <ol className="mb0">
<li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li>
- <li className="pb1">Verify that Browser Support is enabled in Settings</li>
+ <li className="pb1">Verify that "Browser Support" AND "Contract Data" are enabled in Settings</li>
<li className="pb1">
If no Browser Support is found in settings, verify that you have{' '}
<a href="https://www.ledgerwallet.com/apps/manager" target="_blank">
Firmware >1.2
</a>
</li>
+ <li>Choose your desired network:</li>
</ol>
+ <div className="pb2">
+ <NetworkDropDown
+ updateSelectedNetwork={this._onSelectedNetworkUpdated.bind(this)}
+ selectedNetworkId={this.state.preferredNetworkId}
+ avialableNetworkIds={networkIds}
+ />
+ </div>
<div className="center pb3">
<LifeCycleRaisedButton
isPrimary={true}
@@ -95,9 +112,9 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
labelComplete="Connected!"
onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)}
/>
- {this.state.didConnectFail && (
+ {!_.isEmpty(this.state.connectionErrMsg) && (
<div className="pt2 left-align" style={{ color: colors.red200 }}>
- Failed to connect. Follow the instructions and try again.
+ {this.state.connectionErrMsg}
</div>
)}
</div>
@@ -172,7 +189,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
}
private _onClose() {
this.setState({
- didConnectFail: false,
+ connectionErrMsg: '',
+ stepIndex: LedgerSteps.CONNECT,
});
const isOpen = false;
this.props.toggleDialogFn(isOpen);
@@ -184,6 +202,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
this.props.dispatcher.updateUserAddress(selectedAddress);
this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress);
+ // tslint:disable-next-line:no-floating-promises
+ this.props.blockchain.fetchTokenInformationAsync();
this.props.dispatcher.updateUserEtherBalance(selectAddressBalance);
this.setState({
stepIndex: LedgerSteps.CONNECT,
@@ -219,7 +239,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
} catch (err) {
utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`);
this.setState({
- didConnectFail: true,
+ connectionErrMsg: 'Failed to connect. Follow the instructions and try again.',
});
return false;
}
@@ -241,6 +261,22 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
});
}
private async _onConnectLedgerClickAsync() {
+ const isU2FSupported = await utils.isU2FSupportedAsync();
+ if (!isU2FSupported) {
+ utils.consoleLog(`U2F not supported in this browser`);
+ this.setState({
+ connectionErrMsg: 'U2F not supported by this browser. Try using Chrome.',
+ });
+ return false;
+ }
+
+ if (
+ this.props.providerType !== ProviderType.Ledger ||
+ (this.props.providerType === ProviderType.Ledger && this.props.networkId !== this.state.preferredNetworkId)
+ ) {
+ await this.props.blockchain.updateProviderToLedgerAsync(this.state.preferredNetworkId);
+ }
+
const didSucceed = await this._fetchAddressesAndBalancesAsync();
if (didSucceed) {
this.setState({
@@ -258,4 +294,9 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
}
return userAddresses;
}
+ private _onSelectedNetworkUpdated(e: any, index: number, networkId: number) {
+ this.setState({
+ preferredNetworkId: networkId,
+ });
+ }
}
diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx
index b3dbce598..d44dd9aab 100644
--- a/packages/website/ts/components/dialogs/send_dialog.tsx
+++ b/packages/website/ts/components/dialogs/send_dialog.tsx
@@ -3,16 +3,20 @@ import * as _ from 'lodash';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import * as React from 'react';
+import { Blockchain } from 'ts/blockchain';
import { AddressInput } from 'ts/components/inputs/address_input';
import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
-import { Token, TokenState } from 'ts/types';
+import { Token } from 'ts/types';
interface SendDialogProps {
+ blockchain: Blockchain;
+ userAddress: string;
+ networkId: number;
onComplete: (recipient: string, value: BigNumber) => void;
onCancelled: () => void;
isOpen: boolean;
token: Token;
- tokenState: TokenState;
+ lastForceTokenStateRefetch: number;
}
interface SendDialogState {
@@ -66,15 +70,18 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState
/>
</div>
<TokenAmountInput
+ blockchain={this.props.blockchain}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
label="Amount to send"
token={this.props.token}
- tokenState={this.props.tokenState}
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
shouldCheckBalance={true}
shouldCheckAllowance={false}
onChange={this._onValueChange.bind(this)}
amount={this.state.value}
onVisitBalancesPageClick={this.props.onCancelled}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
</div>
);
diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx
index 3f29d46f8..bb7e3ed1a 100644
--- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx
+++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx
@@ -82,16 +82,6 @@ export class TrackTokenConfirmationDialog extends React.Component<
newTokenEntry.isTracked = true;
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
-
- const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
- token.address,
- );
- this.props.dispatcher.updateTokenStateByAddress({
- [token.address]: {
- balance,
- allowance,
- },
- });
}
this.setState({
diff --git a/packages/website/ts/components/dropdowns/network_drop_down.tsx b/packages/website/ts/components/dropdowns/network_drop_down.tsx
new file mode 100644
index 000000000..28ec28ed5
--- /dev/null
+++ b/packages/website/ts/components/dropdowns/network_drop_down.tsx
@@ -0,0 +1,40 @@
+import * as _ from 'lodash';
+import DropDownMenu from 'material-ui/DropDownMenu';
+import MenuItem from 'material-ui/MenuItem';
+import * as React from 'react';
+import { constants } from 'ts/utils/constants';
+
+interface NetworkDropDownProps {
+ updateSelectedNetwork: (e: any, index: number, value: number) => void;
+ selectedNetworkId: number;
+ avialableNetworkIds: number[];
+}
+
+interface NetworkDropDownState {}
+
+export class NetworkDropDown extends React.Component<NetworkDropDownProps, NetworkDropDownState> {
+ public render() {
+ return (
+ <div className="mx-auto" style={{ width: 120 }}>
+ <DropDownMenu value={this.props.selectedNetworkId} onChange={this.props.updateSelectedNetwork}>
+ {this._renderDropDownItems()}
+ </DropDownMenu>
+ </div>
+ );
+ }
+ private _renderDropDownItems() {
+ const items = _.map(this.props.avialableNetworkIds, networkId => {
+ const networkName = constants.NETWORK_NAME_BY_ID[networkId];
+ const primaryText = (
+ <div className="flex">
+ <div className="pr1" style={{ width: 14, paddingTop: 2 }}>
+ <img src={`/images/network_icons/${networkName.toLowerCase()}.png`} style={{ width: 14 }} />
+ </div>
+ <div>{networkName}</div>
+ </div>
+ );
+ return <MenuItem key={networkId} value={networkId} primaryText={primaryText} />;
+ });
+ return items;
+ }
+}
diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx
index 300e71f1f..62bafdba7 100644
--- a/packages/website/ts/components/eth_weth_conversion_button.tsx
+++ b/packages/website/ts/components/eth_weth_conversion_button.tsx
@@ -6,21 +6,24 @@ import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
import { EthWethConversionDialog } from 'ts/components/dialogs/eth_weth_conversion_dialog';
import { Dispatcher } from 'ts/redux/dispatcher';
-import { BlockchainCallErrs, Side, Token, TokenState } from 'ts/types';
+import { BlockchainCallErrs, Side, Token } from 'ts/types';
import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
interface EthWethConversionButtonProps {
+ userAddress: string;
+ networkId: number;
direction: Side;
ethToken: Token;
- ethTokenState: TokenState;
dispatcher: Dispatcher;
blockchain: Blockchain;
userEtherBalance: BigNumber;
isOutdatedWrappedEther: boolean;
onConversionSuccessful?: () => void;
isDisabled?: boolean;
+ lastForceTokenStateRefetch: number;
+ refetchEthTokenStateAsync: () => Promise<void>;
}
interface EthWethConversionButtonState {
@@ -64,13 +67,16 @@ export class EthWethConversionButton extends React.Component<
onClick={this._toggleConversionDialog.bind(this)}
/>
<EthWethConversionDialog
+ blockchain={this.props.blockchain}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
direction={this.props.direction}
isOpen={this.state.isEthConversionDialogVisible}
onComplete={this._onConversionAmountSelectedAsync.bind(this)}
onCancelled={this._toggleConversionDialog.bind(this)}
etherBalance={this.props.userEtherBalance}
token={this.props.ethToken}
- tokenState={this.props.ethTokenState}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
</div>
);
@@ -86,29 +92,25 @@ export class EthWethConversionButton extends React.Component<
});
this._toggleConversionDialog();
const token = this.props.ethToken;
- const tokenState = this.props.ethTokenState;
- let balance = tokenState.balance;
try {
if (direction === Side.Deposit) {
await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value);
const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH);
this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
- balance = balance.plus(value);
} else {
await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value);
const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals);
this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
- balance = balance.minus(value);
}
if (!this.props.isOutdatedWrappedEther) {
- this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
+ await this.props.refetchEthTokenStateAsync();
}
this.props.onConversionSuccessful();
} catch (err) {
const errMsg = `${err}`;
if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) {
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- } else if (!_.includes(errMsg, 'User denied transaction')) {
+ } else if (!utils.didUserDenyWeb3Request(errMsg)) {
utils.consoleLog(`Unexpected error encountered: ${err}`);
utils.consoleLog(err.stack);
const errorMsg =
diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx
index d074ec787..c2cdf6751 100644
--- a/packages/website/ts/components/eth_wrappers.tsx
+++ b/packages/website/ts/components/eth_wrappers.tsx
@@ -16,7 +16,6 @@ import {
Token,
TokenByAddress,
TokenState,
- TokenStateByAddress,
} from 'ts/types';
import { colors } from 'ts/utils/colors';
import { configs } from 'ts/utils/configs';
@@ -41,19 +40,23 @@ interface EthWrappersProps {
blockchain: Blockchain;
dispatcher: Dispatcher;
tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
userAddress: string;
userEtherBalance: BigNumber;
+ lastForceTokenStateRefetch: number;
}
interface EthWrappersState {
+ ethTokenState: TokenState;
+ isWethStateLoaded: boolean;
outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded;
outdatedWETHStateByAddress: OutdatedWETHStateByAddress;
}
export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> {
+ private _isUnmounted: boolean;
constructor(props: EthWrappersProps) {
super(props);
+ this._isUnmounted = false;
const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {};
const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {};
@@ -67,18 +70,34 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
this.state = {
outdatedWETHAddressToIsStateLoaded,
outdatedWETHStateByAddress,
+ isWethStateLoaded: false,
+ ethTokenState: {
+ balance: new BigNumber(0),
+ allowance: new BigNumber(0),
+ },
};
}
+ public componentWillReceiveProps(nextProps: EthWrappersProps) {
+ if (
+ nextProps.userAddress !== this.props.userAddress ||
+ nextProps.networkId !== this.props.networkId ||
+ nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch
+ ) {
+ // tslint:disable-next-line:no-floating-promises
+ this._fetchWETHStateAsync();
+ }
+ }
public componentDidMount() {
window.scrollTo(0, 0);
// tslint:disable-next-line:no-floating-promises
- this._fetchOutdatedWETHStateAsync();
+ this._fetchWETHStateAsync();
+ }
+ public componentWillUnmount() {
+ this._isUnmounted = true;
}
public render() {
- const tokens = _.values(this.props.tokenByAddress);
- const etherToken = _.find(tokens, { symbol: 'WETH' });
- const etherTokenState = this.props.tokenStateByAddress[etherToken.address];
- const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH);
+ const etherToken = this._getEthToken();
+ const wethBalance = ZeroEx.toUnitAmount(this.state.ethTokenState.balance, constants.DECIMAL_PLACES_ETH);
const isBidirectional = true;
const etherscanUrl = utils.getEtherScanLinkIfExists(
etherToken.address,
@@ -136,10 +155,13 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
</TableRowColumn>
<TableRowColumn>
<EthWethConversionButton
+ refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
isOutdatedWrappedEther={false}
direction={Side.Deposit}
ethToken={etherToken}
- ethTokenState={etherTokenState}
dispatcher={this.props.dispatcher}
blockchain={this.props.blockchain}
userEtherBalance={this.props.userEtherBalance}
@@ -150,13 +172,23 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
<TableRowColumn className="py1">
{this._renderTokenLink(tokenLabel, etherscanUrl)}
</TableRowColumn>
- <TableRowColumn>{wethBalance.toFixed(PRECISION)} WETH</TableRowColumn>
+ <TableRowColumn>
+ {this.state.isWethStateLoaded ? (
+ `${wethBalance.toFixed(PRECISION)} WETH`
+ ) : (
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ )}
+ </TableRowColumn>
<TableRowColumn>
<EthWethConversionButton
+ refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
isOutdatedWrappedEther={false}
direction={Side.Receive}
+ isDisabled={!this.state.isWethStateLoaded}
ethToken={etherToken}
- ethTokenState={etherTokenState}
dispatcher={this.props.dispatcher}
blockchain={this.props.blockchain}
userEtherBalance={this.props.userEtherBalance}
@@ -190,7 +222,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
</TableRow>
</TableHeader>
<TableBody displayRowCheckbox={false}>
- {this._renderOutdatedWeths(etherToken, etherTokenState)}
+ {this._renderOutdatedWeths(etherToken, this.state.ethTokenState)}
</TableBody>
</Table>
</div>
@@ -269,11 +301,14 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
</TableRowColumn>
<TableRowColumn>
<EthWethConversionButton
+ refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
isDisabled={!isStateLoaded}
isOutdatedWrappedEther={true}
direction={Side.Receive}
ethToken={outdatedEtherToken}
- ethTokenState={outdatedEtherTokenState}
dispatcher={this.props.dispatcher}
blockchain={this.props.blockchain}
userEtherBalance={this.props.userEtherBalance}
@@ -338,7 +373,14 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
},
});
}
- private async _fetchOutdatedWETHStateAsync() {
+ private async _fetchWETHStateAsync() {
+ const tokens = _.values(this.props.tokenByAddress);
+ const wethToken = _.find(tokens, token => token.symbol === 'WETH');
+ const [wethBalance, wethAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
+ this.props.userAddress,
+ wethToken.address,
+ );
+
const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {};
const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {};
@@ -353,10 +395,17 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
};
outdatedWETHAddressToIsStateLoaded[address] = true;
}
- this.setState({
- outdatedWETHStateByAddress,
- outdatedWETHAddressToIsStateLoaded,
- });
+ if (!this._isUnmounted) {
+ this.setState({
+ outdatedWETHStateByAddress,
+ outdatedWETHAddressToIsStateLoaded,
+ ethTokenState: {
+ balance: wethBalance,
+ allowance: wethAllowance,
+ },
+ isWethStateLoaded: true,
+ });
+ }
}
private _getOutdatedWETHAddresses(): string[] {
const outdatedWETHAddresses = _.compact(
@@ -371,4 +420,22 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
);
return outdatedWETHAddresses;
}
+ private _getEthToken() {
+ const tokens = _.values(this.props.tokenByAddress);
+ const etherToken = _.find(tokens, { symbol: 'WETH' });
+ return etherToken;
+ }
+ private async _refetchEthTokenStateAsync() {
+ const etherToken = this._getEthToken();
+ const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
+ this.props.userAddress,
+ etherToken.address,
+ );
+ this.setState({
+ ethTokenState: {
+ balance,
+ allowance,
+ },
+ });
+ }
} // tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx
index 1a150e9ee..d0cfd2cf5 100644
--- a/packages/website/ts/components/fill_order.tsx
+++ b/packages/website/ts/components/fill_order.tsx
@@ -19,7 +19,7 @@ import { VisualOrder } from 'ts/components/visual_order';
import { Dispatcher } from 'ts/redux/dispatcher';
import { orderSchema } from 'ts/schemas/order_schema';
import { SchemaValidator } from 'ts/schemas/validator';
-import { AlertTypes, BlockchainErrs, Order, Token, TokenByAddress, TokenStateByAddress, WebsitePaths } from 'ts/types';
+import { AlertTypes, BlockchainErrs, Order, Token, TokenByAddress, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter';
@@ -33,9 +33,9 @@ interface FillOrderProps {
networkId: number;
userAddress: string;
tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
initialOrder: Order;
dispatcher: Dispatcher;
+ lastForceTokenStateRefetch: number;
}
interface FillOrderState {
@@ -59,8 +59,10 @@ interface FillOrderState {
export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
private _validator: SchemaValidator;
+ private _isUnmounted: boolean;
constructor(props: FillOrderProps) {
super(props);
+ this._isUnmounted = false;
this.state = {
globalErrMsg: '',
didOrderValidationRun: false,
@@ -90,6 +92,9 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
public componentDidMount() {
window.scrollTo(0, 0);
}
+ public componentWillUnmount() {
+ this._isUnmounted = true;
+ }
public render() {
return (
<div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}>
@@ -185,7 +190,6 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
symbol: takerToken.symbol,
};
const fillToken = this.props.tokenByAddress[takerToken.address];
- const fillTokenState = this.props.tokenStateByAddress[takerToken.address];
const makerTokenAddress = this.state.parsedOrder.maker.token.address;
const makerToken = this.props.tokenByAddress[makerTokenAddress];
const makerAssetToken = {
@@ -249,14 +253,17 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
{!isUserMaker && (
<div className="clearfix mx-auto relative" style={{ width: 235, height: 108 }}>
<TokenAmountInput
+ blockchain={this.props.blockchain}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
label="Fill amount"
onChange={this._onFillAmountChange.bind(this)}
shouldShowIncompleteErrs={false}
token={fillToken}
- tokenState={fillTokenState}
amount={fillAssetToken.amount}
shouldCheckBalance={true}
shouldCheckAllowance={true}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
<div
className="absolute sm-hide xs-hide"
@@ -454,12 +461,14 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
if (!_.isEmpty(orderJSON)) {
orderJSONErrMsg = 'Submitted order JSON is not valid JSON';
}
- this.setState({
- didOrderValidationRun: true,
- orderJSON,
- orderJSONErrMsg,
- parsedOrder,
- });
+ if (!this._isUnmounted) {
+ this.setState({
+ didOrderValidationRun: true,
+ orderJSON,
+ orderJSONErrMsg,
+ parsedOrder,
+ });
+ }
return;
}
@@ -556,11 +565,8 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
signedOrder,
this.props.orderFillAmount,
);
- // After fill completes, let's update the token balances
- const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address];
- const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address];
- const tokens = [makerToken, takerToken];
- await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
+ // After fill completes, let's force fetch the token balances
+ this.props.dispatcher.forceTokenStateRefetch();
this.setState({
isFilling: false,
didFillOrderSucceed: true,
@@ -573,7 +579,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
isFilling: false,
});
const errMsg = `${err}`;
- if (_.includes(errMsg, 'User denied transaction signature')) {
+ if (utils.didUserDenyWeb3Request(errMsg)) {
return;
}
globalErrMsg = 'Failed to fill order, please refresh and try again';
@@ -653,7 +659,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
isCancelling: false,
});
const errMsg = `${err}`;
- if (_.includes(errMsg, 'User denied transaction signature')) {
+ if (utils.didUserDenyWeb3Request(errMsg)) {
return;
}
globalErrMsg = 'Failed to cancel order, please refresh and try again';
diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx
index df7d87cfd..69fb32a21 100644
--- a/packages/website/ts/components/generate_order/asset_picker.tsx
+++ b/packages/website/ts/components/generate_order/asset_picker.tsx
@@ -8,7 +8,7 @@ import { TrackTokenConfirmation } from 'ts/components/track_token_confirmation';
import { TokenIcon } from 'ts/components/ui/token_icon';
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
import { Dispatcher } from 'ts/redux/dispatcher';
-import { DialogConfigs, Token, TokenByAddress, TokenState, TokenVisibility } from 'ts/types';
+import { DialogConfigs, Token, TokenByAddress, TokenVisibility } from 'ts/types';
const TOKEN_ICON_DIMENSION = 100;
const TILE_DIMENSION = 146;
@@ -223,10 +223,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
assetView: AssetViews.NEW_TOKEN_FORM,
});
}
- private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) {
- this.props.dispatcher.updateTokenStateByAddress({
- [newToken.address]: newTokenState,
- });
+ private _onNewTokenSubmitted(newToken: Token) {
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken);
this.props.dispatcher.addTokenToTokenByAddress(newToken);
this.setState({
@@ -256,15 +253,6 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
newTokenEntry.isTracked = true;
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
- const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
- token.address,
- );
- this.props.dispatcher.updateTokenStateByAddress({
- [token.address]: {
- balance,
- allowance,
- },
- });
this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
this.setState({
isAddingTokenToTracked: false,
diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx
index 3ae0d48a7..df1241d8d 100644
--- a/packages/website/ts/components/generate_order/generate_order_form.tsx
+++ b/packages/website/ts/components/generate_order/generate_order_form.tsx
@@ -27,7 +27,6 @@ import {
SignatureData,
Token,
TokenByAddress,
- TokenStateByAddress,
} from 'ts/types';
import { colors } from 'ts/utils/colors';
import { errorReporter } from 'ts/utils/error_reporter';
@@ -53,7 +52,7 @@ interface GenerateOrderFormProps {
orderSalt: BigNumber;
sideToAssetToken: SideToAssetToken;
tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
+ lastForceTokenStateRefetch: number;
}
interface GenerateOrderFormState {
@@ -80,10 +79,8 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
const dispatcher = this.props.dispatcher;
const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address;
const depositToken = this.props.tokenByAddress[depositTokenAddress];
- const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress];
const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address;
const receiveToken = this.props.tokenByAddress[receiveTokenAddress];
- const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress];
const takerExplanation =
'If a taker is specified, only they are<br> \
allowed to fill this order. If no taker is<br> \
@@ -110,9 +107,12 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
tokenByAddress={this.props.tokenByAddress}
/>
<TokenAmountInput
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ blockchain={this.props.blockchain}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
label="Sell amount"
token={depositToken}
- tokenState={depositTokenState}
amount={this.props.sideToAssetToken[Side.Deposit].amount}
onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)}
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
@@ -139,9 +139,12 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
tokenByAddress={this.props.tokenByAddress}
/>
<TokenAmountInput
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ blockchain={this.props.blockchain}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
label="Receive amount"
token={receiveToken}
- tokenState={receiveTokenState}
amount={this.props.sideToAssetToken[Side.Receive].amount}
onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)}
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
@@ -242,8 +245,10 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G
// Check if all required inputs were supplied
const debitToken = this.props.sideToAssetToken[Side.Deposit];
- const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance;
- const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance;
+ const [debitBalance, debitAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
+ this.props.userAddress,
+ debitToken.address,
+ );
const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount;
if (
!_.isUndefined(debitToken.amount) &&
diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx
index 63645be9a..f76830a49 100644
--- a/packages/website/ts/components/generate_order/new_token_form.tsx
+++ b/packages/website/ts/components/generate_order/new_token_form.tsx
@@ -1,4 +1,3 @@
-import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import TextField from 'material-ui/TextField';
import * as React from 'react';
@@ -7,13 +6,13 @@ import { AddressInput } from 'ts/components/inputs/address_input';
import { Alert } from 'ts/components/ui/alert';
import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button';
import { RequiredLabel } from 'ts/components/ui/required_label';
-import { AlertTypes, Token, TokenByAddress, TokenState } from 'ts/types';
+import { AlertTypes, Token, TokenByAddress } from 'ts/types';
import { colors } from 'ts/utils/colors';
interface NewTokenFormProps {
blockchain: Blockchain;
tokenByAddress: TokenByAddress;
- onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void;
+ onNewTokenSubmitted: (token: Token) => void;
}
interface NewTokenFormState {
@@ -110,13 +109,9 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
}
let hasBalanceAllowanceErr = false;
- let balance = new BigNumber(0);
- let allowance = new BigNumber(0);
if (doesContractExist) {
try {
- [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
- this.state.address,
- );
+ await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(this.state.address);
} catch (err) {
hasBalanceAllowanceErr = true;
}
@@ -155,11 +150,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
isTracked: true,
isRegistered: false,
};
- const newTokenState: TokenState = {
- balance,
- allowance,
- };
- this.props.onNewTokenSubmitted(newToken, newTokenState);
+ this.props.onNewTokenSubmitted(newToken);
}
private _onTokenNameChanged(e: any, name: string) {
let nameErrText = '';
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
index da46db4f4..45531e74b 100644
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -17,6 +17,8 @@ interface AllowanceToggleProps {
token: Token;
tokenState: TokenState;
userAddress: string;
+ isDisabled: boolean;
+ refetchTokenStateAsync: () => Promise<void>;
}
interface AllowanceToggleState {
@@ -45,7 +47,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
<div className="flex">
<div>
<Toggle
- disabled={this.state.isSpinnerVisible}
+ disabled={this.state.isSpinnerVisible || this.props.isDisabled}
toggled={this._isAllowanceSet()}
onToggle={this._onToggleAllowanceAsync.bind(this)}
/>
@@ -73,12 +75,13 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
}
try {
await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
+ await this.props.refetchTokenStateAsync();
} catch (err) {
this.setState({
isSpinnerVisible: false,
});
const errMsg = `${err}`;
- if (_.includes(errMsg, 'User denied transaction')) {
+ if (utils.didUserDenyWeb3Request(errMsg)) {
return;
}
utils.consoleLog(`Unexpected error encountered: ${err}`);
diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx
index ddc434b51..3bbc7a5f6 100644
--- a/packages/website/ts/components/inputs/balance_bounded_input.tsx
+++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx
@@ -18,6 +18,7 @@ interface BalanceBoundedInputProps {
validate?: (amount: BigNumber) => InputErrMsg;
onVisitBalancesPageClick?: () => void;
shouldHideVisitBalancesLink?: boolean;
+ isDisabled?: boolean;
}
interface BalanceBoundedInputState {
@@ -29,6 +30,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
public static defaultProps: Partial<BalanceBoundedInputProps> = {
shouldShowIncompleteErrs: false,
shouldHideVisitBalancesLink: false,
+ isDisabled: false,
};
constructor(props: BalanceBoundedInputProps) {
super(props);
@@ -88,6 +90,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>}
onChange={this._onValueChange.bind(this)}
underlineStyle={{ width: 'calc(100% + 50px)' }}
+ disabled={this.props.isDisabled}
/>
);
}
@@ -100,7 +103,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
},
() => {
const isValid = _.isUndefined(errMsg);
- if (utils.isNumeric(amountString)) {
+ if (utils.isNumeric(amountString) && !_.includes(amountString, '-')) {
this.props.onChange(isValid, new BigNumber(amountString));
} else {
this.props.onChange(isValid);
diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx
index 63966d759..2b167d875 100644
--- a/packages/website/ts/components/inputs/token_amount_input.tsx
+++ b/packages/website/ts/components/inputs/token_amount_input.tsx
@@ -3,13 +3,16 @@ import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import * as React from 'react';
import { Link } from 'react-router-dom';
+import { Blockchain } from 'ts/blockchain';
import { BalanceBoundedInput } from 'ts/components/inputs/balance_bounded_input';
-import { InputErrMsg, Token, TokenState, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
+import { InputErrMsg, Token, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
interface TokenAmountInputProps {
+ userAddress: string;
+ networkId: number;
+ blockchain: Blockchain;
token: Token;
- tokenState: TokenState;
label?: string;
amount?: BigNumber;
shouldShowIncompleteErrs: boolean;
@@ -17,11 +20,45 @@ interface TokenAmountInputProps {
shouldCheckAllowance: boolean;
onChange: ValidatedBigNumberCallback;
onVisitBalancesPageClick?: () => void;
+ lastForceTokenStateRefetch: number;
}
-interface TokenAmountInputState {}
+interface TokenAmountInputState {
+ balance: BigNumber;
+ allowance: BigNumber;
+ isBalanceAndAllowanceLoaded: boolean;
+}
export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> {
+ private _isUnmounted: boolean;
+ constructor(props: TokenAmountInputProps) {
+ super(props);
+ this._isUnmounted = false;
+ const defaultAmount = new BigNumber(0);
+ this.state = {
+ balance: defaultAmount,
+ allowance: defaultAmount,
+ isBalanceAndAllowanceLoaded: false,
+ };
+ }
+ public componentWillMount() {
+ // tslint:disable-next-line:no-floating-promises
+ this._fetchBalanceAndAllowanceAsync(this.props.token.address, this.props.userAddress);
+ }
+ public componentWillUnmount() {
+ this._isUnmounted = true;
+ }
+ public componentWillReceiveProps(nextProps: TokenAmountInputProps) {
+ if (
+ nextProps.userAddress !== this.props.userAddress ||
+ nextProps.networkId !== this.props.networkId ||
+ nextProps.token.address !== this.props.token.address ||
+ nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch
+ ) {
+ // tslint:disable-next-line:no-floating-promises
+ this._fetchBalanceAndAllowanceAsync(nextProps.token.address, nextProps.userAddress);
+ }
+ }
public render() {
const amount = this.props.amount
? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals)
@@ -32,12 +69,13 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
<BalanceBoundedInput
label={this.props.label}
amount={amount}
- balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)}
+ balance={ZeroEx.toUnitAmount(this.state.balance, this.props.token.decimals)}
onChange={this._onChange.bind(this)}
validate={this._validate.bind(this)}
shouldCheckBalance={this.props.shouldCheckBalance}
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
+ isDisabled={!this.state.isBalanceAndAllowanceLoaded}
/>
<div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div>
</div>
@@ -51,7 +89,7 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
this.props.onChange(isValid, baseUnitAmount);
}
private _validate(amount: BigNumber): InputErrMsg {
- if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) {
+ if (this.props.shouldCheckAllowance && amount.gt(this.state.allowance)) {
return (
<span>
Insufficient allowance.{' '}
@@ -67,4 +105,20 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
return undefined;
}
}
+ private async _fetchBalanceAndAllowanceAsync(tokenAddress: string, userAddress: string) {
+ this.setState({
+ isBalanceAndAllowanceLoaded: false,
+ });
+ const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
+ userAddress,
+ tokenAddress,
+ );
+ if (!this._isUnmounted) {
+ this.setState({
+ balance,
+ allowance,
+ isBalanceAndAllowanceLoaded: true,
+ });
+ }
+ }
}
diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx
index e2e28e8b6..92589f75c 100644
--- a/packages/website/ts/components/portal.tsx
+++ b/packages/website/ts/components/portal.tsx
@@ -1,11 +1,13 @@
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
+import CircularProgress from 'material-ui/CircularProgress';
import Paper from 'material-ui/Paper';
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { Route, Switch } from 'react-router-dom';
import { Blockchain } from 'ts/blockchain';
import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
+import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog';
import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog';
import { WrappedEthSectionNoticeDialog } from 'ts/components/dialogs/wrapped_eth_section_notice_dialog';
import { EthWrappers } from 'ts/components/eth_wrappers';
@@ -13,25 +15,15 @@ import { FillOrder } from 'ts/components/fill_order';
import { Footer } from 'ts/components/footer';
import { PortalMenu } from 'ts/components/portal_menu';
import { TokenBalances } from 'ts/components/token_balances';
-import { TopBar } from 'ts/components/top_bar';
+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 { Loading } from 'ts/components/ui/loading';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
import { localStorage } from 'ts/local_storage/local_storage';
import { Dispatcher } from 'ts/redux/dispatcher';
import { orderSchema } from 'ts/schemas/order_schema';
import { SchemaValidator } from 'ts/schemas/validator';
-import {
- BlockchainErrs,
- HashData,
- Order,
- ScreenWidths,
- Token,
- TokenByAddress,
- TokenStateByAddress,
- WebsitePaths,
-} from 'ts/types';
+import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
@@ -46,18 +38,20 @@ export interface PortalAllProps {
blockchainIsLoaded: boolean;
dispatcher: Dispatcher;
hashData: HashData;
+ injectedProviderName: string;
networkId: number;
nodeVersion: string;
orderFillAmount: BigNumber;
+ providerType: ProviderType;
screenWidth: ScreenWidths;
tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
userEtherBalance: BigNumber;
userAddress: string;
shouldBlockchainErrDialogBeOpen: boolean;
userSuppliedOrderCache: Order;
location: Location;
flashMessage?: string | React.ReactNode;
+ lastForceTokenStateRefetch: number;
}
interface PortalAllState {
@@ -67,6 +61,7 @@ interface PortalAllState {
prevPathname: string;
isDisclaimerDialogOpen: boolean;
isWethNoticeDialogOpen: boolean;
+ isLedgerDialogOpen: boolean;
}
export class Portal extends React.Component<PortalAllProps, PortalAllState> {
@@ -96,6 +91,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
prevPathname: this.props.location.pathname,
isDisclaimerDialogOpen: !hasAcceptedDisclaimer,
isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances,
+ isLedgerDialogOpen: false,
};
}
public componentDidMount() {
@@ -125,11 +121,6 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
if (nextProps.userAddress !== this.state.prevUserAddress) {
// tslint:disable-next-line:no-floating-promises
this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress);
- if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) {
- const tokens = _.values(nextProps.tokenByAddress);
- // tslint:disable-next-line:no-floating-promises
- this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens);
- }
this.setState({
prevUserAddress: nextProps.userAddress,
});
@@ -167,8 +158,14 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
<DocumentTitle title="0x Portal DApp" />
<TopBar
userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ injectedProviderName={this.props.injectedProviderName}
+ onToggleLedgerDialog={this.onToggleLedgerDialog.bind(this)}
+ dispatcher={this.props.dispatcher}
+ providerType={this.props.providerType}
blockchainIsLoaded={this.props.blockchainIsLoaded}
location={this.props.location}
+ blockchain={this._blockchain}
/>
<div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}>
<Paper className="mb3 mt2">
@@ -215,7 +212,19 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
/>
</Switch>
) : (
- <Loading />
+ <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}>
+ <div
+ className="relative sm-px2 sm-pt2 sm-m1"
+ style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }}
+ >
+ <div className="center pb2">
+ <CircularProgress size={40} thickness={5} />
+ </div>
+ <div className="center pt2" style={{ paddingBottom: 11 }}>
+ Loading Portal...
+ </div>
+ </div>
+ </div>
)}
</div>
</div>
@@ -239,11 +248,26 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)}
/>
<FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
+ {this.props.blockchainIsLoaded && (
+ <LedgerConfigDialog
+ providerType={this.props.providerType}
+ networkId={this.props.networkId}
+ blockchain={this._blockchain}
+ dispatcher={this.props.dispatcher}
+ toggleDialogFn={this.onToggleLedgerDialog.bind(this)}
+ isOpen={this.state.isLedgerDialogOpen}
+ />
+ )}
</div>
- <Footer />
+ <Footer />;
</div>
);
}
+ public onToggleLedgerDialog() {
+ this.setState({
+ isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
+ });
+ }
private _renderEthWrapper() {
return (
<EthWrappers
@@ -251,9 +275,9 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
blockchain={this._blockchain}
dispatcher={this.props.dispatcher}
tokenByAddress={this.props.tokenByAddress}
- tokenStateByAddress={this.props.tokenStateByAddress}
userAddress={this.props.userAddress}
userEtherBalance={this.props.userEtherBalance}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
);
}
@@ -267,6 +291,8 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
);
}
private _renderTokenBalances() {
+ const allTokens = _.values(this.props.tokenByAddress);
+ const trackedTokens = _.filter(allTokens, t => t.isTracked);
return (
<TokenBalances
blockchain={this._blockchain}
@@ -275,10 +301,11 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
dispatcher={this.props.dispatcher}
screenWidth={this.props.screenWidth}
tokenByAddress={this.props.tokenByAddress}
- tokenStateByAddress={this.props.tokenStateByAddress}
+ trackedTokens={trackedTokens}
userAddress={this.props.userAddress}
userEtherBalance={this.props.userEtherBalance}
networkId={this.props.networkId}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
);
}
@@ -296,8 +323,8 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
networkId={this.props.networkId}
userAddress={this.props.userAddress}
tokenByAddress={this.props.tokenByAddress}
- tokenStateByAddress={this.props.tokenStateByAddress}
dispatcher={this.props.dispatcher}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
);
}
@@ -353,9 +380,4 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
const newScreenWidth = utils.getScreenWidth();
this.props.dispatcher.updateScreenWidth(newScreenWidth);
}
- private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) {
- this.props.dispatcher.updateBlockchainIsLoaded(false);
- await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
- this.props.dispatcher.updateBlockchainIsLoaded(true);
- }
}
diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx
index f94ec346a..ffa165f60 100644
--- a/packages/website/ts/components/send_button.tsx
+++ b/packages/website/ts/components/send_button.tsx
@@ -5,16 +5,19 @@ import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
import { SendDialog } from 'ts/components/dialogs/send_dialog';
import { Dispatcher } from 'ts/redux/dispatcher';
-import { BlockchainCallErrs, Token, TokenState } from 'ts/types';
+import { BlockchainCallErrs, Token } from 'ts/types';
import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
interface SendButtonProps {
+ userAddress: string;
+ networkId: number;
token: Token;
- tokenState: TokenState;
dispatcher: Dispatcher;
blockchain: Blockchain;
onError: () => void;
+ lastForceTokenStateRefetch: number;
+ refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
}
interface SendButtonState {
@@ -42,11 +45,14 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState
onClick={this._toggleSendDialog.bind(this)}
/>
<SendDialog
+ blockchain={this.props.blockchain}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
isOpen={this.state.isSendDialogVisible}
onComplete={this._onSendAmountSelectedAsync.bind(this)}
onCancelled={this._toggleSendDialog.bind(this)}
token={this.props.token}
- tokenState={this.props.tokenState}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
</div>
);
@@ -62,18 +68,15 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState
});
this._toggleSendDialog();
const token = this.props.token;
- const tokenState = this.props.tokenState;
- let balance = tokenState.balance;
try {
await this.props.blockchain.transferAsync(token, recipient, value);
- balance = balance.minus(value);
- this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
+ await this.props.refetchTokenStateAsync(token.address);
} catch (err) {
const errMsg = `${err}`;
if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) {
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
return;
- } else if (!_.includes(errMsg, 'User denied transaction')) {
+ } else if (!utils.didUserDenyWeb3Request(errMsg)) {
utils.consoleLog(`Unexpected error encountered: ${err}`);
utils.consoleLog(err.stack);
this.props.onError();
diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx
index 2cef413c7..c6a9a46be 100644
--- a/packages/website/ts/components/token_balances.tsx
+++ b/packages/website/ts/components/token_balances.tsx
@@ -27,11 +27,11 @@ import {
BlockchainCallErrs,
BlockchainErrs,
EtherscanLinkSuffixes,
+ Networks,
ScreenWidths,
Styles,
Token,
TokenByAddress,
- TokenStateByAddress,
TokenVisibility,
} from 'ts/types';
import { colors } from 'ts/utils/colors';
@@ -58,6 +58,14 @@ const styles: Styles = {
},
};
+interface TokenStateByAddress {
+ [address: string]: {
+ balance: BigNumber;
+ allowance: BigNumber;
+ isLoaded: boolean;
+ };
+}
+
interface TokenBalancesProps {
blockchain: Blockchain;
blockchainErr: BlockchainErrs;
@@ -65,10 +73,11 @@ interface TokenBalancesProps {
dispatcher: Dispatcher;
screenWidth: ScreenWidths;
tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
+ trackedTokens: Token[];
userAddress: string;
userEtherBalance: BigNumber;
networkId: number;
+ lastForceTokenStateRefetch: number;
}
interface TokenBalancesState {
@@ -76,14 +85,17 @@ interface TokenBalancesState {
isBalanceSpinnerVisible: boolean;
isDharmaDialogVisible: boolean;
isZRXSpinnerVisible: boolean;
- currentZrxBalance?: BigNumber;
isTokenPickerOpen: boolean;
isAddingToken: boolean;
+ trackedTokenStateByAddress: TokenStateByAddress;
}
export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> {
+ private _isUnmounted: boolean;
public constructor(props: TokenBalancesProps) {
super(props);
+ this._isUnmounted = false;
+ const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens);
this.state = {
errorType: undefined,
isBalanceSpinnerVisible: false,
@@ -91,8 +103,17 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(),
isTokenPickerOpen: false,
isAddingToken: false,
+ 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: TokenBalancesProps) {
if (nextProps.userEtherBalance !== this.props.userEtherBalance) {
if (this.state.isBalanceSpinnerVisible) {
@@ -103,18 +124,36 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
isBalanceSpinnerVisible: false,
});
}
- const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL);
- const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance;
- if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) {
- if (this.state.isZRXSpinnerVisible) {
- const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance);
- const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX);
- this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`);
+
+ 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;
+ for (const tokenAddress of newTokenAddresses) {
+ trackedTokenStateByAddress[tokenAddress] = {
+ balance: new BigNumber(0),
+ allowance: new BigNumber(0),
+ isLoaded: false,
+ };
}
this.setState({
- isZRXSpinnerVisible: false,
- currentZrxBalance: undefined,
+ trackedTokenStateByAddress,
});
+ // Fetch the actual balance/allowance.
+ // tslint:disable-next-line:no-floating-promises
+ this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
}
}
public componentDidMount() {
@@ -137,13 +176,13 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
onTouchTap={this._onDharmaDialogToggle.bind(this, false)}
/>,
];
- const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET;
+ const isKovanTestNetwork = this.props.networkId === constants.NETWORK_ID_KOVAN;
const dharmaButtonColumnStyle = {
paddingLeft: 3,
- display: isTestNetwork ? 'table-cell' : 'none',
+ display: isKovanTestNetwork ? 'table-cell' : 'none',
};
const stubColumnStyle = {
- display: isTestNetwork ? 'none' : 'table-cell',
+ display: isKovanTestNetwork ? 'none' : 'table-cell',
};
const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT;
const tokenTableHeight =
@@ -162,10 +201,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
smart contract so you can start trading that token.';
return (
<div className="lg-px4 md-px4 sm-px1 pb2">
- <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3>
+ <h3>{isKovanTestNetwork ? 'Test ether' : 'Ether'}</h3>
<Divider />
<div className="pt2 pb2">
- {isTestNetwork
+ {isKovanTestNetwork
? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \
gas costs. It might take a bit of time for the test ether to show up.'
: 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \
@@ -177,12 +216,12 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
<TableHeaderColumn>Currency</TableHeaderColumn>
<TableHeaderColumn>Balance</TableHeaderColumn>
<TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} />
- {isTestNetwork && (
+ {isKovanTestNetwork && (
<TableHeaderColumn style={{ paddingLeft: 3 }}>
{isSmallScreen ? 'Faucet' : 'Request from faucet'}
</TableHeaderColumn>
)}
- {isTestNetwork && (
+ {isKovanTestNetwork && (
<TableHeaderColumn style={dharmaButtonColumnStyle}>
{isSmallScreen ? 'Loan' : 'Request Dharma loan'}
<HelpTooltip style={{ paddingLeft: 4 }} explanation={dharmaLoanExplanation} />
@@ -204,7 +243,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
)}
</TableRowColumn>
<TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} />
- {isTestNetwork && (
+ {isKovanTestNetwork && (
<TableRowColumn style={{ paddingLeft: 3 }}>
<LifeCycleRaisedButton
labelReady="Request"
@@ -214,7 +253,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
/>
</TableRowColumn>
)}
- {isTestNetwork && (
+ {isKovanTestNetwork && (
<TableRowColumn style={dharmaButtonColumnStyle}>
<RaisedButton
label="Request"
@@ -228,7 +267,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
</Table>
<div className="clearfix" style={{ paddingBottom: 1 }}>
<div className="col col-10">
- <h3 className="pt2">{isTestNetwork ? 'Test tokens' : 'Tokens'}</h3>
+ <h3 className="pt2">{isKovanTestNetwork ? 'Test tokens' : 'Tokens'}</h3>
</div>
<div className="col col-1 pt3 align-right">
<FloatingActionButton mini={true} zDepth={0} onClick={this._onAddTokenClicked.bind(this)}>
@@ -243,7 +282,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
</div>
<Divider />
<div className="pt2 pb2">
- {isTestNetwork
+ {isKovanTestNetwork
? "Mint some test tokens you'd like to use to generate or fill an order using 0x."
: "Set trading permissions for a token you'd like to start trading."}
</div>
@@ -303,8 +342,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm;
const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG;
const actionPaddingX = isSmallScreen ? 2 : 24;
- const allTokens = _.values(this.props.tokenByAddress);
- const trackedTokens = _.filter(allTokens, t => t.isTracked);
+ const trackedTokens = this.props.trackedTokens;
const trackedTokensStartingWithEtherToken = trackedTokens.sort(
firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL)
.thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL)
@@ -317,7 +355,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
return tableRows;
}
private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) {
- const tokenState = this.props.tokenStateByAddress[token.address];
+ const tokenState = this.state.trackedTokenStateByAddress[token.address];
const tokenLink = utils.getEtherScanLinkIfExists(
token.address,
this.props.networkId,
@@ -338,13 +376,19 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
)}
</TableRowColumn>
<TableRowColumn style={{ paddingRight: 3, paddingLeft: 3 }}>
- {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol}
- {this.state.isZRXSpinnerVisible &&
- token.symbol === ZRX_TOKEN_SYMBOL && (
- <span className="pl1">
- <i className="zmdi zmdi-spinner zmdi-hc-spin" />
- </span>
- )}
+ {tokenState.isLoaded ? (
+ <span>
+ {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol}
+ {this.state.isZRXSpinnerVisible &&
+ token.symbol === ZRX_TOKEN_SYMBOL && (
+ <span className="pl1">
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </span>
+ )}
+ </span>
+ ) : (
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ )}
</TableRowColumn>
<TableRowColumn>
<AllowanceToggle
@@ -354,6 +398,8 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
tokenState={tokenState}
onErrorOccurred={this._onErrorOccurred.bind(this)}
userAddress={this.props.userAddress}
+ isDisabled={!tokenState.isLoaded}
+ refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)}
/>
</TableRowColumn>
<TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}>
@@ -366,7 +412,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
/>
)}
{token.symbol === ZRX_TOKEN_SYMBOL &&
- this.props.networkId === constants.NETWORK_ID_TESTNET && (
+ this.props.networkId === constants.NETWORK_ID_KOVAN && (
<LifeCycleRaisedButton
labelReady="Request"
labelLoading="Sending..."
@@ -383,11 +429,14 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
}}
>
<SendButton
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
blockchain={this.props.blockchain}
dispatcher={this.props.dispatcher}
token={token}
- tokenState={tokenState}
onError={this._onSendFailed.bind(this)}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)}
/>
</TableRowColumn>
)}
@@ -414,7 +463,6 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
} else {
this.props.dispatcher.removeTokenToTokenByAddress(token);
}
- this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress);
trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress);
} else if (isDefaultTrackedToken) {
this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`);
@@ -449,9 +497,9 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
case BalanceErrs.incorrectNetworkForFaucet:
return (
<div>
- Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} testnet
- (networkId {constants.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '}
- {constants.TESTNET_NAME} testnet and try requesting ether again.
+ Our faucet can only send test Ether to addresses on the {Networks.Kovan} testnet (networkId{' '}
+ {constants.NETWORK_ID_KOVAN}). Please make sure you are connected to the {Networks.Kovan}{' '}
+ testnet and try requesting ether again.
</div>
);
@@ -510,6 +558,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
private async _onMintTestTokensAsync(token: Token): Promise<boolean> {
try {
await this.props.blockchain.mintTestTokensAsync(token);
+ await this._refetchTokenStateAsync(token.address);
const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals);
this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`);
return true;
@@ -519,7 +568,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
return false;
}
- if (_.includes(errMsg, 'User denied transaction')) {
+ if (utils.didUserDenyWeb3Request(errMsg)) {
return false;
}
utils.consoleLog(`Unexpected error encountered: ${err}`);
@@ -539,7 +588,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
// If on another network other then the testnet our faucet serves test ether
// from, we must show user an error message
- if (this.props.blockchain.networkId !== constants.NETWORK_ID_TESTNET) {
+ if (this.props.blockchain.networkId !== constants.NETWORK_ID_KOVAN) {
this.setState({
errorType: BalanceErrs.incorrectNetworkForFaucet,
});
@@ -569,15 +618,11 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
isBalanceSpinnerVisible: true,
});
} else {
- const tokens = _.values(this.props.tokenByAddress);
- const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL);
- const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address];
this.setState({
isZRXSpinnerVisible: true,
- currentZrxBalance: zrxTokenState.balance,
});
// tslint:disable-next-line:no-floating-promises
- this.props.blockchain.pollTokenBalanceAsync(zrxToken);
+ this._startPollingZrxBalanceAsync();
}
return true;
}
@@ -603,4 +648,65 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
isAddingToken: false,
});
}
+ private async _startPollingZrxBalanceAsync() {
+ const tokens = _.values(this.props.tokenByAddress);
+ const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL);
+
+ // tslint:disable-next-line:no-floating-promises
+ const balance = await this.props.blockchain.pollTokenBalanceAsync(zrxToken);
+ const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
+ trackedTokenStateByAddress[zrxToken.address] = {
+ ...trackedTokenStateByAddress[zrxToken.address],
+ balance,
+ };
+ this.setState({
+ isZRXSpinnerVisible: false,
+ });
+ }
+ private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) {
+ const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
+ for (const tokenAddress of tokenAddresses) {
+ const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
+ this.props.userAddress,
+ tokenAddress,
+ );
+ trackedTokenStateByAddress[tokenAddress] = {
+ balance,
+ allowance,
+ isLoaded: true,
+ };
+ }
+ if (!this._isUnmounted) {
+ this.setState({
+ trackedTokenStateByAddress,
+ });
+ }
+ }
+ 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 _refetchTokenStateAsync(tokenAddress: string) {
+ const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
+ this.props.userAddress,
+ tokenAddress,
+ );
+ this.setState({
+ trackedTokenStateByAddress: {
+ ...this.state.trackedTokenStateByAddress,
+ [tokenAddress]: {
+ balance,
+ allowance,
+ isLoaded: true,
+ },
+ },
+ });
+ }
} // tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx
new file mode 100644
index 000000000..39e7f2a8c
--- /dev/null
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -0,0 +1,148 @@
+import * as _ from 'lodash';
+import RaisedButton from 'material-ui/RaisedButton';
+import * as React from 'react';
+import { Blockchain } from 'ts/blockchain';
+import { ProviderPicker } from 'ts/components/top_bar/provider_picker';
+import { DropDown } from 'ts/components/ui/drop_down';
+import { Identicon } from 'ts/components/ui/identicon';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { ProviderType } from 'ts/types';
+import { colors } from 'ts/utils/colors';
+import { constants } from 'ts/utils/constants';
+import { utils } from 'ts/utils/utils';
+
+const IDENTICON_DIAMETER = 32;
+
+interface ProviderDisplayProps {
+ dispatcher: Dispatcher;
+ userAddress: string;
+ networkId: number;
+ injectedProviderName: string;
+ providerType: ProviderType;
+ onToggleLedgerDialog: () => void;
+ blockchain: Blockchain;
+}
+
+interface ProviderDisplayState {}
+
+export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
+ public render() {
+ const isAddressAvailable = !_.isEmpty(this.props.userAddress);
+ const isExternallyInjectedProvider = ProviderType.Injected && this.props.injectedProviderName !== '0x Public';
+ const displayAddress = isAddressAvailable
+ ? utils.getAddressBeginAndEnd(this.props.userAddress)
+ : isExternallyInjectedProvider ? 'Account locked' : '0x0000...0000';
+ // If the "injected" provider is our fallback public node, then we want to
+ // show the "connect a wallet" message instead of the providerName
+ const injectedProviderName = isExternallyInjectedProvider
+ ? this.props.injectedProviderName
+ : 'Connect a wallet';
+ const providerTitle =
+ this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
+ const hoverActiveNode = (
+ <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}>
+ <div>
+ <Identicon address={this.props.userAddress} diameter={IDENTICON_DIAMETER} />
+ </div>
+ <div style={{ marginLeft: 12, paddingTop: 1 }}>
+ <div style={{ fontSize: 12, color: colors.amber800 }}>{providerTitle}</div>
+ <div style={{ fontSize: 14 }}>{displayAddress}</div>
+ </div>
+ <div
+ style={{ borderLeft: `1px solid ${colors.grey300}`, marginLeft: 17, paddingTop: 1 }}
+ className="px2"
+ >
+ <i style={{ fontSize: 30, color: colors.grey300 }} className="zmdi zmdi zmdi-chevron-down" />
+ </div>
+ </div>
+ );
+ const hasInjectedProvider =
+ this.props.injectedProviderName !== '0x Public' && this.props.providerType === ProviderType.Injected;
+ const hasLedgerProvider = this.props.providerType === ProviderType.Ledger;
+ const horizontalPosition = hasInjectedProvider || hasLedgerProvider ? 'left' : 'middle';
+ return (
+ <div style={{ width: 'fit-content', height: 48, float: 'right' }}>
+ <DropDown
+ hoverActiveNode={hoverActiveNode}
+ popoverContent={this.renderPopoverContent(hasInjectedProvider, hasLedgerProvider)}
+ anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }}
+ targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }}
+ zDepth={1}
+ />
+ </div>
+ );
+ }
+ public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean) {
+ if (hasInjectedProvider || hasLedgerProvider) {
+ return (
+ <ProviderPicker
+ dispatcher={this.props.dispatcher}
+ networkId={this.props.networkId}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ blockchain={this.props.blockchain}
+ />
+ );
+ } else {
+ // Nothing to connect to, show install/info popover
+ return (
+ <div className="px2" style={{ maxWidth: 420 }}>
+ <div className="center h4 py2" style={{ color: colors.grey700 }}>
+ Choose a wallet:
+ </div>
+ <div className="flex pb3">
+ <div className="center px2">
+ <div style={{ color: colors.darkGrey }}>Install a browser wallet</div>
+ <div className="py2">
+ <img src="/images/metamask_or_parity.png" width="135" />
+ </div>
+ <div>
+ Use{' '}
+ <a
+ href={constants.URL_METAMASK_CHROME_STORE}
+ target="_blank"
+ style={{ color: colors.lightBlueA700 }}
+ >
+ Metamask
+ </a>{' '}
+ or{' '}
+ <a
+ href={constants.URL_PARITY_CHROME_STORE}
+ target="_blank"
+ style={{ color: colors.lightBlueA700 }}
+ >
+ Parity Signer
+ </a>
+ </div>
+ </div>
+ <div>
+ <div
+ className="pl1 ml1"
+ style={{ borderLeft: `1px solid ${colors.grey300}`, height: 65 }}
+ />
+ <div className="py1">or</div>
+ <div
+ className="pl1 ml1"
+ style={{ borderLeft: `1px solid ${colors.grey300}`, height: 68 }}
+ />
+ </div>
+ <div className="px2 center">
+ <div style={{ color: colors.darkGrey }}>Connect to a ledger hardware wallet</div>
+ <div style={{ paddingTop: 21, paddingBottom: 29 }}>
+ <img src="/images/ledger_icon.png" style={{ width: 80 }} />
+ </div>
+ <div>
+ <RaisedButton
+ style={{ width: '100%' }}
+ label="Use Ledger"
+ onClick={this.props.onToggleLedgerDialog}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ }
+}
diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx
new file mode 100644
index 000000000..be7e57d6f
--- /dev/null
+++ b/packages/website/ts/components/top_bar/provider_picker.tsx
@@ -0,0 +1,81 @@
+import * as _ from 'lodash';
+import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
+import * as React from 'react';
+import { Blockchain } from 'ts/blockchain';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { ProviderType } from 'ts/types';
+import { colors } from 'ts/utils/colors';
+import { constants } from 'ts/utils/constants';
+
+interface ProviderPickerProps {
+ networkId: number;
+ injectedProviderName: string;
+ providerType: ProviderType;
+ onToggleLedgerDialog: () => void;
+ dispatcher: Dispatcher;
+ blockchain: Blockchain;
+}
+
+interface ProviderPickerState {}
+
+export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> {
+ public render() {
+ const isLedgerSelected = this.props.providerType === ProviderType.Ledger;
+ const menuStyle = {
+ padding: 10,
+ paddingTop: 15,
+ paddingBottom: 15,
+ };
+ // Show dropdown with two options
+ return (
+ <div style={{ width: 225, overflow: 'hidden' }}>
+ <RadioButtonGroup name="provider" defaultSelected={this.props.providerType}>
+ <RadioButton
+ onClick={this._onProviderRadioChanged.bind(this, ProviderType.Injected)}
+ style={{ ...menuStyle, backgroundColor: !isLedgerSelected && colors.grey50 }}
+ value={ProviderType.Injected}
+ label={this._renderLabel(this.props.injectedProviderName, !isLedgerSelected)}
+ />
+ <RadioButton
+ onClick={this._onProviderRadioChanged.bind(this, ProviderType.Ledger)}
+ style={{ ...menuStyle, backgroundColor: isLedgerSelected && colors.grey50 }}
+ value={ProviderType.Ledger}
+ label={this._renderLabel('Ledger Nano S', isLedgerSelected)}
+ />
+ </RadioButtonGroup>
+ </div>
+ );
+ }
+ private _renderLabel(title: string, shouldShowNetwork: boolean) {
+ const label = (
+ <div className="flex">
+ <div style={{ fontSize: 14 }}>{title}</div>
+ {shouldShowNetwork && this._renderNetwork()}
+ </div>
+ );
+ return label;
+ }
+ private _renderNetwork() {
+ const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId];
+ return (
+ <div className="flex" style={{ marginTop: 1 }}>
+ <div className="relative" style={{ width: 14, paddingLeft: 14 }}>
+ <img
+ src={`/images/network_icons/${networkName.toLowerCase()}.png`}
+ className="absolute"
+ style={{ top: 6, width: 10 }}
+ />
+ </div>
+ <div style={{ color: colors.lightGrey, fontSize: 11 }}>{networkName}</div>
+ </div>
+ );
+ }
+ private _onProviderRadioChanged(value: string) {
+ if (value === ProviderType.Ledger) {
+ this.props.onToggleLedgerDialog();
+ } else {
+ // tslint:disable-next-line:no-floating-promises
+ this.props.blockchain.updateProviderToInjectedAsync();
+ }
+ }
+}
diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index 11d3e7cc2..1a0691e83 100644
--- a/packages/website/ts/components/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -1,21 +1,31 @@
import * as _ from 'lodash';
import Drawer from 'material-ui/Drawer';
+import Menu from 'material-ui/Menu';
import MenuItem from 'material-ui/MenuItem';
import * as React from 'react';
import { Link } from 'react-router-dom';
import ReactTooltip = require('react-tooltip');
+import { Blockchain } from 'ts/blockchain';
import { PortalMenu } from 'ts/components/portal_menu';
-import { TopBarMenuItem } from 'ts/components/top_bar_menu_item';
-import { DropDownMenuItem } from 'ts/components/ui/drop_down_menu_item';
+import { ProviderDisplay } from 'ts/components/top_bar/provider_display';
+import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item';
+import { DropDown } from 'ts/components/ui/drop_down';
import { Identicon } from 'ts/components/ui/identicon';
import { DocsInfo } from 'ts/pages/documentation/docs_info';
import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu';
-import { DocsMenu, MenuSubsectionsBySection, Styles, WebsitePaths } from 'ts/types';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { DocsMenu, MenuSubsectionsBySection, ProviderType, Styles, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
interface TopBarProps {
userAddress?: string;
+ networkId?: number;
+ injectedProviderName?: string;
+ providerType?: ProviderType;
+ onToggleLedgerDialog?: () => void;
+ blockchain?: Blockchain;
+ dispatcher?: Dispatcher;
blockchainIsLoaded: boolean;
location: Location;
docsVersion?: string;
@@ -125,6 +135,15 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
cursor: 'pointer',
paddingTop: 16,
};
+ const hoverActiveNode = (
+ <div className="flex relative" style={{ color: menuIconStyle.color }}>
+ <div style={{ paddingRight: 10 }}>Developers</div>
+ <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
+ <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} />
+ </div>
+ </div>
+ );
+ const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>;
return (
<div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1">
<div className={parentClassNames}>
@@ -138,11 +157,12 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
{!this._isViewingPortal() && (
<div className={menuClasses}>
<div className="flex justify-between">
- <DropDownMenuItem
- title="Developers"
- subMenuItems={developerSectionMenuItems}
+ <DropDown
+ hoverActiveNode={hoverActiveNode}
+ popoverContent={popoverContent}
+ anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
+ targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
style={styles.menuItem}
- isNightVersion={isNightVersion}
/>
<TopBarMenuItem
title="Wiki"
@@ -167,10 +187,19 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
</div>
</div>
)}
- {this.props.blockchainIsLoaded &&
- !_.isEmpty(this.props.userAddress) && (
- <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div>
- )}
+ {this.props.blockchainIsLoaded && (
+ <div className="sm-hide xs-hide col col-5">
+ <ProviderDisplay
+ dispatcher={this.props.dispatcher}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ blockchain={this.props.blockchain}
+ />
+ </div>
+ )}
<div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
<div style={menuIconStyle}>
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx
index 96ee86142..96ee86142 100644
--- a/packages/website/ts/components/top_bar_menu_item.tsx
+++ b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx
diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down.tsx
index a578fb4f9..63b9eec0b 100644
--- a/packages/website/ts/components/ui/drop_down_menu_item.tsx
+++ b/packages/website/ts/components/ui/drop_down.tsx
@@ -1,36 +1,35 @@
import * as _ from 'lodash';
-import Menu from 'material-ui/Menu';
-import Popover from 'material-ui/Popover';
+import Popover, { PopoverAnimationVertical } from 'material-ui/Popover';
import * as React from 'react';
-import { colors } from 'ts/utils/colors';
+import { MaterialUIPosition } from 'ts/types';
const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300;
const DEFAULT_STYLE = {
fontSize: 14,
};
-interface DropDownMenuItemProps {
- title: string;
- subMenuItems: React.ReactNode[];
+interface DropDownProps {
+ hoverActiveNode: React.ReactNode;
+ popoverContent: React.ReactNode;
+ anchorOrigin: MaterialUIPosition;
+ targetOrigin: MaterialUIPosition;
style?: React.CSSProperties;
- menuItemStyle?: React.CSSProperties;
- isNightVersion?: boolean;
+ zDepth?: number;
}
-interface DropDownMenuItemState {
+interface DropDownState {
isDropDownOpen: boolean;
anchorEl?: HTMLInputElement;
}
-export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, DropDownMenuItemState> {
- public static defaultProps: Partial<DropDownMenuItemProps> = {
+export class DropDown extends React.Component<DropDownProps, DropDownState> {
+ public static defaultProps: Partial<DropDownProps> = {
style: DEFAULT_STYLE,
- menuItemStyle: DEFAULT_STYLE,
- isNightVersion: false,
+ zDepth: 1,
};
private _isHovering: boolean;
private _popoverCloseCheckIntervalId: number;
- constructor(props: DropDownMenuItemProps) {
+ constructor(props: DropDownProps) {
super(props);
this.state = {
isDropDownOpen: false,
@@ -44,30 +43,35 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro
public componentWillUnmount() {
window.clearInterval(this._popoverCloseCheckIntervalId);
}
+ public componentWillReceiveProps(nextProps: DropDownProps) {
+ // HACK: If the popoverContent is updated to a different dimension and the users
+ // mouse is no longer above it, the dropdown can enter an inconsistent state where
+ // it believes the user is still hovering over it. In order to remedy this, we
+ // call hoverOff whenever the dropdown receives updated props. This is a hack
+ // because it will effectively close the dropdown on any prop update, barring
+ // dropdowns from having dynamic content.
+ this._onHoverOff();
+ }
public render() {
- const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color;
return (
<div
- style={{ ...this.props.style, color: colorStyle }}
+ style={{ ...this.props.style, width: 'fit-content', height: '100%' }}
onMouseEnter={this._onHover.bind(this)}
onMouseLeave={this._onHoverOff.bind(this)}
>
- <div className="flex relative">
- <div style={{ paddingRight: 10 }}>{this.props.title}</div>
- <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
- <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} />
- </div>
- </div>
+ {this.props.hoverActiveNode}
<Popover
open={this.state.isDropDownOpen}
anchorEl={this.state.anchorEl}
- anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
- targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
+ anchorOrigin={this.props.anchorOrigin}
+ targetOrigin={this.props.targetOrigin}
onRequestClose={this._closePopover.bind(this)}
useLayerForClickAway={false}
+ animation={PopoverAnimationVertical}
+ zDepth={this.props.zDepth}
>
<div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}>
- <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu>
+ {this.props.popoverContent}
</div>
</Popover>
</div>
@@ -87,7 +91,7 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro
anchorEl: event.currentTarget,
});
}
- private _onHoverOff(event: React.FormEvent<HTMLInputElement>) {
+ private _onHoverOff() {
this._isHovering = false;
}
private _checkIfShouldClosePopover() {
diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx
deleted file mode 100644
index aa319e9e9..000000000
--- a/packages/website/ts/components/ui/loading.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import * as _ from 'lodash';
-import Paper from 'material-ui/Paper';
-import * as React from 'react';
-import { DefaultPlayer as Video } from 'react-html5video';
-import 'react-html5video/dist/styles.css';
-import { utils } from 'ts/utils/utils';
-
-interface LoadingProps {}
-
-interface LoadingState {}
-
-export class Loading extends React.Component<LoadingProps, LoadingState> {
- public render() {
- return (
- <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}>
- <Paper className="mx-auto" style={{ maxWidth: 400 }}>
- {utils.isUserOnMobile() ? (
- <img className="p1" src="/gifs/0xAnimation.gif" width="96%" />
- ) : (
- <div style={{ pointerEvents: 'none' }}>
- <Video
- autoPlay={true}
- loop={true}
- muted={true}
- controls={[]}
- poster="/images/loading_poster.png"
- >
- <source src="/videos/0xAnimation.mp4" type="video/mp4" />
- </Video>
- </div>
- )}
- <div className="center pt2" style={{ paddingBottom: 11 }}>
- Connecting to the blockchain...
- </div>
- </Paper>
- </div>
- );
- }
-}
diff --git a/packages/website/ts/containers/generate_order_form.tsx b/packages/website/ts/containers/generate_order_form.tsx
index 3fd31087f..57863dbae 100644
--- a/packages/website/ts/containers/generate_order_form.tsx
+++ b/packages/website/ts/containers/generate_order_form.tsx
@@ -6,14 +6,7 @@ import { Blockchain } from 'ts/blockchain';
import { GenerateOrderForm as GenerateOrderFormComponent } from 'ts/components/generate_order/generate_order_form';
import { Dispatcher } from 'ts/redux/dispatcher';
import { State } from 'ts/redux/reducer';
-import {
- BlockchainErrs,
- HashData,
- SideToAssetToken,
- SignatureData,
- TokenByAddress,
- TokenStateByAddress,
-} from 'ts/types';
+import { BlockchainErrs, HashData, SideToAssetToken, SignatureData, TokenByAddress } from 'ts/types';
interface GenerateOrderFormProps {
blockchain: Blockchain;
@@ -32,7 +25,7 @@ interface ConnectedState {
networkId: number;
sideToAssetToken: SideToAssetToken;
tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
+ lastForceTokenStateRefetch: number;
}
const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({
@@ -45,8 +38,8 @@ const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): Connec
networkId: state.networkId,
sideToAssetToken: state.sideToAssetToken,
tokenByAddress: state.tokenByAddress,
- tokenStateByAddress: state.tokenStateByAddress,
userAddress: state.userAddress,
+ lastForceTokenStateRefetch: state.lastForceTokenStateRefetch,
});
export const GenerateOrderForm: React.ComponentClass<GenerateOrderFormProps> = connect(mapStateToProps)(
diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx
index f0247935b..bcca0d70f 100644
--- a/packages/website/ts/containers/portal.tsx
+++ b/packages/website/ts/containers/portal.tsx
@@ -6,18 +6,20 @@ import { Dispatch } from 'redux';
import { Portal as PortalComponent, PortalAllProps as PortalComponentAllProps } from 'ts/components/portal';
import { Dispatcher } from 'ts/redux/dispatcher';
import { State } from 'ts/redux/reducer';
-import { BlockchainErrs, HashData, Order, ScreenWidths, Side, TokenByAddress, TokenStateByAddress } from 'ts/types';
+import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, Side, TokenByAddress } from 'ts/types';
import { constants } from 'ts/utils/constants';
interface ConnectedState {
blockchainErr: BlockchainErrs;
blockchainIsLoaded: boolean;
hashData: HashData;
+ injectedProviderName: string;
networkId: number;
nodeVersion: string;
orderFillAmount: BigNumber;
+ providerType: ProviderType;
tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
+ lastForceTokenStateRefetch: number;
userEtherBalance: BigNumber;
screenWidth: ScreenWidths;
shouldBlockchainErrDialogBeOpen: boolean;
@@ -57,14 +59,16 @@ const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): Conne
return {
blockchainErr: state.blockchainErr,
blockchainIsLoaded: state.blockchainIsLoaded,
+ hashData,
+ injectedProviderName: state.injectedProviderName,
networkId: state.networkId,
nodeVersion: state.nodeVersion,
orderFillAmount: state.orderFillAmount,
- hashData,
+ providerType: state.providerType,
screenWidth: state.screenWidth,
shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen,
tokenByAddress: state.tokenByAddress,
- tokenStateByAddress: state.tokenStateByAddress,
+ lastForceTokenStateRefetch: state.lastForceTokenStateRefetch,
userAddress: state.userAddress,
userEtherBalance: state.userEtherBalance,
userSuppliedOrderCache: state.userSuppliedOrderCache,
diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts
index 383e5cbe0..d7f887c6d 100644
--- a/packages/website/ts/globals.d.ts
+++ b/packages/website/ts/globals.d.ts
@@ -10,7 +10,6 @@ declare module 'thenby';
declare module 'react-highlight';
declare module 'react-recaptcha';
declare module 'react-document-title';
-declare module 'ledgerco';
declare module 'ethereumjs-tx';
declare module '*.json' {
diff --git a/packages/website/ts/local_storage/tracked_token_storage.ts b/packages/website/ts/local_storage/tracked_token_storage.ts
index 7733e8436..f865f8109 100644
--- a/packages/website/ts/local_storage/tracked_token_storage.ts
+++ b/packages/website/ts/local_storage/tracked_token_storage.ts
@@ -1,6 +1,6 @@
import * as _ from 'lodash';
import { localStorage } from 'ts/local_storage/local_storage';
-import { Token, TrackedTokensByUserAddress } from 'ts/types';
+import { Token, TokenByAddress, TrackedTokensByUserAddress } from 'ts/types';
import { configs } from 'ts/utils/configs';
const TRACKED_TOKENS_KEY = 'trackedTokens';
@@ -39,18 +39,22 @@ export const trackedTokenStorage = {
const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString);
return trackedTokensByUserAddress;
},
- getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] {
+ getTrackedTokensByAddress(userAddress: string, networkId: number): TokenByAddress {
+ const trackedTokensByAddress: TokenByAddress = {};
const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY);
if (_.isEmpty(trackedTokensJSONString)) {
- return undefined;
+ return trackedTokensByAddress;
}
const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString);
const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress];
if (_.isUndefined(trackedTokensByNetworkId)) {
- return undefined;
+ return trackedTokensByAddress;
}
const trackedTokens = trackedTokensByNetworkId[networkId];
- return trackedTokens;
+ _.each(trackedTokens, (trackedToken: Token) => {
+ trackedTokensByAddress[trackedToken.address] = trackedToken;
+ });
+ return trackedTokensByAddress;
},
removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string): void {
const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress();
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx
index c929673f5..0889e79f3 100644
--- a/packages/website/ts/pages/about/about.tsx
+++ b/packages/website/ts/pages/about/about.tsx
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { Footer } from 'ts/components/footer';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { Profile } from 'ts/pages/about/profile';
import { ProfileInfo, Styles } from 'ts/types';
import { colors } from 'ts/utils/colors';
diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx
index 2315847ad..7ad1d3b9c 100644
--- a/packages/website/ts/pages/documentation/documentation.tsx
+++ b/packages/website/ts/pages/documentation/documentation.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import DocumentTitle = require('react-document-title');
import { scroller } from 'react-scroll';
import semverSort = require('semver-sort');
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { Badge } from 'ts/components/ui/badge';
import { Comment } from 'ts/pages/documentation/comment';
import { DocsInfo } from 'ts/pages/documentation/docs_info';
@@ -40,9 +40,9 @@ import { utils } from 'ts/utils/utils';
const SCROLL_TOP_ID = 'docsScrollTop';
const networkNameToColor: { [network: string]: string } = {
- [Networks.kovan]: colors.purple,
- [Networks.ropsten]: colors.red,
- [Networks.mainnet]: colors.turquois,
+ [Networks.Kovan]: colors.purple,
+ [Networks.Ropsten]: colors.red,
+ [Networks.Mainnet]: colors.turquois,
};
export interface DocumentationAllProps {
@@ -78,8 +78,10 @@ const styles: Styles = {
};
export class Documentation extends React.Component<DocumentationAllProps, DocumentationState> {
+ private _isUnmounted: boolean;
constructor(props: DocumentationAllProps) {
super(props);
+ this._isUnmounted = false;
this.state = {
docAgnosticFormat: undefined,
};
@@ -92,6 +94,9 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume
// tslint:disable-next-line:no-floating-promises
this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists);
}
+ public componentWillUnmount() {
+ this._isUnmounted = true;
+ }
public render() {
const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat)
? {}
@@ -367,13 +372,15 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume
);
const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj);
- this.setState(
- {
- docAgnosticFormat,
- },
- () => {
- this._scrollToHash();
- },
- );
+ if (!this._isUnmounted) {
+ this.setState(
+ {
+ docAgnosticFormat,
+ },
+ () => {
+ this._scrollToHash();
+ },
+ );
+ }
}
}
diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx
index b4b5214a2..0a7eecc2d 100644
--- a/packages/website/ts/pages/faq/faq.tsx
+++ b/packages/website/ts/pages/faq/faq.tsx
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { Footer } from 'ts/components/footer';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { Question } from 'ts/pages/faq/question';
import { FAQQuestion, FAQSection, Styles, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx
index ca76497df..b0c622fb4 100644
--- a/packages/website/ts/pages/landing/landing.tsx
+++ b/packages/website/ts/pages/landing/landing.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
import DocumentTitle = require('react-document-title');
import { Link } from 'react-router-dom';
import { Footer } from 'ts/components/footer';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { ScreenWidths, WebsitePaths } from 'ts/types';
import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx
index ff277c377..0a6ec071c 100644
--- a/packages/website/ts/pages/not_found.tsx
+++ b/packages/website/ts/pages/not_found.tsx
@@ -1,7 +1,7 @@
import * as _ from 'lodash';
import * as React from 'react';
import { Footer } from 'ts/components/footer';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { Styles } from 'ts/types';
export interface NotFoundProps {
diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx
index d065614ba..daf5c27a7 100644
--- a/packages/website/ts/pages/wiki/wiki.tsx
+++ b/packages/website/ts/pages/wiki/wiki.tsx
@@ -3,7 +3,7 @@ import CircularProgress from 'material-ui/CircularProgress';
import * as React from 'react';
import DocumentTitle = require('react-document-title');
import { scroller } from 'react-scroll';
-import { TopBar } from 'ts/components/top_bar';
+import { TopBar } from 'ts/components/top_bar/top_bar';
import { MarkdownSection } from 'ts/pages/shared/markdown_section';
import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu';
import { SectionHeader } from 'ts/pages/shared/section_header';
@@ -45,8 +45,10 @@ const styles: Styles = {
export class Wiki extends React.Component<WikiProps, WikiState> {
private _wikiBackoffTimeoutId: number;
+ private _isUnmounted: boolean;
constructor(props: WikiProps) {
super(props);
+ this._isUnmounted = false;
this.state = {
articlesBySection: undefined,
};
@@ -56,6 +58,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> {
this._fetchArticlesBySectionAsync();
}
public componentWillUnmount() {
+ this._isUnmounted = true;
clearTimeout(this._wikiBackoffTimeoutId);
}
public render() {
@@ -179,14 +182,16 @@ export class Wiki extends React.Component<WikiProps, WikiState> {
return;
}
const articlesBySection = await response.json();
- this.setState(
- {
- articlesBySection,
- },
- () => {
- this._scrollToHash();
- },
- );
+ if (!this._isUnmounted) {
+ this.setState(
+ {
+ articlesBySection,
+ },
+ () => {
+ this._scrollToHash();
+ },
+ );
+ }
}
private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) {
const sectionNames = _.keys(articlesBySection);
diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts
index 42989e5e1..87415b285 100644
--- a/packages/website/ts/redux/dispatcher.ts
+++ b/packages/website/ts/redux/dispatcher.ts
@@ -9,9 +9,10 @@ import {
ProviderType,
ScreenWidths,
Side,
+ SideToAssetToken,
SignatureData,
Token,
- TokenStateByAddress,
+ TokenByAddress,
} from 'ts/types';
export class Dispatcher {
@@ -120,9 +121,20 @@ export class Dispatcher {
type: ActionTypes.RemoveTokenFromTokenByAddress,
});
}
- public clearTokenByAddress() {
+ public batchDispatch(
+ tokenByAddress: TokenByAddress,
+ networkId: number,
+ userAddress: string,
+ sideToAssetToken: SideToAssetToken,
+ ) {
this._dispatch({
- type: ActionTypes.ClearTokenByAddress,
+ data: {
+ tokenByAddress,
+ networkId,
+ userAddress,
+ sideToAssetToken,
+ },
+ type: ActionTypes.BatchDispatch,
});
}
public updateTokenByAddress(tokens: Token[]) {
@@ -131,43 +143,9 @@ export class Dispatcher {
type: ActionTypes.UpdateTokenByAddress,
});
}
- public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) {
- this._dispatch({
- data: tokenStateByAddress,
- type: ActionTypes.UpdateTokenStateByAddress,
- });
- }
- public removeFromTokenStateByAddress(tokenAddress: string) {
- this._dispatch({
- data: tokenAddress,
- type: ActionTypes.RemoveFromTokenStateByAddress,
- });
- }
- public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) {
+ public forceTokenStateRefetch() {
this._dispatch({
- data: {
- address,
- allowance,
- },
- type: ActionTypes.ReplaceTokenAllowanceByAddress,
- });
- }
- public replaceTokenBalanceByAddress(address: string, balance: BigNumber) {
- this._dispatch({
- data: {
- address,
- balance,
- },
- type: ActionTypes.ReplaceTokenBalanceByAddress,
- });
- }
- public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) {
- this._dispatch({
- data: {
- address,
- balanceDelta,
- },
- type: ActionTypes.UpdateTokenBalanceByAddress,
+ type: ActionTypes.ForceTokenStateRefetch,
});
}
public updateSignatureData(signatureData: SignatureData) {
diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts
index 06ac8b670..7b0b03dae 100644
--- a/packages/website/ts/redux/reducer.ts
+++ b/packages/website/ts/redux/reducer.ts
@@ -1,6 +1,7 @@
import { ZeroEx } from '0x.js';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
+import * as moment from 'moment';
import {
Action,
ActionTypes,
@@ -12,8 +13,6 @@ import {
SideToAssetToken,
SignatureData,
TokenByAddress,
- TokenState,
- TokenStateByAddress,
} from 'ts/types';
import { utils } from 'ts/utils/utils';
@@ -37,7 +36,7 @@ export interface State {
shouldBlockchainErrDialogBeOpen: boolean;
sideToAssetToken: SideToAssetToken;
tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
+ lastForceTokenStateRefetch: number;
userAddress: string;
userEtherBalance: BigNumber;
// Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
@@ -76,7 +75,7 @@ const INITIAL_STATE: State = {
[Side.Receive]: {},
},
tokenByAddress: {},
- tokenStateByAddress: {},
+ lastForceTokenStateRefetch: moment().unix(),
userAddress: '',
userEtherBalance: new BigNumber(0),
userSuppliedOrderCache: undefined,
@@ -139,13 +138,6 @@ export function reducer(state: State = INITIAL_STATE, action: Action) {
};
}
- case ActionTypes.ClearTokenByAddress: {
- return {
- ...state,
- tokenByAddress: {},
- };
- }
-
case ActionTypes.AddTokenToTokenByAddress: {
const newTokenByAddress = state.tokenByAddress;
newTokenByAddress[action.data.address] = action.data;
@@ -180,74 +172,21 @@ export function reducer(state: State = INITIAL_STATE, action: Action) {
};
}
- case ActionTypes.UpdateTokenStateByAddress: {
- const tokenStateByAddress = state.tokenStateByAddress;
- const updatedTokenStateByAddress = action.data;
- _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => {
- const updatedTokenState = {
- ...tokenStateByAddress[address],
- ...tokenState,
- };
- tokenStateByAddress[address] = updatedTokenState;
- });
- return {
- ...state,
- tokenStateByAddress,
- };
- }
-
- case ActionTypes.RemoveFromTokenStateByAddress: {
- const tokenStateByAddress = state.tokenStateByAddress;
- const tokenAddress = action.data;
- delete tokenStateByAddress[tokenAddress];
- return {
- ...state,
- tokenStateByAddress,
- };
- }
-
- case ActionTypes.ReplaceTokenAllowanceByAddress: {
- const tokenStateByAddress = state.tokenStateByAddress;
- const allowance = action.data.allowance;
- const tokenAddress = action.data.address;
- tokenStateByAddress[tokenAddress] = {
- ...tokenStateByAddress[tokenAddress],
- allowance,
- };
+ case ActionTypes.BatchDispatch: {
return {
...state,
- tokenStateByAddress,
+ networkId: action.data.networkId,
+ userAddress: action.data.userAddress,
+ sideToAssetToken: action.data.sideToAssetToken,
+ tokenByAddress: action.data.tokenByAddress,
};
}
- case ActionTypes.ReplaceTokenBalanceByAddress: {
- const tokenStateByAddress = state.tokenStateByAddress;
- const balance = action.data.balance;
- const tokenAddress = action.data.address;
- tokenStateByAddress[tokenAddress] = {
- ...tokenStateByAddress[tokenAddress],
- balance,
- };
+ case ActionTypes.ForceTokenStateRefetch:
return {
...state,
- tokenStateByAddress,
+ lastForceTokenStateRefetch: moment().unix(),
};
- }
-
- case ActionTypes.UpdateTokenBalanceByAddress: {
- const tokenStateByAddress = state.tokenStateByAddress;
- const balanceDelta = action.data.balanceDelta;
- const tokenAddress = action.data.address;
- const currBalance = tokenStateByAddress[tokenAddress].balance;
- tokenStateByAddress[tokenAddress] = {
- ...tokenStateByAddress[tokenAddress],
- balance: currBalance.plus(balanceDelta),
- };
- return {
- ...state,
- tokenStateByAddress,
- };
- }
case ActionTypes.UpdateOrderSignatureData: {
return {
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index f873f95fa..c48c88cae 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -25,10 +25,6 @@ export interface TokenState {
balance: BigNumber;
}
-export interface TokenStateByAddress {
- [address: string]: TokenState;
-}
-
export interface AssetToken {
address?: string;
amount?: BigNumber;
@@ -110,12 +106,12 @@ export enum BalanceErrs {
export enum ActionTypes {
// Portal
+ BatchDispatch = 'BATCH_DISPATCH',
UpdateScreenWidth = 'UPDATE_SCREEN_WIDTH',
UpdateNodeVersion = 'UPDATE_NODE_VERSION',
ResetState = 'RESET_STATE',
AddTokenToTokenByAddress = 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS',
BlockchainErrEncountered = 'BLOCKCHAIN_ERR_ENCOUNTERED',
- ClearTokenByAddress = 'CLEAR_TOKEN_BY_ADDRESS',
UpdateBlockchainIsLoaded = 'UPDATE_BLOCKCHAIN_IS_LOADED',
UpdateNetworkId = 'UPDATE_NETWORK_ID',
UpdateChosenAssetToken = 'UPDATE_CHOSEN_ASSET_TOKEN',
@@ -125,11 +121,7 @@ export enum ActionTypes {
UpdateOrderSignatureData = 'UPDATE_ORDER_SIGNATURE_DATA',
UpdateTokenByAddress = 'UPDATE_TOKEN_BY_ADDRESS',
RemoveTokenFromTokenByAddress = 'REMOVE_TOKEN_FROM_TOKEN_BY_ADDRESS',
- UpdateTokenStateByAddress = 'UPDATE_TOKEN_STATE_BY_ADDRESS',
- RemoveFromTokenStateByAddress = 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS',
- ReplaceTokenAllowanceByAddress = 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS',
- ReplaceTokenBalanceByAddress = 'REPLACE_TOKEN_BALANCE_BY_ADDRESS',
- UpdateTokenBalanceByAddress = 'UPDATE_TOKEN_BALANCE_BY_ADDRESS',
+ ForceTokenStateRefetch = 'FORCE_TOKEN_STATE_REFETCH',
UpdateOrderExpiry = 'UPDATE_ORDER_EXPIRY',
SwapAssetTokens = 'SWAP_ASSET_TOKENS',
UpdateUserAddress = 'UPDATE_USER_ADDRESS',
@@ -496,16 +488,6 @@ export interface SignPersonalMessageParams {
data: string;
}
-export interface TxParams {
- nonce: string;
- gasPrice?: number;
- gasLimit: string;
- to: string;
- value?: string;
- data?: string;
- chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3
-}
-
export interface PublicNodeUrlsByNetworkId {
[networkId: number]: string[];
}
@@ -610,10 +592,10 @@ export interface AddressByContractName {
}
export enum Networks {
- mainnet = 'Mainnet',
- kovan = 'Kovan',
- ropsten = 'Ropsten',
- rinkeby = 'Rinkeby',
+ Mainnet = 'Mainnet',
+ Kovan = 'Kovan',
+ Ropsten = 'Ropsten',
+ Rinkeby = 'Rinkeby',
}
export enum AbiTypes {
@@ -678,4 +660,9 @@ export enum SmartContractDocSections {
ZRXToken = 'ZRXToken',
}
+export interface MaterialUIPosition {
+ vertical: 'bottom' | 'top' | 'center';
+ horizontal: 'left' | 'middle' | 'right';
+}
+
// tslint:disable:max-file-line-count
diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts
index 3d37a89ab..874ad04c2 100644
--- a/packages/website/ts/utils/configs.ts
+++ b/packages/website/ts/utils/configs.ts
@@ -16,24 +16,24 @@ const isDevelopment = _.includes(
const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs';
export const configs = {
- BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com',
+ BACKEND_BASE_URL: 'https://website-api.0xproject.com',
BASE_URL,
BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208',
CONTRACT_ADDRESS: {
'1.0.0': {
- [Networks.mainnet]: {
+ [Networks.Mainnet]: {
[SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093',
[SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
[SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498',
[SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c',
},
- [Networks.ropsten]: {
+ [Networks.Ropsten]: {
[SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac',
[SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
[SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d',
[SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed',
},
- [Networks.kovan]: {
+ [Networks.Kovan]: {
[SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364',
[SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4',
[SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
@@ -120,6 +120,8 @@ export const configs = {
PUBLIC_NODE_URLS_BY_NETWORK_ID: {
[1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'],
[42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'],
+ [3]: [`https://ropsten.infura.io/${INFURA_API_KEY}`],
+ [4]: [`https://rinkeby.infura.io/${INFURA_API_KEY}`],
} as PublicNodeUrlsByNetworkId,
SHOULD_DEPRECATE_OLD_WETH_TOKEN: true,
SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'],
diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts
index dded82114..26a793f38 100644
--- a/packages/website/ts/utils/constants.ts
+++ b/packages/website/ts/utils/constants.ts
@@ -10,6 +10,8 @@ export const constants = {
1: 4145578,
42: 3117574,
50: 0,
+ 3: 1719261,
+ 4: 1570919,
} as { [networkId: number]: number },
HOME_SCROLL_DURATION_MS: 500,
HTTP_NO_CONTENT_STATUS_CODE: 204,
@@ -19,19 +21,19 @@ export const constants = {
MAINNET_NAME: 'Main network',
MINT_AMOUNT: new BigNumber('100000000000000000000'),
NETWORK_ID_MAINNET: 1,
- NETWORK_ID_TESTNET: 42,
+ NETWORK_ID_KOVAN: 42,
NETWORK_ID_TESTRPC: 50,
NETWORK_NAME_BY_ID: {
- 1: Networks.mainnet,
- 3: Networks.ropsten,
- 4: Networks.rinkeby,
- 42: Networks.kovan,
+ 1: Networks.Mainnet,
+ 3: Networks.Ropsten,
+ 4: Networks.Rinkeby,
+ 42: Networks.Kovan,
} as { [symbol: number]: string },
NETWORK_ID_BY_NAME: {
- [Networks.mainnet]: 1,
- [Networks.ropsten]: 3,
- [Networks.rinkeby]: 4,
- [Networks.kovan]: 42,
+ [Networks.Mainnet]: 1,
+ [Networks.Ropsten]: 3,
+ [Networks.Rinkeby]: 4,
+ [Networks.Kovan]: 42,
} as { [networkName: string]: number },
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
PROVIDER_NAME_LEDGER: 'Ledger',
diff --git a/packages/website/ts/utils/mui_theme.ts b/packages/website/ts/utils/mui_theme.ts
index d73e80606..32891baca 100644
--- a/packages/website/ts/utils/mui_theme.ts
+++ b/packages/website/ts/utils/mui_theme.ts
@@ -8,6 +8,7 @@ export const muiTheme = getMuiTheme({
textColor: colors.black,
},
palette: {
+ accent1Color: colors.lightBlueA700,
pickerHeaderColor: colors.lightBlue,
primary1Color: colors.lightBlue,
primary2Color: colors.lightBlue,
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index 13a6d6ae2..7e69b1c5f 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -151,7 +151,7 @@ export const utils = {
if (_.isUndefined(networkName)) {
return undefined;
}
- const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`;
+ const etherScanPrefix = networkName === Networks.Mainnet ? '' : `${networkName.toLowerCase()}.`;
return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`;
},
setUrlHash(anchorId: string) {
@@ -183,7 +183,7 @@ export const utils = {
// after a user was prompted to sign a message or send a transaction and decided to
// reject the request.
didUserDenyWeb3Request(errMsg: string) {
- const metamaskDenialErrMsg = 'User denied message';
+ const metamaskDenialErrMsg = 'User denied';
const paritySignerDenialErrMsg = 'Request has been rejected';
const ledgerDenialErrMsg = 'Invalid status 6985';
const isUserDeniedErrMsg =
@@ -276,4 +276,10 @@ export const utils = {
exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error];
return humanReadableErrorMsg;
},
+ isParityNode(nodeVersion: string): boolean {
+ return _.includes(nodeVersion, 'Parity');
+ },
+ isTestRpc(nodeVersion: string): boolean {
+ return _.includes(nodeVersion, 'TestRPC');
+ },
};
diff --git a/packages/website/ts/web3_wrapper.ts b/packages/website/ts/web3_wrapper.ts
index 415df6e8b..9d8d771af 100644
--- a/packages/website/ts/web3_wrapper.ts
+++ b/packages/website/ts/web3_wrapper.ts
@@ -24,9 +24,6 @@ export class Web3Wrapper {
this._web3 = new Web3();
this._web3.setProvider(provider);
-
- // tslint:disable-next-line:no-floating-promises
- this._startEmittingNetworkConnectionAndUserBalanceStateAsync();
}
public isAddress(address: string) {
return this._web3.isAddress(address);
@@ -90,11 +87,7 @@ export class Web3Wrapper {
public updatePrevUserAddress(userAddress: string) {
this._prevUserAddress = userAddress;
}
- private async _getNetworkAsync() {
- const networkId = await promisify(this._web3.version.getNetwork)();
- return networkId;
- }
- private async _startEmittingNetworkConnectionAndUserBalanceStateAsync() {
+ public startEmittingNetworkConnectionAndUserBalanceState() {
if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
return; // we are already emitting the state
}
@@ -127,7 +120,7 @@ export class Web3Wrapper {
}
// Check for user ether balance changes
- if (userAddressIfExists !== '') {
+ if (!_.isEmpty(userAddressIfExists)) {
await this._updateUserEtherBalanceAsync(userAddressIfExists);
}
} else {
@@ -140,11 +133,15 @@ export class Web3Wrapper {
},
5000,
(err: Error) => {
- utils.consoleLog(`Watching network and balances failed: ${err}`);
+ utils.consoleLog(`Watching network and balances failed: ${err.stack}`);
this._stopEmittingNetworkConnectionAndUserBalanceStateAsync();
},
);
}
+ private async _getNetworkAsync() {
+ const networkId = await promisify(this._web3.version.getNetwork)();
+ return networkId;
+ }
private async _updateUserEtherBalanceAsync(userAddress: string) {
const balance = await this.getBalanceInEthAsync(userAddress);
if (!balance.eq(this._prevUserEtherBalanceInEth)) {
@@ -153,6 +150,8 @@ export class Web3Wrapper {
}
}
private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() {
- intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
+ if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
+ intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
+ }
}
}