aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website')
-rw-r--r--packages/website/md/docs/sol_cov/usage.md48
-rw-r--r--packages/website/package.json4
-rw-r--r--packages/website/ts/blockchain_watcher.ts6
-rw-r--r--packages/website/ts/components/eth_weth_conversion_button.tsx2
-rw-r--r--packages/website/ts/components/eth_wrappers.tsx2
-rw-r--r--packages/website/ts/components/legacy_portal/legacy_portal.tsx31
-rw-r--r--packages/website/ts/components/onboarding/portal_onboarding_flow.tsx4
-rw-r--r--packages/website/ts/components/portal/menu.tsx11
-rw-r--r--packages/website/ts/components/portal/portal.tsx38
-rw-r--r--packages/website/ts/components/token_balances.tsx3
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx36
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx3
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx191
-rw-r--r--packages/website/ts/components/wallet/wallet_disconnected_item.tsx2
-rw-r--r--packages/website/ts/containers/legacy_portal.ts2
-rw-r--r--packages/website/ts/containers/portal.ts2
-rw-r--r--packages/website/ts/containers/portal_onboarding_flow.ts4
-rw-r--r--packages/website/ts/containers/sol_cov_documentation.ts36
-rw-r--r--packages/website/ts/redux/dispatcher.ts2
-rw-r--r--packages/website/ts/redux/reducer.ts4
-rw-r--r--packages/website/ts/utils/order_parser.ts33
21 files changed, 341 insertions, 123 deletions
diff --git a/packages/website/md/docs/sol_cov/usage.md b/packages/website/md/docs/sol_cov/usage.md
index 63a88f595..59638f611 100644
--- a/packages/website/md/docs/sol_cov/usage.md
+++ b/packages/website/md/docs/sol_cov/usage.md
@@ -2,8 +2,47 @@ Sol-cov uses transaction traces in order to figure out which lines of Solidity s
The CoverageSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot.
+Coverage subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data.
+
+In order to use `CoverageSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it.
+
+### Sol-compiler
+
+If you are generating your artifacts with [@0xproject/sol-compiler](LINK) you can use the `SolCompilerArtifactsAdapter` we've implemented for you.
+
+```typescript
+<<<<<<< HEAD
+import { CoverageSubprovider } from '@0xproject/sol-cov';
+=======
+import { SolCompilerArtifactsAdapter } from '@0xproject/sol-cov';
+const artifactsPath = 'src/artifacts';
+const contractsPath = 'src/contracts';
+const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath);
+```
+
+### Truffle
+
+If your project is using [Truffle](LINK), we've written a `TruffleArtifactsAdapter`for you.
+
+```typescript
+import { TruffleArtifactAdapter } from '@0xproject/sol-cov';
+const contractsPath = 'src/contracts';
+const artifactAdapter = new TruffleArtifactAdapter(contractsDir);
+```
+
+Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`.
+
+### Other framework/toolset
+
+You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`.
+Look at the code of the two adapters above for examples.
+
+### Usage
+
```typescript
import { CoverageSubprovider } from '@0xproject/sol-cov';
+import ProviderEngine = require('web3-provider-engine');
+>>>>>>> Improve sol-cov docs
const provider = new ProviderEngine();
@@ -12,15 +51,20 @@ const contractsPath = 'src/contracts';
const networkId = 50;
// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from.
const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
+<<<<<<< HEAD
const coverageSubprovider = new CoverageSubprovider(artifactsPath, contractsPath, defaultFromAddress);
+=======
+const isVerbose = true;
+const coverageSubprovider = new CoverageSubprovider(artifactsAdapter, defaultFromAddress, isVerbose);
+>>>>>>> Improve sol-cov docs
provider.addProvider(coverageSubprovider);
```
-After your test suite is complete (e.g global `after` hook), you'll need to call:
+After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call:
```typescript
await coverageSubprovider.writeCoverageAsync();
```
-This will create a `coverage.json` file in the `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use any of the existing Instanbul reporters.
+This will create a `coverage.json` file in a `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use it with any of the existing Istanbul reporters.
diff --git a/packages/website/package.json b/packages/website/package.json
index 95804f988..a17964f2b 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -57,11 +57,13 @@
"web3": "^0.20.0",
"web3-provider-engine": "^14.0.4",
"whatwg-fetch": "^2.0.3",
- "xml-js": "^1.3.2"
+ "xml-js": "^1.6.4"
},
"devDependencies": {
"@types/accounting": "^0.4.1",
+ "@types/blockies": "^0.0.0",
"@types/deep-equal": "^1.0.0",
+ "@types/find-versions": "^2.0.0",
"@types/jsonschema": "^1.1.1",
"@types/lodash": "4.14.104",
"@types/material-ui": "0.18.0",
diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts
index c420a98a4..0d376bc74 100644
--- a/packages/website/ts/blockchain_watcher.ts
+++ b/packages/website/ts/blockchain_watcher.ts
@@ -10,7 +10,7 @@ export class BlockchainWatcher {
private _prevNetworkId: number;
private _shouldPollUserAddress: boolean;
private _watchNetworkAndBalanceIntervalId: NodeJS.Timer;
- private _prevUserEtherBalanceInWei: BigNumber;
+ private _prevUserEtherBalanceInWei?: BigNumber;
private _prevUserAddressIfExists: string;
constructor(
dispatcher: Dispatcher,
@@ -41,7 +41,7 @@ export class BlockchainWatcher {
}
let prevNodeVersion: string;
- this._prevUserEtherBalanceInWei = new BigNumber(0);
+ this._prevUserEtherBalanceInWei = undefined;
this._dispatcher.updateNetworkId(this._prevNetworkId);
this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
async () => {
@@ -94,7 +94,7 @@ export class BlockchainWatcher {
}
private async _updateUserWeiBalanceAsync(userAddress: string): Promise<void> {
const balanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(userAddress);
- if (!balanceInWei.eq(this._prevUserEtherBalanceInWei)) {
+ if (_.isUndefined(this._prevUserEtherBalanceInWei) || !balanceInWei.eq(this._prevUserEtherBalanceInWei)) {
this._prevUserEtherBalanceInWei = balanceInWei;
this._dispatcher.updateUserWeiBalance(balanceInWei);
}
diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx
index 4b91a2ebd..2fb35cc1c 100644
--- a/packages/website/ts/components/eth_weth_conversion_button.tsx
+++ b/packages/website/ts/components/eth_weth_conversion_button.tsx
@@ -18,7 +18,7 @@ interface EthWethConversionButtonProps {
ethToken: Token;
dispatcher: Dispatcher;
blockchain: Blockchain;
- userEtherBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
isOutdatedWrappedEther: boolean;
onConversionSuccessful?: () => void;
isDisabled?: boolean;
diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx
index a5758a66a..1db5ff77f 100644
--- a/packages/website/ts/components/eth_wrappers.tsx
+++ b/packages/website/ts/components/eth_wrappers.tsx
@@ -33,7 +33,7 @@ interface EthWrappersProps {
dispatcher: Dispatcher;
tokenByAddress: TokenByAddress;
userAddress: string;
- userEtherBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
lastForceTokenStateRefetch: number;
}
diff --git a/packages/website/ts/components/legacy_portal/legacy_portal.tsx b/packages/website/ts/components/legacy_portal/legacy_portal.tsx
index a5ea95629..e5d152e3e 100644
--- a/packages/website/ts/components/legacy_portal/legacy_portal.tsx
+++ b/packages/website/ts/components/legacy_portal/legacy_portal.tsx
@@ -38,6 +38,7 @@ import {
} from 'ts/types';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
+import { orderParser } from 'ts/utils/order_parser';
import { Translate } from 'ts/utils/translate';
import { utils } from 'ts/utils/utils';
@@ -55,7 +56,7 @@ export interface LegacyPortalProps {
providerType: ProviderType;
screenWidth: ScreenWidths;
tokenByAddress: TokenByAddress;
- userEtherBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
userAddress: string;
shouldBlockchainErrDialogBeOpen: boolean;
userSuppliedOrderCache: Order;
@@ -86,7 +87,7 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
}
constructor(props: LegacyPortalProps) {
super(props);
- this._sharedOrderIfExists = this._getSharedOrderIfExists();
+ this._sharedOrderIfExists = orderParser.parse(window.location.search);
this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT);
const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`);
@@ -362,32 +363,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
isWethNoticeDialogOpen: false,
});
}
- private _getSharedOrderIfExists(): Order | undefined {
- const queryString = window.location.search;
- if (queryString.length === 0) {
- return undefined;
- }
- const queryParams = queryString.substring(1).split('&');
- const orderQueryParam = _.find(queryParams, queryParam => {
- const queryPair = queryParam.split('=');
- return queryPair[0] === 'order';
- });
- if (_.isUndefined(orderQueryParam)) {
- return undefined;
- }
- const orderPair = orderQueryParam.split('=');
- if (orderPair.length !== 2) {
- return undefined;
- }
-
- const order = JSON.parse(decodeURIComponent(orderPair[1]));
- const validationResult = validator.validate(order, portalOrderSchema);
- if (validationResult.errors.length > 0) {
- logUtils.log(`Invalid shared order: ${validationResult.errors}`);
- return undefined;
- }
- return order;
- }
private _updateScreenWidth(): void {
const newScreenWidth = utils.getScreenWidth();
this.props.dispatcher.updateScreenWidth(newScreenWidth);
diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
index edaeb3736..2014dd7b0 100644
--- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
@@ -14,7 +14,7 @@ export interface PortalOnboardingFlowProps {
providerType: ProviderType;
injectedProviderName: string;
blockchainIsLoaded: boolean;
- userEthBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
tokenByAddress: TokenByAddress;
updateIsRunning: (isRunning: boolean) => void;
updateOnboardingStep: (stepIndex: number) => void;
@@ -85,7 +85,7 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
}
private _userHasEth(): boolean {
- return this.props.userEthBalanceInWei > new BigNumber(0);
+ return this.props.userEtherBalanceInWei > new BigNumber(0);
}
private _userHasWeth(): boolean {
diff --git a/packages/website/ts/components/portal/menu.tsx b/packages/website/ts/components/portal/menu.tsx
index 6a3301549..6e97ee37e 100644
--- a/packages/website/ts/components/portal/menu.tsx
+++ b/packages/website/ts/components/portal/menu.tsx
@@ -43,9 +43,14 @@ export const defaultMenuItemEntries: MenuItemEntry[] = [
iconName: 'zmdi-circle-o',
},
{
- to: `${WebsitePaths.Portal}/direct`,
- labelText: 'Trade direct',
- iconName: 'zmdi-swap',
+ to: `${WebsitePaths.Portal}/generate`,
+ labelText: 'Generate order',
+ iconName: 'zmdi-arrow-right-top',
+ },
+ {
+ to: `${WebsitePaths.Portal}/fill`,
+ labelText: 'Fill order',
+ iconName: 'zmdi-arrow-left-bottom',
},
];
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index 0e1506e17..589ad00ad 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -10,6 +10,7 @@ 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 { EthWrappers } from 'ts/components/eth_wrappers';
+import { FillOrder } from 'ts/components/fill_order';
import { AssetPicker } from 'ts/components/generate_order/asset_picker';
import { BackButton } from 'ts/components/portal/back_button';
import { Loading } from 'ts/components/portal/loading';
@@ -42,6 +43,7 @@ import {
} from 'ts/types';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
+import { orderParser } from 'ts/utils/order_parser';
import { Translate } from 'ts/utils/translate';
import { utils } from 'ts/utils/utils';
@@ -57,7 +59,7 @@ export interface PortalProps {
providerType: ProviderType;
screenWidth: ScreenWidths;
tokenByAddress: TokenByAddress;
- userEtherBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
userAddress: string;
shouldBlockchainErrDialogBeOpen: boolean;
userSuppliedOrderCache: Order;
@@ -116,9 +118,11 @@ const styles: Styles = {
export class Portal extends React.Component<PortalProps, PortalState> {
private _blockchain: Blockchain;
+ private _sharedOrderIfExists: Order;
private _throttledScreenWidthUpdate: () => void;
constructor(props: PortalProps) {
super(props);
+ this._sharedOrderIfExists = orderParser.parse(window.location.search);
this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT);
const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER);
const hasAcceptedDisclaimer =
@@ -336,9 +340,14 @@ export class Portal extends React.Component<PortalProps, PortalState> {
render: this._renderTradeHistory.bind(this),
},
{
- pathName: `${WebsitePaths.Portal}/direct`,
- headerText: 'Trade Direct',
- render: this._renderTradeDirect.bind(this),
+ pathName: `${WebsitePaths.Portal}/generate`,
+ headerText: 'Generate Order',
+ render: this._renderGenerateOrderForm.bind(this),
+ },
+ {
+ pathName: `${WebsitePaths.Portal}/fill`,
+ headerText: 'Fill Order',
+ render: this._renderFillOrder.bind(this),
},
];
return (
@@ -386,7 +395,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
/>
);
}
- private _renderTradeDirect(match: any, location: Location, history: History): React.ReactNode {
+ private _renderGenerateOrderForm(): React.ReactNode {
return (
<GenerateOrderForm
blockchain={this._blockchain}
@@ -395,6 +404,25 @@ export class Portal extends React.Component<PortalProps, PortalState> {
/>
);
}
+ private _renderFillOrder(): React.ReactNode {
+ const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache)
+ ? this.props.userSuppliedOrderCache
+ : this._sharedOrderIfExists;
+ return (
+ <FillOrder
+ blockchain={this._blockchain}
+ blockchainErr={this.props.blockchainErr}
+ initialOrder={initialFillOrder}
+ isOrderInUrl={!_.isUndefined(this._sharedOrderIfExists)}
+ orderFillAmount={this.props.orderFillAmount}
+ networkId={this.props.networkId}
+ userAddress={this.props.userAddress}
+ tokenByAddress={this.props.tokenByAddress}
+ dispatcher={this.props.dispatcher}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ />
+ );
+ }
private _renderTokenBalances(): React.ReactNode {
const allTokens = _.values(this.props.tokenByAddress);
const trackedTokens = _.filter(allTokens, t => t.isTracked);
diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx
index f5a51dabb..7a0742bbe 100644
--- a/packages/website/ts/components/token_balances.tsx
+++ b/packages/website/ts/components/token_balances.tsx
@@ -85,6 +85,9 @@ interface TokenBalancesState {
}
export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> {
+ public static defaultProps: Partial<TokenBalancesProps> = {
+ userEtherBalanceInWei: new BigNumber(0),
+ };
private _isUnmounted: boolean;
public constructor(props: TokenBalancesProps) {
super(props);
diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx
index 679ec07dc..8a337119a 100644
--- a/packages/website/ts/components/top_bar/provider_display.tsx
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -1,5 +1,6 @@
import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
+import CircularProgress from 'material-ui/CircularProgress';
import RaisedButton from 'material-ui/RaisedButton';
import * as React from 'react';
@@ -23,7 +24,8 @@ export interface ProviderDisplayProps {
injectedProviderName: string;
providerType: ProviderType;
onToggleLedgerDialog: () => void;
- blockchain: Blockchain;
+ blockchain?: Blockchain;
+ blockchainIsLoaded: boolean;
}
interface ProviderDisplayState {}
@@ -44,11 +46,18 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
this.props.providerType,
this.props.injectedProviderName,
);
- const displayAddress = isAddressAvailable
- ? utils.getAddressBeginAndEnd(this.props.userAddress)
- : isExternallyInjectedProvider
- ? 'Account locked'
- : '0x0000...0000';
+ let displayMessage;
+ if (!this._isBlockchainReady()) {
+ displayMessage = 'loading account';
+ } else if (isAddressAvailable) {
+ displayMessage = utils.getAddressBeginAndEnd(this.props.userAddress);
+ // tslint:disable-next-line: prefer-conditional-expression
+ } else if (isExternallyInjectedProvider) {
+ displayMessage = 'Account locked';
+ } else {
+ displayMessage = '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
@@ -60,10 +69,14 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
const hoverActiveNode = (
<div className="flex right lg-pr0 md-pr2 sm-pr2 p1" style={styles.root}>
<div>
- <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
+ {this._isBlockchainReady() ? (
+ <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
+ ) : (
+ <CircularProgress size={ROOT_HEIGHT} thickness={2} />
+ )}
</div>
<div style={{ marginLeft: 12, paddingTop: 3 }}>
- <div style={{ fontSize: 16, color: colors.darkGrey }}>{displayAddress}</div>
+ <div style={{ fontSize: 16, color: colors.darkGrey }}>{displayMessage}</div>
</div>
{isProviderMetamask && (
<div style={{ marginLeft: 16 }}>
@@ -87,7 +100,9 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
);
}
public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean): React.ReactNode {
- if (hasInjectedProvider || hasLedgerProvider) {
+ if (!this._isBlockchainReady()) {
+ return null;
+ } else if (hasInjectedProvider || hasLedgerProvider) {
return (
<ProviderPicker
dispatcher={this.props.dispatcher}
@@ -159,4 +174,7 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
);
}
}
+ private _isBlockchainReady(): boolean {
+ return this.props.blockchainIsLoaded && !_.isUndefined(this.props.blockchain);
+ }
}
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index db8e3cb82..e2d791ae3 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -261,7 +261,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
</div>
</div>
)}
- {this.props.blockchainIsLoaded && (
+ {this._isViewingPortal() && (
<div className="sm-hide xs-hide col col-5" style={{ paddingTop: 8, marginRight: 36 }}>
<ProviderDisplay
dispatcher={this.props.dispatcher}
@@ -271,6 +271,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
providerType={this.props.providerType}
onToggleLedgerDialog={this.props.onToggleLedgerDialog}
blockchain={this.props.blockchain}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
/>
</div>
)}
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 30d1285f4..18dada22f 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -7,6 +7,7 @@ import {
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
+import CircularProgress from 'material-ui/CircularProgress';
import FlatButton from 'material-ui/FlatButton';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import { ListItem } from 'material-ui/List';
@@ -23,6 +24,7 @@ import firstBy = require('thenby');
import { Blockchain } from 'ts/blockchain';
import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle';
+import { Container } from 'ts/components/ui/container';
import { IconButton } from 'ts/components/ui/icon_button';
import { Identicon } from 'ts/components/ui/identicon';
import { Island } from 'ts/components/ui/island';
@@ -59,7 +61,7 @@ export interface WalletProps {
dispatcher: Dispatcher;
tokenByAddress: TokenByAddress;
trackedTokens: Token[];
- userEtherBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
lastForceTokenStateRefetch: number;
injectedProviderName: string;
providerType: ProviderType;
@@ -92,9 +94,6 @@ const styles: Styles = {
zIndex: zIndex.aboveOverlay,
position: 'relative',
},
- headerItemInnerDiv: {
- paddingLeft: 65,
- },
footerItemInnerDiv: {
paddingLeft: 24,
borderTopColor: colors.walletBorder,
@@ -108,6 +107,7 @@ const styles: Styles = {
},
tokenItem: {
backgroundColor: colors.walletDefaultItemBackground,
+ minHeight: 85,
},
amountLabel: {
fontWeight: 'bold',
@@ -129,10 +129,13 @@ const styles: Styles = {
color: colors.mediumBlue,
fontWeight: 'bold',
},
+ loadingBody: {
+ height: 381,
+ },
};
const ETHER_ICON_PATH = '/images/ether.png';
-const ICON_DIMENSION = 24;
+const ICON_DIMENSION = 28;
const TOKEN_AMOUNT_DISPLAY_PRECISION = 3;
const BODY_ITEM_KEY = 'BODY';
const HEADER_ITEM_KEY = 'HEADER';
@@ -191,26 +194,40 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
}
}
public render(): React.ReactNode {
- const isReadyToRender = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
- const isAddressAvailable = !_.isEmpty(this.props.userAddress);
+ const isBlockchainLoaded = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
return (
<Island className="flex flex-column wallet" style={styles.root}>
- {isReadyToRender && isAddressAvailable
- ? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows())
- : _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())}
+ {isBlockchainLoaded ? this._renderLoadedRows() : this._renderLoadingRows()}
</Island>
);
}
+ private _renderLoadedRows(): React.ReactNode {
+ const isAddressAvailable = !_.isEmpty(this.props.userAddress);
+ return isAddressAvailable
+ ? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows())
+ : _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows());
+ }
+ private _renderLoadingRows(): React.ReactNode {
+ return _.concat(this._renderDisconnectedHeaderRows(), this._renderLoadingBodyRows());
+ }
+ private _renderLoadingBodyRows(): React.ReactElement<{}> {
+ return (
+ <div key={BODY_ITEM_KEY} className="flex items-center" style={styles.loadingBody}>
+ <div className="mx-auto">
+ <CircularProgress size={40} thickness={5} />
+ </div>
+ </div>
+ );
+ }
private _renderDisconnectedHeaderRows(): React.ReactElement<{}> {
const userAddress = this.props.userAddress;
const primaryText = 'wallet';
return (
- <ListItem
+ <StandardIconRow
key={HEADER_ITEM_KEY}
- primaryText={primaryText.toUpperCase()}
- leftIcon={<ActionAccountBalanceWallet color={colors.mediumBlue} />}
- style={styles.paddedItem}
- innerDivStyle={styles.headerItemInnerDiv}
+ icon={<ActionAccountBalanceWallet color={colors.mediumBlue} />}
+ main={primaryText.toUpperCase()}
+ style={styles.borderedItem}
/>
);
}
@@ -229,11 +246,10 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
const primaryText = utils.getAddressBeginAndEnd(userAddress);
return (
<Link key={HEADER_ITEM_KEY} to={ACCOUNT_PATH} style={{ textDecoration: 'none' }}>
- <ListItem
- primaryText={primaryText}
- leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
- style={{ ...styles.paddedItem, ...styles.borderedItem }}
- innerDivStyle={styles.headerItemInnerDiv}
+ <StandardIconRow
+ icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
+ main={primaryText}
+ style={styles.borderedItem}
/>
</Link>
);
@@ -320,26 +336,23 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
private _renderEthRows(): React.ReactNode {
const icon = <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />;
const primaryText = this._renderAmount(
- this.props.userEtherBalanceInWei,
+ this.props.userEtherBalanceInWei || new BigNumber(0),
constants.DECIMAL_PLACES_ETH,
constants.ETHER_SYMBOL,
+ _.isUndefined(this.props.userEtherBalanceInWei),
);
const etherToken = this._getEthToken();
- const etherPrice = this.state.trackedTokenStateByAddress[etherToken.address].price;
+ const etherTokenState = this.state.trackedTokenStateByAddress[etherToken.address];
+ const etherPrice = etherTokenState.price;
const secondaryText = this._renderValue(
- this.props.userEtherBalanceInWei,
+ this.props.userEtherBalanceInWei || new BigNumber(0),
constants.DECIMAL_PLACES_ETH,
etherPrice,
+ _.isUndefined(this.props.userEtherBalanceInWei) || !etherTokenState.isLoaded,
);
const accessoryItemConfig = {
wrappedEtherDirection: Side.Deposit,
};
- const isInWrappedEtherState =
- !_.isUndefined(this.state.wrappedEtherDirection) &&
- this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection;
- const style = isInWrappedEtherState
- ? { ...walletItemStyles.focusedItem, ...styles.paddedItem }
- : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem };
const key = ETHER_ITEM_KEY;
return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig, 'eth-row');
}
@@ -360,10 +373,15 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
EtherscanLinkSuffixes.Address,
);
const icon = <TokenIcon token={token} diameter={ICON_DIMENSION} link={tokenLink} />;
- const primaryText = this._renderAmount(tokenState.balance, token.decimals, token.symbol);
- const secondaryText = this._renderValue(tokenState.balance, token.decimals, tokenState.price);
const isWeth = token.symbol === constants.ETHER_TOKEN_SYMBOL;
const wrappedEtherDirection = isWeth ? Side.Receive : undefined;
+ const primaryText = this._renderAmount(tokenState.balance, token.decimals, token.symbol, !tokenState.isLoaded);
+ const secondaryText = this._renderValue(
+ tokenState.balance,
+ token.decimals,
+ tokenState.price,
+ !tokenState.isLoaded,
+ );
const accessoryItemConfig: AccessoryItemConfig = {
wrappedEtherDirection,
allowanceToggleConfig: {
@@ -391,22 +409,24 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
): React.ReactNode {
const shouldShowWrapEtherItem =
!_.isUndefined(this.state.wrappedEtherDirection) &&
- this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection;
- const style = shouldShowWrapEtherItem
- ? { ...walletItemStyles.focusedItem, ...styles.paddedItem }
- : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem };
+ this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection &&
+ !_.isUndefined(this.props.userEtherBalanceInWei);
+ const additionalStyle = shouldShowWrapEtherItem ? walletItemStyles.focusedItem : styles.borderedItem;
+ const style = { ...styles.tokenItem, ...additionalStyle };
const etherToken = this._getEthToken();
return (
<div key={key} className={`flex flex-column ${className || ''}`}>
- <div className="flex items-center" style={style}>
- <div className="px2">{icon}</div>
- <div className="flex-none pr2 pt2 pb2">
- {primaryText}
- {secondaryText}
- </div>
- <div className="flex-auto" />
- <div>{this._renderAccessoryItems(accessoryItemConfig)}</div>
- </div>
+ <StandardIconRow
+ icon={icon}
+ main={
+ <div className="flex flex-column">
+ {primaryText}
+ <Container marginTop="3px">{secondaryText}</Container>
+ </div>
+ }
+ accessory={this._renderAccessoryItems(accessoryItemConfig)}
+ style={style}
+ />
{shouldShowWrapEtherItem && (
<WrapEtherItem
userAddress={this.props.userAddress}
@@ -458,21 +478,45 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
/>
);
}
- private _renderAmount(amount: BigNumber, decimals: number, symbol: string): React.ReactNode {
+ private _renderAmount(
+ amount: BigNumber,
+ decimals: number,
+ symbol: string,
+ isLoading: boolean = false,
+ ): React.ReactNode {
const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
const formattedAmount = unitAmount.toPrecision(TOKEN_AMOUNT_DISPLAY_PRECISION);
const result = `${formattedAmount} ${symbol}`;
- return <div style={styles.amountLabel}>{result}</div>;
+ return (
+ <PlaceHolder hideChildren={isLoading}>
+ <div style={styles.amountLabel}>{result}</div>
+ </PlaceHolder>
+ );
}
- private _renderValue(amount: BigNumber, decimals: number, price?: BigNumber): React.ReactNode {
- if (_.isUndefined(price)) {
- return null;
+ private _renderValue(
+ amount: BigNumber,
+ decimals: number,
+ price?: BigNumber,
+ isLoading: boolean = false,
+ ): React.ReactNode {
+ let result;
+ if (!isLoading) {
+ if (_.isUndefined(price)) {
+ result = '--';
+ } else {
+ const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
+ const value = unitAmount.mul(price);
+ const formattedAmount = value.toFixed(USD_DECIMAL_PLACES);
+ result = `$${formattedAmount}`;
+ }
+ } else {
+ result = '$0.00';
}
- const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
- const value = unitAmount.mul(price);
- const formattedAmount = value.toFixed(USD_DECIMAL_PLACES);
- const result = `$${formattedAmount}`;
- return <div style={styles.valueLabel}>{result}</div>;
+ return (
+ <PlaceHolder hideChildren={isLoading}>
+ <div style={styles.valueLabel}>{result}</div>
+ </PlaceHolder>
+ );
}
private _renderWrappedEtherButton(wrappedEtherDirection: Side): React.ReactNode {
const isWrappedEtherDirectionOpen = this.state.wrappedEtherDirection === wrappedEtherDirection;
@@ -589,4 +633,41 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
private _getEthToken(): Token {
return utils.getEthToken(this.props.tokenByAddress);
}
-} // tslint:disable:max-file-line-count
+}
+
+interface StandardIconRowProps {
+ icon: React.ReactNode;
+ main: React.ReactNode;
+ accessory?: React.ReactNode;
+ style?: React.CSSProperties;
+}
+const StandardIconRow = (props: StandardIconRowProps) => {
+ return (
+ <div className="flex items-center" style={props.style}>
+ <div className="p2">{props.icon}</div>
+ <div className="flex-none pr2 pt2 pb2">{props.main}</div>
+ <div className="flex-auto" />
+ <div>{props.accessory}</div>
+ </div>
+ );
+};
+interface PlaceHolderProps {
+ hideChildren: React.ReactNode;
+ children?: React.ReactNode;
+}
+const PlaceHolder = (props: PlaceHolderProps) => {
+ const rootBackgroundColor = props.hideChildren ? colors.lightGrey : 'transparent';
+ const rootStyle: React.CSSProperties = {
+ backgroundColor: rootBackgroundColor,
+ display: 'inline-block',
+ borderRadius: 2,
+ };
+ const childrenVisibility = props.hideChildren ? 'hidden' : 'visible';
+ const childrenStyle: React.CSSProperties = { visibility: childrenVisibility };
+ return (
+ <div style={rootStyle}>
+ <div style={childrenStyle}>{props.children}</div>
+ </div>
+ );
+};
+// tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
index 39a62e1fb..17fd8a19e 100644
--- a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
+++ b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
@@ -31,7 +31,7 @@ const styles: Styles = {
},
};
-const ITEM_HEIGHT = 292;
+const ITEM_HEIGHT = 381;
const METAMASK_ICON_WIDTH = 35;
const LEDGER_ICON_WIDTH = 30;
const BUTTON_BOTTOM_PADDING = 80;
diff --git a/packages/website/ts/containers/legacy_portal.ts b/packages/website/ts/containers/legacy_portal.ts
index 3b1172a44..eae450c21 100644
--- a/packages/website/ts/containers/legacy_portal.ts
+++ b/packages/website/ts/containers/legacy_portal.ts
@@ -24,7 +24,7 @@ interface ConnectedState {
providerType: ProviderType;
tokenByAddress: TokenByAddress;
lastForceTokenStateRefetch: number;
- userEtherBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
screenWidth: ScreenWidths;
shouldBlockchainErrDialogBeOpen: boolean;
userAddress: string;
diff --git a/packages/website/ts/containers/portal.ts b/packages/website/ts/containers/portal.ts
index 3f0feb6e9..b8c8fb999 100644
--- a/packages/website/ts/containers/portal.ts
+++ b/packages/website/ts/containers/portal.ts
@@ -21,7 +21,7 @@ interface ConnectedState {
providerType: ProviderType;
tokenByAddress: TokenByAddress;
lastForceTokenStateRefetch: number;
- userEtherBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
screenWidth: ScreenWidths;
shouldBlockchainErrDialogBeOpen: boolean;
userAddress: string;
diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts
index 84739192f..8202fb2ae 100644
--- a/packages/website/ts/containers/portal_onboarding_flow.ts
+++ b/packages/website/ts/containers/portal_onboarding_flow.ts
@@ -17,7 +17,7 @@ interface ConnectedState {
providerType: ProviderType;
injectedProviderName: string;
blockchainIsLoaded: boolean;
- userEthBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
tokenByAddress: TokenByAddress;
}
@@ -33,7 +33,7 @@ const mapStateToProps = (state: State): ConnectedState => ({
providerType: state.providerType,
injectedProviderName: state.injectedProviderName,
blockchainIsLoaded: state.blockchainIsLoaded,
- userEthBalanceInWei: state.userEtherBalanceInWei,
+ userEtherBalanceInWei: state.userEtherBalanceInWei,
tokenByAddress: state.tokenByAddress,
hasBeenSeen: state.hasPortalOnboardingBeenSeen,
});
diff --git a/packages/website/ts/containers/sol_cov_documentation.ts b/packages/website/ts/containers/sol_cov_documentation.ts
index 58755c1e0..bc05b6854 100644
--- a/packages/website/ts/containers/sol_cov_documentation.ts
+++ b/packages/website/ts/containers/sol_cov_documentation.ts
@@ -22,6 +22,9 @@ const docSections = {
installation: 'installation',
usage: 'usage',
coverageSubprovider: 'coverageSubprovider',
+ abstractArtifactAdapter: 'abstractArtifactAdapter',
+ solCompilerArtifactAdapter: 'solCompilerArtifactAdapter',
+ truffleArtifactAdapter: 'truffleArtifactAdapter',
types: docConstants.TYPES_SECTION_NAME,
};
@@ -34,7 +37,10 @@ const docsInfoConfig: DocsInfoConfig = {
introduction: [docSections.introduction],
install: [docSections.installation],
usage: [docSections.usage],
- coverageSubprovider: [docSections.coverageSubprovider],
+ 'coverage-subprovider': [docSections.coverageSubprovider],
+ 'abstract-artifact-adapter': [docSections.abstractArtifactAdapter],
+ 'sol-compiler-artifact-adapter': [docSections.solCompilerArtifactAdapter],
+ 'truffle-artifact-adapter': [docSections.truffleArtifactAdapter],
types: [docSections.types],
},
sectionNameToMarkdown: {
@@ -44,18 +50,40 @@ const docsInfoConfig: DocsInfoConfig = {
},
sectionNameToModulePath: {
[docSections.coverageSubprovider]: ['"sol-cov/src/coverage_subprovider"'],
+ [docSections.abstractArtifactAdapter]: ['"sol-cov/src/artifact_adapters/abstract_artifact_adapter"'],
+ [docSections.solCompilerArtifactAdapter]: ['"sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter"'],
+ [docSections.truffleArtifactAdapter]: ['"sol-cov/src/artifact_adapters/truffle_artifact_adapter"'],
[docSections.types]: ['"subproviders/src/types"', '"types/src/index"'],
},
menuSubsectionToVersionWhenIntroduced: {},
sections: docSections,
- visibleConstructors: [docSections.coverageSubprovider],
+ visibleConstructors: [
+ docSections.coverageSubprovider,
+ docSections.abstractArtifactAdapter,
+ docSections.solCompilerArtifactAdapter,
+ docSections.truffleArtifactAdapter,
+ ],
typeConfigs: {
// Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is
// currently no way to extract the re-exported types from index.ts via TypeDoc :(
- publicTypes: ['NextCallback', 'OnNextCompleted', 'ErrorCallback', 'JSONRPCRequestPayload'],
+ publicTypes: [
+ 'JSONRPCRequestPayload',
+ 'NextCallback',
+ 'ErrorCallback',
+ 'AbstractArtifactAdapter',
+ 'CoverageSubprovider',
+ 'TruffleArtifactAdapter',
+ 'SolCompilerArtifactAdapter',
+ 'ContractData',
+ ],
typeNameToExternalLink: {},
typeNameToPrefix: {},
- typeNameToDocSection: {},
+ typeNameToDocSection: {
+ AbstractArtifactAdapter: docSections.abstractArtifactAdapter,
+ CoverageSubprovider: docSections.coverageSubprovider,
+ TruffleArtifactAdapter: docSections.truffleArtifactAdapter,
+ SolCompilerArtifactAdapter: docSections.solCompilerArtifactAdapter,
+ },
},
};
const docsInfo = new DocsInfo(docsInfoConfig);
diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts
index 0b4cc3938..e0ce43ae5 100644
--- a/packages/website/ts/redux/dispatcher.ts
+++ b/packages/website/ts/redux/dispatcher.ts
@@ -155,7 +155,7 @@ export class Dispatcher {
type: ActionTypes.UpdateOrderECSignature,
});
}
- public updateUserWeiBalance(balance: BigNumber): void {
+ public updateUserWeiBalance(balance?: BigNumber): void {
this._dispatch({
data: balance,
type: ActionTypes.UpdateUserEtherBalance,
diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts
index 5c57792f7..9d3d8f7d9 100644
--- a/packages/website/ts/redux/reducer.ts
+++ b/packages/website/ts/redux/reducer.ts
@@ -39,7 +39,7 @@ export interface State {
tokenByAddress: TokenByAddress;
lastForceTokenStateRefetch: number;
userAddress: string;
- userEtherBalanceInWei: BigNumber;
+ userEtherBalanceInWei?: BigNumber;
portalOnboardingStep: number;
isPortalOnboardingShowing: boolean;
hasPortalOnboardingBeenSeen: boolean;
@@ -81,7 +81,7 @@ export const INITIAL_STATE: State = {
tokenByAddress: {},
lastForceTokenStateRefetch: moment().unix(),
userAddress: '',
- userEtherBalanceInWei: new BigNumber(0),
+ userEtherBalanceInWei: undefined,
userSuppliedOrderCache: undefined,
portalOnboardingStep: 0,
isPortalOnboardingShowing: false,
diff --git a/packages/website/ts/utils/order_parser.ts b/packages/website/ts/utils/order_parser.ts
new file mode 100644
index 000000000..be08da80e
--- /dev/null
+++ b/packages/website/ts/utils/order_parser.ts
@@ -0,0 +1,33 @@
+import { logUtils } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { portalOrderSchema } from 'ts/schemas/portal_order_schema';
+import { validator } from 'ts/schemas/validator';
+import { Order } from 'ts/types';
+
+export const orderParser = {
+ parse(queryString: string): Order | undefined {
+ if (queryString.length === 0) {
+ return undefined;
+ }
+ const queryParams = queryString.substring(1).split('&');
+ const orderQueryParam = _.find(queryParams, queryParam => {
+ const queryPair = queryParam.split('=');
+ return queryPair[0] === 'order';
+ });
+ if (_.isUndefined(orderQueryParam)) {
+ return undefined;
+ }
+ const orderPair = orderQueryParam.split('=');
+ if (orderPair.length !== 2) {
+ return undefined;
+ }
+ const order = JSON.parse(decodeURIComponent(orderPair[1]));
+ const validationResult = validator.validate(order, portalOrderSchema);
+ if (validationResult.errors.length > 0) {
+ logUtils.log(`Invalid shared order: ${validationResult.errors}`);
+ return undefined;
+ }
+ return order;
+ },
+};