aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/0x.js/CHANGELOG.md3
-rw-r--r--packages/0x.js/src/0x.ts29
-rw-r--r--packages/0x.js/src/order_watcher/event_watcher.ts8
-rw-r--r--packages/0x.js/src/order_watcher/order_state_watcher.ts7
-rw-r--r--packages/0x.js/src/types.ts6
-rw-r--r--packages/0x.js/test/order_state_watcher_test.ts89
-rw-r--r--packages/react-shared/CHANGELOG.md1
-rw-r--r--packages/react-shared/src/utils/colors.ts21
-rw-r--r--packages/website/public/images/metamask_icon.pngbin0 -> 5728 bytes
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx31
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx18
-rw-r--r--packages/website/ts/components/inputs/eth_amount_input.tsx14
-rw-r--r--packages/website/ts/components/inputs/token_amount_input.tsx19
-rw-r--r--packages/website/ts/components/portal.tsx5
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx3
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx (renamed from packages/website/ts/components/wallet.tsx)230
-rw-r--r--packages/website/ts/components/wallet/wallet_disconnected_item.tsx81
-rw-r--r--packages/website/ts/components/wallet/wrap_ether_item.tsx184
-rw-r--r--packages/website/ts/utils/mui_theme.ts10
-rw-r--r--packages/website/ts/utils/wallet_item_styles.ts7
20 files changed, 624 insertions, 142 deletions
diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md
index ca6b985d3..af63118a7 100644
--- a/packages/0x.js/CHANGELOG.md
+++ b/packages/0x.js/CHANGELOG.md
@@ -3,6 +3,9 @@
## v0.34.0 - _TBD_
* Fix the bug causing `zeroEx.exchange.fillOrdersUpToAsync` validation to fail if there were some extra orders passed (#470)
+ * Remove automatic instantiation of `zeroEx.orderStateWatcher` (#488)
+ * Add `zeroEx.createOrderStateWatcher` to allow creating arbitrary number of OrderStateWatchers (#488)
+ * Added `stateLayer` setting to `OrderStateWatcherConfig` so OrderStateWatcher can be set to monitor different blockchain state layers (#488)
## v0.33.2 - _March 18, 2018_
diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts
index 0dd728ff1..7627f1d6e 100644
--- a/packages/0x.js/src/0x.ts
+++ b/packages/0x.js/src/0x.ts
@@ -15,7 +15,7 @@ import { OrderStateWatcher } from './order_watcher/order_state_watcher';
import { zeroExConfigSchema } from './schemas/zero_ex_config_schema';
import { zeroExPrivateNetworkConfigSchema } from './schemas/zero_ex_private_network_config_schema';
import { zeroExPublicNetworkConfigSchema } from './schemas/zero_ex_public_network_config_schema';
-import { Web3Provider, ZeroExConfig, ZeroExError } from './types';
+import { OrderStateWatcherConfig, ZeroExConfig, ZeroExError } from './types';
import { assert } from './utils/assert';
import { constants } from './utils/constants';
import { decorators } from './utils/decorators';
@@ -57,11 +57,6 @@ export class ZeroEx {
* tokenTransferProxy smart contract.
*/
public proxy: TokenTransferProxyWrapper;
- /**
- * An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant
- * blockchain state changes.
- */
- public orderStateWatcher: OrderStateWatcher;
private _web3Wrapper: Web3Wrapper;
private _abiDecoder: AbiDecoder;
/**
@@ -197,13 +192,6 @@ export class ZeroEx {
config.tokenRegistryContractAddress,
);
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token);
- this.orderStateWatcher = new OrderStateWatcher(
- this._web3Wrapper,
- this._abiDecoder,
- this.token,
- this.exchange,
- config.orderWatcherConfig,
- );
}
/**
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
@@ -336,6 +324,21 @@ export class ZeroEx {
const txReceipt = await txReceiptPromise;
return txReceipt;
}
+ /**
+ * Instantiates and returns a new OrderStateWatcher instance.
+ * Defaults to watching the pending state.
+ * @param config The configuration object. Look up the type for the description.
+ * @return An instance of the 0x.js OrderStateWatcher class.
+ */
+ public createOrderStateWatcher(config?: OrderStateWatcherConfig) {
+ return new OrderStateWatcher(
+ this._web3Wrapper,
+ this._abiDecoder,
+ this.token,
+ this.exchange,
+ config,
+ );
+ }
/*
* HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from
* an `ExchangeWrapper`. `ExchangeWrapper` needs `TokenWrapper` to validate orders, creating a dependency cycle.
diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts
index 246ab8292..d01542a8c 100644
--- a/packages/0x.js/src/order_watcher/event_watcher.ts
+++ b/packages/0x.js/src/order_watcher/event_watcher.ts
@@ -22,8 +22,10 @@ export class EventWatcher {
private _pollingIntervalMs: number;
private _intervalIdIfExists?: NodeJS.Timer;
private _lastEvents: LogEntry[] = [];
- constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) {
+ private _stateLayer: BlockParamLiteral;
+ constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number, stateLayer: BlockParamLiteral = BlockParamLiteral.Latest) {
this._web3Wrapper = web3Wrapper;
+ this._stateLayer = stateLayer;
this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs)
? DEFAULT_EVENT_POLLING_INTERVAL_MS
: pollingIntervalIfExistsMs;
@@ -69,8 +71,8 @@ export class EventWatcher {
}
private async _getEventsAsync(): Promise<LogEntry[]> {
const eventFilter = {
- fromBlock: BlockParamLiteral.Pending,
- toBlock: BlockParamLiteral.Pending,
+ fromBlock: this._stateLayer,
+ toBlock: this._stateLayer,
};
const events = await this._web3Wrapper.getLogsAsync(eventFilter);
return events;
diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts
index 9cccadb7f..bfd250e21 100644
--- a/packages/0x.js/src/order_watcher/order_state_watcher.ts
+++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts
@@ -86,10 +86,13 @@ export class OrderStateWatcher {
this._abiDecoder = abiDecoder;
this._web3Wrapper = web3Wrapper;
const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
- this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs);
+ const stateLayer = _.isUndefined(config) || _.isUndefined(config.stateLayer)
+ ? BlockParamLiteral.Latest
+ : config.stateLayer;
+ this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs, stateLayer);
this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(
token,
- BlockParamLiteral.Pending,
+ stateLayer,
);
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange);
this._orderStateUtils = new OrderStateUtils(
diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts
index 38cfb6306..7f1f140ca 100644
--- a/packages/0x.js/src/types.ts
+++ b/packages/0x.js/src/types.ts
@@ -165,16 +165,18 @@ export type Web3Provider = Web3.Provider;
/*
* orderExpirationCheckingIntervalMs: How often to check for expired orders. Default: 50
- * eventPollingIntervalMs: How often to poll the Ethereum node for new events. Defaults: 200
+ * eventPollingIntervalMs: How often to poll the Ethereum node for new events. Default: 200
* expirationMarginMs: Amount of time before order expiry that you'd like to be notified
- * of an orders expiration. Defaults: 0
+ * of an orders expiration. Default: 0
* cleanupJobIntervalMs: How often to run a cleanup job which revalidates all the orders. Defaults: 1h
+ * stateLayer: Optional blockchain state layer OrderWatcher will monitor for new events. Default: latest
*/
export interface OrderStateWatcherConfig {
orderExpirationCheckingIntervalMs?: number;
eventPollingIntervalMs?: number;
expirationMarginMs?: number;
cleanupJobIntervalMs?: number;
+ stateLayer: BlockParamLiteral;
}
/*
diff --git a/packages/0x.js/test/order_state_watcher_test.ts b/packages/0x.js/test/order_state_watcher_test.ts
index d08272c3b..4f727d495 100644
--- a/packages/0x.js/test/order_state_watcher_test.ts
+++ b/packages/0x.js/test/order_state_watcher_test.ts
@@ -15,6 +15,9 @@ import {
ZeroEx,
ZeroExError,
} from '../src';
+import {
+ OrderStateWatcher,
+} from '../src/order_watcher/order_state_watcher';
import { DoneCallback } from '../src/types';
import { chaiSetup } from './utils/chai_setup';
@@ -43,6 +46,7 @@ describe('OrderStateWatcher', () => {
let maker: string;
let taker: string;
let signedOrder: SignedOrder;
+ let orderStateWatcher: OrderStateWatcher;
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
@@ -50,6 +54,7 @@ describe('OrderStateWatcher', () => {
const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
before(async () => {
zeroEx = new ZeroEx(web3.currentProvider, config);
+ orderStateWatcher = zeroEx.createOrderStateWatcher();
exchangeContractAddress = zeroEx.exchange.getContractAddress();
userAddresses = await zeroEx.getAvailableAddressesAsync();
[, maker, taker] = userAddresses;
@@ -76,17 +81,17 @@ describe('OrderStateWatcher', () => {
fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
- expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({
+ orderStateWatcher.addOrder(signedOrder);
+ expect((orderStateWatcher as any)._orderByOrderHash).to.include({
[orderHash]: signedOrder,
});
- let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
+ let dependentOrderHashes = (orderStateWatcher as any)._dependentOrderHashes;
expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash);
- zeroEx.orderStateWatcher.removeOrder(orderHash);
- expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({
+ orderStateWatcher.removeOrder(orderHash);
+ expect((orderStateWatcher as any)._orderByOrderHash).to.not.include({
[orderHash]: signedOrder,
});
- dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
+ dependentOrderHashes = (orderStateWatcher as any)._dependentOrderHashes;
expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined();
});
it('should no-op when removing a non-existing order', async () => {
@@ -103,23 +108,23 @@ describe('OrderStateWatcher', () => {
.split('')
.reverse()
.join('')}`;
- zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash);
+ orderStateWatcher.removeOrder(nonExistentOrderHash);
});
});
describe('#subscribe', async () => {
afterEach(async () => {
- zeroEx.orderStateWatcher.unsubscribe();
+ orderStateWatcher.unsubscribe();
});
it('should fail when trying to subscribe twice', async () => {
- zeroEx.orderStateWatcher.subscribe(_.noop);
- expect(() => zeroEx.orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent);
+ orderStateWatcher.subscribe(_.noop);
+ expect(() => orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent);
});
});
describe('tests with cleanup', async () => {
afterEach(async () => {
- zeroEx.orderStateWatcher.unsubscribe();
+ orderStateWatcher.unsubscribe();
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.removeOrder(orderHash);
+ orderStateWatcher.removeOrder(orderHash);
});
it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => {
(async () => {
@@ -131,14 +136,14 @@ describe('OrderStateWatcher', () => {
fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0));
})().catch(done);
});
@@ -151,11 +156,11 @@ describe('OrderStateWatcher', () => {
taker,
fillableAmount,
);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
throw new Error('OrderState callback fired for irrelevant order');
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
const notTheMaker = userAddresses[0];
const anyRecipient = taker;
const transferAmount = new BigNumber(2);
@@ -175,14 +180,14 @@ describe('OrderStateWatcher', () => {
fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
const invalidOrderState = orderState as OrderStateInvalid;
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
const anyRecipient = taker;
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance);
@@ -198,7 +203,7 @@ describe('OrderStateWatcher', () => {
fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
@@ -206,7 +211,7 @@ describe('OrderStateWatcher', () => {
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
const shouldThrowOnInsufficientBalanceOrAllowance = true;
await zeroEx.exchange.fillOrderAsync(
@@ -230,7 +235,7 @@ describe('OrderStateWatcher', () => {
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
const fillAmountInBaseUnits = new BigNumber(2);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.true();
@@ -247,7 +252,7 @@ describe('OrderStateWatcher', () => {
);
expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
const shouldThrowOnInsufficientBalanceOrAllowance = true;
await zeroEx.exchange.fillOrderAsync(
signedOrder,
@@ -272,8 +277,8 @@ describe('OrderStateWatcher', () => {
taker,
);
const callback = reportNodeCallbackErrors(done)();
- zeroEx.orderStateWatcher.addOrder(signedOrder);
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.subscribe(callback);
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0));
})().catch(done);
});
@@ -292,7 +297,7 @@ describe('OrderStateWatcher', () => {
);
const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.true();
const validOrderState = orderState as OrderStateValid;
@@ -305,7 +310,7 @@ describe('OrderStateWatcher', () => {
ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals),
);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
const shouldThrowOnInsufficientBalanceOrAllowance = true;
await zeroEx.exchange.fillOrderAsync(
signedOrder,
@@ -326,7 +331,7 @@ describe('OrderStateWatcher', () => {
);
const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
const validOrderState = orderState as OrderStateValid;
@@ -338,7 +343,7 @@ describe('OrderStateWatcher', () => {
changedMakerApprovalAmount,
);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount);
})().catch(done);
});
@@ -356,7 +361,7 @@ describe('OrderStateWatcher', () => {
const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
const transferAmount = makerBalance.sub(remainingAmount);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.true();
@@ -369,7 +374,7 @@ describe('OrderStateWatcher', () => {
remainingAmount,
);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.token.transferAsync(makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount);
})().catch(done);
});
@@ -391,7 +396,7 @@ describe('OrderStateWatcher', () => {
const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals);
const transferTokenAmount = makerFee.sub(remainingTokenAmount);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.true();
@@ -401,7 +406,7 @@ describe('OrderStateWatcher', () => {
remainingTokenAmount,
);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount);
})().catch(done);
});
@@ -425,7 +430,7 @@ describe('OrderStateWatcher', () => {
const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals);
const transferTokenAmount = makerFee.sub(remainingTokenAmount);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
const validOrderState = orderState as OrderStateValid;
@@ -434,7 +439,7 @@ describe('OrderStateWatcher', () => {
remainingFeeAmount,
);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount);
await zeroEx.token.transferAsync(
makerToken.address,
@@ -460,7 +465,7 @@ describe('OrderStateWatcher', () => {
feeRecipient,
);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
const validOrderState = orderState as OrderStateValid;
@@ -469,7 +474,7 @@ describe('OrderStateWatcher', () => {
fillableAmount,
);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.token.setProxyAllowanceAsync(
makerToken.address,
maker,
@@ -488,7 +493,7 @@ describe('OrderStateWatcher', () => {
fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
@@ -496,7 +501,7 @@ describe('OrderStateWatcher', () => {
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
})().catch(done);
@@ -512,7 +517,7 @@ describe('OrderStateWatcher', () => {
fillableAmount,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.false();
@@ -520,7 +525,7 @@ describe('OrderStateWatcher', () => {
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.exchange.cancelOrderAsync(
signedOrder,
fillableAmount.minus(remainingFillableAmountInBaseUnits),
@@ -539,7 +544,7 @@ describe('OrderStateWatcher', () => {
const cancelAmountInBaseUnits = new BigNumber(2);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
- zeroEx.orderStateWatcher.addOrder(signedOrder);
+ orderStateWatcher.addOrder(signedOrder);
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
expect(orderState.isValid).to.be.true();
@@ -548,7 +553,7 @@ describe('OrderStateWatcher', () => {
const orderRelevantState = validOrderState.orderRelevantState;
expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits);
});
- zeroEx.orderStateWatcher.subscribe(callback);
+ orderStateWatcher.subscribe(callback);
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits);
})().catch(done);
});
diff --git a/packages/react-shared/CHANGELOG.md b/packages/react-shared/CHANGELOG.md
index 51fb8e4b6..9fffd8ea3 100644
--- a/packages/react-shared/CHANGELOG.md
+++ b/packages/react-shared/CHANGELOG.md
@@ -4,3 +4,4 @@
* Added new colors (#468)
* Fix section and menuItem text display to replace dashes with spaces.
+ * Reorganized colors and added new ones
diff --git a/packages/react-shared/src/utils/colors.ts b/packages/react-shared/src/utils/colors.ts
index ea0165305..7613414ae 100644
--- a/packages/react-shared/src/utils/colors.ts
+++ b/packages/react-shared/src/utils/colors.ts
@@ -1,7 +1,6 @@
import { colors as materialUiColors } from 'material-ui/styles';
-export const colors = {
- ...materialUiColors,
+const baseColors = {
gray40: '#F8F8F8',
grey50: '#FAFAFA',
grey100: '#F5F5F5',
@@ -27,6 +26,7 @@ export const colors = {
lightBlue: '#60A4F4',
lightBlueA700: '#0091EA',
linkBlue: '#1D5CDE',
+ mediumBlue: '#488AEA',
darkBlue: '#4D5481',
turquois: '#058789',
lightPurple: '#A81CA6',
@@ -45,7 +45,22 @@ export const colors = {
orange: '#E69D00',
amber800: '#FF8F00',
darkYellow: '#caca03',
+};
+
+const appColors = {
+ // wallet specific colors
walletBoxShadow: 'rgba(56, 59, 137, 0.2)',
- walletBorder: '#f5f5f6',
+ walletBorder: '#ededee',
walletDefaultItemBackground: '#fbfbfc',
+ walletFocusedItemBackground: '#f0f1f4',
+ allowanceToggleShadow: 'rgba(0, 0, 0, 0)',
+ allowanceToggleOffTrack: '#adadad',
+ allowanceToggleOnTrack: baseColors.mediumBlue,
+ wrapEtherConfirmationButton: baseColors.mediumBlue,
+};
+
+export const colors = {
+ ...materialUiColors,
+ ...baseColors,
+ ...appColors,
};
diff --git a/packages/website/public/images/metamask_icon.png b/packages/website/public/images/metamask_icon.png
new file mode 100644
index 000000000..ab497ecb7
--- /dev/null
+++ b/packages/website/public/images/metamask_icon.png
Binary files differ
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
index da6f900e6..cfe75b751 100644
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -1,4 +1,4 @@
-import { constants as sharedConstants } from '@0xproject/react-shared';
+import { colors, constants as sharedConstants, Styles } from '@0xproject/react-shared';
import { BigNumber, logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import Toggle from 'material-ui/Toggle';
@@ -30,6 +30,31 @@ interface AllowanceToggleState {
prevAllowance: BigNumber;
}
+const styles: Styles = {
+ baseThumbStyle: {
+ height: 10,
+ width: 10,
+ top: 6,
+ backgroundColor: colors.white,
+ boxShadow: `0px 0px 0px ${colors.allowanceToggleShadow}`,
+ },
+ offThumbStyle: {
+ left: 4,
+ },
+ onThumbStyle: {
+ left: 25,
+ },
+ baseTrackStyle: {
+ width: 25,
+ },
+ offTrackStyle: {
+ backgroundColor: colors.allowanceToggleOffTrack,
+ },
+ onTrackStyle: {
+ backgroundColor: colors.allowanceToggleOnTrack,
+ },
+};
+
export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> {
constructor(props: AllowanceToggleProps) {
super(props);
@@ -54,6 +79,10 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow
disabled={this.state.isSpinnerVisible || this.props.isDisabled}
toggled={this._isAllowanceSet()}
onToggle={this._onToggleAllowanceAsync.bind(this)}
+ thumbStyle={{ ...styles.baseThumbStyle, ...styles.offThumbStyle }}
+ thumbSwitchedStyle={{ ...styles.baseThumbStyle, ...styles.onThumbStyle }}
+ trackStyle={{ ...styles.baseTrackStyle, ...styles.offTrackStyle }}
+ trackSwitchedStyle={{ ...styles.baseTrackStyle, ...styles.onTrackStyle }}
/>
</div>
{this.state.isSpinnerVisible && (
diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx
index 253b01871..e9b8dd369 100644
--- a/packages/website/ts/components/inputs/balance_bounded_input.tsx
+++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx
@@ -12,6 +12,7 @@ interface BalanceBoundedInputProps {
label?: string;
balance: BigNumber;
amount?: BigNumber;
+ hintText?: string;
onChange: ValidatedBigNumberCallback;
shouldShowIncompleteErrs?: boolean;
shouldCheckBalance: boolean;
@@ -19,6 +20,8 @@ interface BalanceBoundedInputProps {
onVisitBalancesPageClick?: () => void;
shouldHideVisitBalancesLink?: boolean;
isDisabled?: boolean;
+ shouldShowErrs?: boolean;
+ shouldShowUnderline?: boolean;
}
interface BalanceBoundedInputState {
@@ -31,6 +34,9 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
shouldShowIncompleteErrs: false,
shouldHideVisitBalancesLink: false,
isDisabled: false,
+ shouldShowErrs: true,
+ hintText: 'amount',
+ shouldShowUnderline: true,
};
constructor(props: BalanceBoundedInputProps) {
super(props);
@@ -71,9 +77,12 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
}
}
public render() {
- let errorText = this.state.errMsg;
- if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') {
- errorText = 'This field is required';
+ let errorText;
+ if (this.props.shouldShowErrs) {
+ errorText =
+ this.props.shouldShowIncompleteErrs && this.state.amountString === ''
+ ? 'This field is required'
+ : this.state.errMsg;
}
let label: React.ReactNode | string = '';
if (!_.isUndefined(this.props.label)) {
@@ -87,9 +96,10 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
floatingLabelStyle={{ color: colors.grey, width: 206 }}
errorText={errorText}
value={this.state.amountString}
- hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>}
+ hintText={<span style={{ textTransform: 'capitalize' }}>{this.props.hintText}</span>}
onChange={this._onValueChange.bind(this)}
underlineStyle={{ width: 'calc(100% + 50px)' }}
+ underlineShow={this.props.shouldShowUnderline}
disabled={this.props.isDisabled}
/>
);
diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx
index a66f92c8c..f3a879065 100644
--- a/packages/website/ts/components/inputs/eth_amount_input.tsx
+++ b/packages/website/ts/components/inputs/eth_amount_input.tsx
@@ -10,22 +10,31 @@ interface EthAmountInputProps {
label?: string;
balance: BigNumber;
amount?: BigNumber;
+ hintText?: string;
onChange: ValidatedBigNumberCallback;
shouldShowIncompleteErrs: boolean;
onVisitBalancesPageClick?: () => void;
shouldCheckBalance: boolean;
shouldHideVisitBalancesLink?: boolean;
+ shouldShowErrs?: boolean;
+ shouldShowUnderline?: boolean;
+ style?: React.CSSProperties;
}
interface EthAmountInputState {}
export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmountInputState> {
+ public static defaultProps: Partial<EthAmountInputProps> = {
+ shouldShowErrs: true,
+ shouldShowUnderline: true,
+ style: { height: 63 },
+ };
public render() {
const amount = this.props.amount
? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH)
: undefined;
return (
- <div className="flex overflow-hidden" style={{ height: 63 }}>
+ <div className="flex overflow-hidden" style={this.props.style}>
<BalanceBoundedInput
label={this.props.label}
balance={this.props.balance}
@@ -35,6 +44,9 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink}
+ hintText={this.props.hintText}
+ shouldShowErrs={this.props.shouldShowErrs}
+ shouldShowUnderline={this.props.shouldShowUnderline}
/>
<div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div>
</div>
diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx
index b55840fc4..9e638b67b 100644
--- a/packages/website/ts/components/inputs/token_amount_input.tsx
+++ b/packages/website/ts/components/inputs/token_amount_input.tsx
@@ -15,12 +15,16 @@ interface TokenAmountInputProps {
token: Token;
label?: string;
amount?: BigNumber;
+ hintText?: string;
shouldShowIncompleteErrs: boolean;
shouldCheckBalance: boolean;
shouldCheckAllowance: boolean;
onChange: ValidatedBigNumberCallback;
onVisitBalancesPageClick?: () => void;
lastForceTokenStateRefetch: number;
+ shouldShowErrs?: boolean;
+ shouldShowUnderline?: boolean;
+ style?: React.CSSProperties;
}
interface TokenAmountInputState {
@@ -29,7 +33,14 @@ interface TokenAmountInputState {
isBalanceAndAllowanceLoaded: boolean;
}
+const HEIGHT_WITH_LABEL = 84;
+const HEIGHT_WITHOUT_LABEL = 62;
+
export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> {
+ public static defaultProps: Partial<TokenAmountInputProps> = {
+ shouldShowErrs: true,
+ shouldShowUnderline: true,
+ };
private _isUnmounted: boolean;
constructor(props: TokenAmountInputProps) {
super(props);
@@ -64,8 +75,11 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals)
: undefined;
const hasLabel = !_.isUndefined(this.props.label);
+ const style = !_.isUndefined(this.props.style)
+ ? this.props.style
+ : { height: hasLabel ? HEIGHT_WITH_LABEL : HEIGHT_WITHOUT_LABEL };
return (
- <div className="flex overflow-hidden" style={{ height: hasLabel ? 84 : 62 }}>
+ <div className="flex overflow-hidden" style={style}>
<BalanceBoundedInput
label={this.props.label}
amount={amount}
@@ -76,6 +90,9 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
isDisabled={!this.state.isBalanceAndAllowanceLoaded}
+ hintText={this.props.hintText}
+ shouldShowErrs={this.props.shouldShowErrs}
+ shouldShowUnderline={this.props.shouldShowUnderline}
/>
<div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div>
</div>
diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx
index 59eaca67e..ceb0ecc72 100644
--- a/packages/website/ts/components/portal.tsx
+++ b/packages/website/ts/components/portal.tsx
@@ -19,7 +19,7 @@ import { TokenBalances } from 'ts/components/token_balances';
import { TopBar } from 'ts/components/top_bar/top_bar';
import { TradeHistory } from 'ts/components/trade_history/trade_history';
import { FlashMessage } from 'ts/components/ui/flash_message';
-import { Wallet } from 'ts/components/wallet';
+import { Wallet } from 'ts/components/wallet/wallet';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
import { localStorage } from 'ts/local_storage/local_storage';
import { Dispatcher } from 'ts/redux/dispatcher';
@@ -305,6 +305,9 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
trackedTokens={trackedTokens}
userEtherBalanceInWei={this.props.userEtherBalanceInWei}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this.onToggleLedgerDialog.bind(this)}
/>
</div>
</div>
diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx
index 89c506d0e..221c34f8c 100644
--- a/packages/website/ts/components/top_bar/provider_display.tsx
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -28,7 +28,8 @@ 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 isExternallyInjectedProvider =
+ this.props.providerType === ProviderType.Injected && this.props.injectedProviderName !== '0x Public';
const displayAddress = isAddressAvailable
? utils.getAddressBeginAndEnd(this.props.userAddress)
: isExternallyInjectedProvider ? 'Account locked' : '0x0000...0000';
diff --git a/packages/website/ts/components/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 8c6ef9cad..39c95d31c 100644
--- a/packages/website/ts/components/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -10,8 +10,10 @@ import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import FlatButton from 'material-ui/FlatButton';
import { List, ListItem } from 'material-ui/List';
+import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import NavigationArrowDownward from 'material-ui/svg-icons/navigation/arrow-downward';
import NavigationArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward';
+import Close from 'material-ui/svg-icons/navigation/close';
import * as React from 'react';
import ReactTooltip = require('react-tooltip');
import firstBy = require('thenby');
@@ -20,14 +22,26 @@ import { Blockchain } from 'ts/blockchain';
import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle';
import { Identicon } from 'ts/components/ui/identicon';
import { TokenIcon } from 'ts/components/ui/token_icon';
+import { WalletDisconnectedItem } from 'ts/components/wallet/wallet_disconnected_item';
+import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item';
import { Dispatcher } from 'ts/redux/dispatcher';
-import { BalanceErrs, BlockchainErrs, Token, TokenByAddress, TokenState, TokenStateByAddress } from 'ts/types';
+import {
+ BalanceErrs,
+ BlockchainErrs,
+ ProviderType,
+ Side,
+ Token,
+ TokenByAddress,
+ TokenState,
+ TokenStateByAddress,
+} from 'ts/types';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
+import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
export interface WalletProps {
- userAddress?: string;
- networkId?: number;
+ userAddress: string;
+ networkId: number;
blockchain: Blockchain;
blockchainIsLoaded: boolean;
blockchainErr: BlockchainErrs;
@@ -36,15 +50,14 @@ export interface WalletProps {
trackedTokens: Token[];
userEtherBalanceInWei: BigNumber;
lastForceTokenStateRefetch: number;
+ injectedProviderName: string;
+ providerType: ProviderType;
+ onToggleLedgerDialog: () => void;
}
interface WalletState {
trackedTokenStateByAddress: TokenStateByAddress;
-}
-
-enum WrappedEtherAction {
- Wrap,
- Unwrap,
+ wrappedEtherDirection?: Side;
}
interface AllowanceToggleConfig {
@@ -53,7 +66,7 @@ interface AllowanceToggleConfig {
}
interface AccessoryItemConfig {
- wrappedEtherAction?: WrappedEtherAction;
+ wrappedEtherDirection?: Side;
allowanceToggleConfig?: AllowanceToggleConfig;
}
@@ -87,20 +100,19 @@ const styles: Styles = {
},
tokenItem: {
backgroundColor: colors.walletDefaultItemBackground,
- paddingTop: 8,
- paddingBottom: 8,
},
- headerItem: {
- paddingTop: 8,
- paddingBottom: 8,
- },
- wrappedEtherButtonLabel: {
- fontSize: 12,
+ wrappedEtherOpenButtonLabel: {
+ fontSize: 10,
},
amountLabel: {
fontWeight: 'bold',
color: colors.black,
},
+ paddedItem: {
+ paddingTop: 8,
+ paddingBottom: 8,
+ },
+ accessoryItemsContainer: { width: 150, right: 8 },
};
const ETHER_ICON_PATH = '/images/ether.png';
@@ -118,6 +130,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens);
this.state = {
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
+ wrappedEtherDirection: undefined,
};
}
public componentWillMount() {
@@ -164,34 +177,56 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
return <div style={styles.wallet}>{isReadyToRender && this._renderRows()}</div>;
}
private _renderRows() {
+ const isAddressAvailable = !_.isEmpty(this.props.userAddress);
return (
<List style={styles.list}>
- {_.concat(
- this._renderHeaderRows(),
- this._renderEthRows(),
- this._renderTokenRows(),
- this._renderFooterRows(),
- )}
+ {isAddressAvailable
+ ? _.concat(
+ this._renderConnectedHeaderRows(),
+ this._renderEthRows(),
+ this._renderTokenRows(),
+ this._renderFooterRows(),
+ )
+ : _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())}
</List>
);
}
- private _renderHeaderRows() {
+ private _renderDisconnectedHeaderRows() {
+ const userAddress = this.props.userAddress;
+ const primaryText = 'wallet';
+ return (
+ <ListItem
+ primaryText={primaryText.toUpperCase()}
+ leftIcon={<ActionAccountBalanceWallet color={colors.mediumBlue} />}
+ style={styles.paddedItem}
+ innerDivStyle={styles.headerItemInnerDiv}
+ />
+ );
+ }
+ private _renderDisconnectedRows() {
+ return (
+ <WalletDisconnectedItem
+ providerType={this.props.providerType}
+ injectedProviderName={this.props.injectedProviderName}
+ onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ />
+ );
+ }
+ private _renderConnectedHeaderRows() {
const userAddress = this.props.userAddress;
const primaryText = utils.getAddressBeginAndEnd(userAddress);
return (
<ListItem
primaryText={primaryText}
leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
- style={{ ...styles.headerItem, ...styles.borderedItem }}
+ style={{ ...styles.paddedItem, ...styles.borderedItem }}
innerDivStyle={styles.headerItemInnerDiv}
/>
);
}
private _renderFooterRows() {
const primaryText = '+ other tokens';
- return (
- <ListItem primaryText={primaryText} style={styles.borderedItem} innerDivStyle={styles.footerItemInnerDiv} />
- );
+ return <ListItem primaryText={primaryText} innerDivStyle={styles.footerItemInnerDiv} />;
}
private _renderEthRows() {
const primaryText = this._renderAmount(
@@ -200,16 +235,40 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
ETHER_SYMBOL,
);
const accessoryItemConfig = {
- wrappedEtherAction: WrappedEtherAction.Wrap,
+ 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 etherToken = this._getEthToken();
return (
- <ListItem
- primaryText={primaryText}
- leftIcon={<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />}
- rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
- style={{ ...styles.tokenItem, ...styles.borderedItem }}
- innerDivStyle={styles.tokenItemInnerDiv}
- />
+ <div>
+ <ListItem
+ primaryText={primaryText}
+ leftIcon={<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />}
+ rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
+ disableTouchRipple={true}
+ style={style}
+ innerDivStyle={styles.tokenItemInnerDiv}
+ />
+ {isInWrappedEtherState && (
+ <WrapEtherItem
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ direction={accessoryItemConfig.wrappedEtherDirection}
+ etherToken={etherToken}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)}
+ refetchEthTokenStateAsync={this._refetchTokenStateAsync.bind(this, etherToken.address)}
+ />
+ )}
+ </div>
);
}
private _renderTokenRows() {
@@ -229,32 +288,56 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
EtherscanLinkSuffixes.Address,
);
const amount = this._renderAmount(tokenState.balance, token.decimals, token.symbol);
- const wrappedEtherAction = token.symbol === ETHER_TOKEN_SYMBOL ? WrappedEtherAction.Unwrap : undefined;
+ const wrappedEtherDirection = token.symbol === ETHER_TOKEN_SYMBOL ? Side.Receive : undefined;
const accessoryItemConfig: AccessoryItemConfig = {
- wrappedEtherAction,
+ wrappedEtherDirection,
allowanceToggleConfig: {
token,
tokenState,
},
};
+ const shouldShowWrapEtherItem =
+ !_.isUndefined(this.state.wrappedEtherDirection) &&
+ this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection;
+ const style = shouldShowWrapEtherItem
+ ? { ...walletItemStyles.focusedItem, ...styles.paddedItem }
+ : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem };
+ const etherToken = this._getEthToken();
return (
- <ListItem
- primaryText={amount}
- leftIcon={this._renderTokenIcon(token, tokenLink)}
- rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
- style={{ ...styles.tokenItem, ...styles.borderedItem }}
- innerDivStyle={styles.tokenItemInnerDiv}
- />
+ <div>
+ <ListItem
+ primaryText={amount}
+ leftIcon={this._renderTokenIcon(token, tokenLink)}
+ rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
+ disableTouchRipple={true}
+ style={style}
+ innerDivStyle={styles.tokenItemInnerDiv}
+ />
+ {shouldShowWrapEtherItem && (
+ <WrapEtherItem
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ direction={accessoryItemConfig.wrappedEtherDirection}
+ etherToken={etherToken}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)}
+ refetchEthTokenStateAsync={this._refetchTokenStateAsync.bind(this, etherToken.address)}
+ />
+ )}
+ </div>
);
}
private _renderAccessoryItems(config: AccessoryItemConfig) {
- const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherAction);
+ const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherDirection);
const shouldShowToggle = !_.isUndefined(config.allowanceToggleConfig);
return (
- <div style={{ width: 160 }}>
+ <div style={styles.accessoryItemsContainer}>
<div className="flex">
<div className="flex-auto">
- {shouldShowWrappedEtherAction && this._renderWrappedEtherButton(config.wrappedEtherAction)}
+ {shouldShowWrappedEtherAction && this._renderWrappedEtherButton(config.wrappedEtherDirection)}
</div>
<div className="flex-last py1">
{shouldShowToggle && this._renderAllowanceToggle(config.allowanceToggleConfig)}
@@ -297,28 +380,38 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
}
}
- private _renderWrappedEtherButton(action: WrappedEtherAction) {
+ private _renderWrappedEtherButton(wrappedEtherDirection: Side) {
+ const isWrappedEtherDirectionOpen = this.state.wrappedEtherDirection === wrappedEtherDirection;
let buttonLabel;
let buttonIcon;
- switch (action) {
- case WrappedEtherAction.Wrap:
- buttonLabel = 'wrap';
- buttonIcon = <NavigationArrowDownward />;
- break;
- case WrappedEtherAction.Unwrap:
- buttonLabel = 'unwrap';
- buttonIcon = <NavigationArrowUpward />;
- break;
- default:
- throw utils.spawnSwitchErr('wrappedEtherAction', action);
+ if (isWrappedEtherDirectionOpen) {
+ buttonLabel = 'cancel';
+ buttonIcon = <Close />;
+ } else {
+ switch (wrappedEtherDirection) {
+ case Side.Deposit:
+ buttonLabel = 'wrap';
+ buttonIcon = <NavigationArrowDownward />;
+ break;
+ case Side.Receive:
+ buttonLabel = 'unwrap';
+ buttonIcon = <NavigationArrowUpward />;
+ break;
+ default:
+ throw utils.spawnSwitchErr('wrappedEtherDirection', wrappedEtherDirection);
+ }
}
+ const onClick = isWrappedEtherDirectionOpen
+ ? this._closeWrappedEtherActionRow.bind(this)
+ : this._openWrappedEtherActionRow.bind(this, wrappedEtherDirection);
return (
<FlatButton
label={buttonLabel}
labelPosition="after"
primary={true}
icon={buttonIcon}
- labelStyle={styles.wrappedEtherButtonLabel}
+ labelStyle={styles.wrappedEtherOpenButtonLabel}
+ onClick={onClick}
/>
);
}
@@ -370,4 +463,19 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
},
});
}
+ private _openWrappedEtherActionRow(wrappedEtherDirection: Side) {
+ this.setState({
+ wrappedEtherDirection,
+ });
+ }
+ private _closeWrappedEtherActionRow() {
+ this.setState({
+ wrappedEtherDirection: undefined,
+ });
+ }
+ private _getEthToken() {
+ const tokens = _.values(this.props.tokenByAddress);
+ const etherToken = _.find(tokens, { symbol: ETHER_TOKEN_SYMBOL });
+ return etherToken;
+ }
}
diff --git a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
new file mode 100644
index 000000000..89e32f7be
--- /dev/null
+++ b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
@@ -0,0 +1,81 @@
+import { colors, Styles } from '@0xproject/react-shared';
+import FlatButton from 'material-ui/FlatButton';
+import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
+import * as React from 'react';
+
+import { ProviderType } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+
+export interface WalletDisconnectedItemProps {
+ providerType: ProviderType;
+ injectedProviderName: string;
+ onToggleLedgerDialog: () => void;
+}
+
+const styles: Styles = {
+ button: {
+ border: colors.walletBorder,
+ borderStyle: 'solid',
+ borderWidth: 1,
+ height: 80,
+ },
+ hrefAdjustment: {
+ paddingTop: 20, // HACK: For some reason when we set the href prop of a FlatButton material-ui reduces the top padding
+ },
+ otherWalletText: {
+ fontSize: 14,
+ color: colors.grey500,
+ textDecoration: 'underline',
+ },
+};
+
+const ITEM_HEIGHT = 292;
+const METAMASK_ICON_WIDTH = 35;
+const LEDGER_ICON_WIDTH = 30;
+const BUTTON_BOTTOM_PADDING = 80;
+
+export const WalletDisconnectedItem: React.StatelessComponent<WalletDisconnectedItemProps> = (
+ props: WalletDisconnectedItemProps,
+) => {
+ const isExternallyInjectedProvider =
+ props.providerType === ProviderType.Injected && props.injectedProviderName !== '0x Public';
+ return (
+ <div className="flex flex-center">
+ <div className="mx-auto">
+ <div className="table" style={{ height: ITEM_HEIGHT }}>
+ <div className="table-cell align-middle">
+ <ProviderButton isExternallyInjectedProvider={isExternallyInjectedProvider} />
+ <div className="flex flex-center py2" style={{ paddingBottom: BUTTON_BOTTOM_PADDING }}>
+ <div className="mx-auto">
+ <div onClick={props.onToggleLedgerDialog} style={{ cursor: 'pointer' }}>
+ <img src="/images/ledger_icon.png" style={{ width: LEDGER_ICON_WIDTH }} />
+ <span className="px1" style={styles.otherWalletText}>
+ user other wallet
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+interface ProviderButtonProps {
+ isExternallyInjectedProvider: boolean;
+}
+
+const ProviderButton: React.StatelessComponent<ProviderButtonProps> = (props: ProviderButtonProps) => (
+ <FlatButton
+ label={props.isExternallyInjectedProvider ? 'Please unlock account' : 'Get Metamask Wallet Extension'}
+ labelStyle={{ color: colors.black }}
+ labelPosition="after"
+ primary={true}
+ icon={<img src="/images/metamask_icon.png" width={METAMASK_ICON_WIDTH.toString()} />}
+ style={props.isExternallyInjectedProvider ? styles.button : { ...styles.button, ...styles.hrefAdjustment }}
+ href={props.isExternallyInjectedProvider ? undefined : constants.URL_METAMASK_CHROME_STORE}
+ target={props.isExternallyInjectedProvider ? undefined : '_blank'}
+ disabled={props.isExternallyInjectedProvider}
+ />
+);
diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx
new file mode 100644
index 000000000..3a876721a
--- /dev/null
+++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx
@@ -0,0 +1,184 @@
+import { ZeroEx } from '0x.js';
+import { colors, Styles } from '@0xproject/react-shared';
+import { BigNumber, logUtils } from '@0xproject/utils';
+import * as _ from 'lodash';
+import FlatButton from 'material-ui/FlatButton';
+import { ListItem } from 'material-ui/List';
+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 { Dispatcher } from 'ts/redux/dispatcher';
+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';
+import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
+
+export interface WrapEtherItemProps {
+ userAddress: string;
+ networkId: number;
+ blockchain: Blockchain;
+ dispatcher: Dispatcher;
+ userEtherBalanceInWei: BigNumber;
+ direction: Side;
+ etherToken: Token;
+ lastForceTokenStateRefetch: number;
+ onConversionSuccessful?: () => void;
+ refetchEthTokenStateAsync: () => Promise<void>;
+}
+
+interface WrapEtherItemState {
+ currentInputAmount?: BigNumber;
+ currentInputHasErrors: boolean;
+ isEthConversionHappening: boolean;
+}
+
+const styles: Styles = {
+ topLabel: { color: colors.black, fontSize: 11 },
+ inputContainer: {
+ backgroundColor: colors.white,
+ borderBottomRightRadius: 3,
+ borderBottomLeftRadius: 3,
+ borderTopRightRadius: 3,
+ borderTopLeftRadius: 3,
+ padding: 4,
+ width: 125,
+ },
+ ethAmountInput: { height: 32 },
+ innerDiv: { paddingLeft: 60, paddingTop: 0 },
+ wrapEtherConfirmationButtonContainer: { width: 128, top: 16 },
+ wrapEtherConfirmationButtonLabel: {
+ fontSize: 10,
+ color: colors.white,
+ },
+};
+
+export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEtherItemState> {
+ constructor(props: WrapEtherItemProps) {
+ super(props);
+ this.state = {
+ currentInputAmount: undefined,
+ currentInputHasErrors: false,
+ isEthConversionHappening: false,
+ };
+ }
+ public render() {
+ const etherBalanceInEth = ZeroEx.toUnitAmount(this.props.userEtherBalanceInWei, constants.DECIMAL_PLACES_ETH);
+ const isWrappingEth = this.props.direction === Side.Deposit;
+ const topLabelText = isWrappingEth ? 'Convert ETH into WETH 1:1' : 'Convert WETH into ETH 1:1';
+ return (
+ <ListItem
+ primaryText={
+ <div>
+ <div style={styles.topLabel}>{topLabelText}</div>
+ <div style={styles.inputContainer}>
+ {isWrappingEth ? (
+ <EthAmountInput
+ balance={etherBalanceInEth}
+ amount={this.state.currentInputAmount}
+ hintText="0.00"
+ onChange={this._onValueChange.bind(this)}
+ shouldCheckBalance={true}
+ shouldShowIncompleteErrs={false}
+ shouldShowErrs={false}
+ shouldShowUnderline={false}
+ style={styles.ethAmountInput}
+ />
+ ) : (
+ <TokenAmountInput
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ blockchain={this.props.blockchain}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ token={this.props.etherToken}
+ shouldShowIncompleteErrs={false}
+ shouldCheckBalance={true}
+ shouldCheckAllowance={false}
+ onChange={this._onValueChange.bind(this)}
+ amount={this.state.currentInputAmount}
+ hintText="0.00"
+ shouldShowErrs={false} // TODO: error handling
+ shouldShowUnderline={false}
+ style={styles.ethAmountInput}
+ />
+ )}
+ </div>
+ </div>
+ }
+ secondaryTextLines={2}
+ disableTouchRipple={true}
+ style={walletItemStyles.focusedItem}
+ innerDivStyle={styles.innerDiv}
+ leftIcon={this.state.isEthConversionHappening && this._renderIsEthConversionHappeningSpinner()}
+ rightAvatar={this._renderWrapEtherConfirmationButton()}
+ />
+ );
+ }
+ private _onValueChange(isValid: boolean, amount?: BigNumber) {
+ this.setState({
+ currentInputAmount: amount,
+ currentInputHasErrors: !isValid,
+ });
+ }
+ private _renderIsEthConversionHappeningSpinner() {
+ return (
+ <div className="pl1" style={{ paddingTop: 10 }}>
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </div>
+ );
+ }
+ private _renderWrapEtherConfirmationButton() {
+ const isWrappingEth = this.props.direction === Side.Deposit;
+ const labelText = isWrappingEth ? 'wrap' : 'unwrap';
+ return (
+ <div style={styles.wrapEtherConfirmationButtonContainer}>
+ <FlatButton
+ backgroundColor={colors.wrapEtherConfirmationButton}
+ label={labelText}
+ labelStyle={styles.wrapEtherConfirmationButtonLabel}
+ onClick={this._wrapEtherConfirmationAction.bind(this)}
+ disabled={this.state.isEthConversionHappening}
+ />
+ </div>
+ );
+ }
+ private async _wrapEtherConfirmationAction() {
+ this.setState({
+ isEthConversionHappening: true,
+ });
+ try {
+ const etherToken = this.props.etherToken;
+ const amountToConvert = this.state.currentInputAmount;
+ if (this.props.direction === Side.Deposit) {
+ await this.props.blockchain.convertEthToWrappedEthTokensAsync(etherToken.address, amountToConvert);
+ const ethAmount = ZeroEx.toUnitAmount(amountToConvert, constants.DECIMAL_PLACES_ETH);
+ this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
+ } else {
+ await this.props.blockchain.convertWrappedEthTokensToEthAsync(etherToken.address, amountToConvert);
+ const tokenAmount = ZeroEx.toUnitAmount(amountToConvert, etherToken.decimals);
+ this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
+ }
+ await this.props.refetchEthTokenStateAsync();
+ this.props.onConversionSuccessful();
+ } catch (err) {
+ const errMsg = `${err}`;
+ if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ } else if (!utils.didUserDenyWeb3Request(errMsg)) {
+ logUtils.log(`Unexpected error encountered: ${err}`);
+ logUtils.log(err.stack);
+ const errorMsg =
+ this.props.direction === Side.Deposit
+ ? 'Failed to wrap your ETH. Please try again.'
+ : 'Failed to unwrap your WETH. Please try again.';
+ this.props.dispatcher.showFlashMessage(errorMsg);
+ await errorReporter.reportAsync(err);
+ }
+ }
+ this.setState({
+ isEthConversionHappening: false,
+ });
+ }
+}
diff --git a/packages/website/ts/utils/mui_theme.ts b/packages/website/ts/utils/mui_theme.ts
index 41bc2844b..d611f0895 100644
--- a/packages/website/ts/utils/mui_theme.ts
+++ b/packages/website/ts/utils/mui_theme.ts
@@ -9,9 +9,9 @@ export const muiTheme = getMuiTheme({
},
palette: {
accent1Color: colors.lightBlueA700,
- pickerHeaderColor: colors.lightBlue,
- primary1Color: colors.lightBlue,
- primary2Color: colors.lightBlue,
+ pickerHeaderColor: colors.mediumBlue,
+ primary1Color: colors.mediumBlue,
+ primary2Color: colors.mediumBlue,
textColor: colors.grey700,
},
datePicker: {
@@ -29,8 +29,4 @@ export const muiTheme = getMuiTheme({
selectColor: colors.darkestGrey,
selectTextColor: colors.darkestGrey,
},
- toggle: {
- thumbOnColor: colors.limeGreen,
- trackOnColor: colors.lightGreen,
- },
});
diff --git a/packages/website/ts/utils/wallet_item_styles.ts b/packages/website/ts/utils/wallet_item_styles.ts
new file mode 100644
index 000000000..1ad304ce1
--- /dev/null
+++ b/packages/website/ts/utils/wallet_item_styles.ts
@@ -0,0 +1,7 @@
+import { colors, Styles } from '@0xproject/react-shared';
+
+export const styles: Styles = {
+ focusedItem: {
+ backgroundColor: colors.walletFocusedItemBackground,
+ },
+};