From 71dba035d8e76eb4ef3ac33ede24ee2fc595437a Mon Sep 17 00:00:00 2001 From: Daniel Kmak Date: Thu, 27 Sep 2018 14:52:03 +0200 Subject: [PATCH 1/9] [FEATURE] Estimate gas used for scheduling. --- .../components/AdvancedGas.tsx | 4 +- .../TXMetaDataPanel/components/FeeSummary.tsx | 5 +- common/features/schedule/actions.ts | 25 ++++ common/features/schedule/sagas.ts | 123 +++++++++++++++--- common/features/schedule/types.ts | 26 +++- common/features/selectors.ts | 3 +- .../transaction/network/sagas.spec.ts | 27 ++-- common/features/transaction/network/sagas.ts | 16 +-- common/libs/scheduling/index.ts | 5 +- 9 files changed, 185 insertions(+), 49 deletions(-) diff --git a/common/components/TXMetaDataPanel/components/AdvancedGas.tsx b/common/components/TXMetaDataPanel/components/AdvancedGas.tsx index b57281da59b..eaf17f2f00d 100644 --- a/common/components/TXMetaDataPanel/components/AdvancedGas.tsx +++ b/common/components/TXMetaDataPanel/components/AdvancedGas.tsx @@ -158,13 +158,13 @@ class AdvancedGas extends React.Component { ); } - private getScheduleFeeFormula({ gasPriceWei, scheduleGasLimit, fee, usd }: RenderData) { + private getScheduleFeeFormula({ gasLimit, gasPriceWei, scheduleGasLimit, fee, usd }: RenderData) { const { scheduleGasPrice, timeBounty } = this.props; return (
{timeBounty && timeBounty.value && timeBounty.value.toString()} + {gasPriceWei} *{' '} - {EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT.toString()} +{' '} + {gasLimit.toString()} +{' '} {scheduleGasPrice && scheduleGasPrice.value && scheduleGasPrice.value.toString()} * ({EAC_SCHEDULING_CONFIG.FUTURE_EXECUTION_COST.toString()}{' '} + {scheduleGasLimit}) = {fee} {usd && ~= ${usd} USD}
diff --git a/common/components/TXMetaDataPanel/components/FeeSummary.tsx b/common/components/TXMetaDataPanel/components/FeeSummary.tsx index 367a282a318..f9bd279a52f 100644 --- a/common/components/TXMetaDataPanel/components/FeeSummary.tsx +++ b/common/components/TXMetaDataPanel/components/FeeSummary.tsx @@ -127,7 +127,7 @@ class FeeSummary extends React.Component { } private calculateSchedulingFee() { - const { gasPrice, scheduleGasLimit, scheduleGasPrice, timeBounty } = this.props; + const { gasLimit, gasPrice, scheduleGasLimit, scheduleGasPrice, timeBounty } = this.props; return ( gasPrice.value && @@ -137,7 +137,8 @@ class FeeSummary extends React.Component { scheduleGasLimit.value, gasPrice.value, scheduleGasPrice.value, - timeBounty.value + timeBounty.value, + gasLimit.value ) ); } diff --git a/common/features/schedule/actions.ts b/common/features/schedule/actions.ts index 464bbb0eb8f..ecc95eb7b65 100644 --- a/common/features/schedule/actions.ts +++ b/common/features/schedule/actions.ts @@ -157,3 +157,28 @@ export const setCurrentWindowStart = ( payload }); //#endregion Window Size + +//#region Estimate Scheduling Gas +export type TEstimateSchedulingGasRequested = typeof estimateSchedulingGasRequested; +export const estimateSchedulingGasRequested = ( + payload: types.EstimateSchedulingGasRequestedAction['payload'] +): types.EstimateSchedulingGasRequestedAction => ({ + type: types.ScheduleActions.ESTIMATE_SCHEDULING_GAS_REQUESTED, + payload +}); + +export type TEstimateSchedulingGasSucceeded = typeof estimateSchedulingGasSucceeded; +export const estimateSchedulingGasSucceeded = (): types.EstimateSchedulingGasSucceededAction => ({ + type: types.ScheduleActions.ESTIMATE_SCHEDULING_GAS_SUCCEEDED +}); + +export type TEstimateSchedulingGasFailed = typeof estimateSchedulingGasFailed; +export const estimateSchedulingGasFailed = (): types.EstimateSchedulingGasFailedAction => ({ + type: types.ScheduleActions.ESTIMATE_SCHEDULING_GAS_FAILED +}); + +export type TEstimateSchedulingGasTimedout = typeof estimateSchedulingGasTimedout; +export const estimateSchedulingGasTimedout = (): types.EstimateSchedulingGasTimeoutAction => ({ + type: types.ScheduleActions.ESTIMATE_SCHEDULING_GAS_TIMEDOUT +}); +//#endregion Estimate Scheduling Gas diff --git a/common/features/schedule/sagas.ts b/common/features/schedule/sagas.ts index 5cd279bd6ba..ceffd240536 100644 --- a/common/features/schedule/sagas.ts +++ b/common/features/schedule/sagas.ts @@ -1,5 +1,16 @@ -import { SagaIterator, delay } from 'redux-saga'; -import { select, take, call, apply, fork, put, all, takeLatest } from 'redux-saga/effects'; +import { SagaIterator, buffers, delay } from 'redux-saga'; +import { + select, + take, + call, + apply, + fork, + put, + all, + takeLatest, + actionChannel, + race +} from 'redux-saga/effects'; import BN from 'bn.js'; import { toTokenBase, Wei, fromWei } from 'libs/units'; @@ -18,6 +29,13 @@ import { import * as types from './types'; import * as actions from './actions'; import * as selectors from './selectors'; +import { IGetTransaction } from 'features/types'; +import * as networkActions from '../transaction/network/actions'; +import { getTransactionFields, IHexStrTransaction } from 'libs/transaction'; +import { localGasEstimation } from '../transaction/network/sagas'; +import { INode } from 'libs/nodes/INode'; +import { IWallet } from 'libs/wallet'; +import { walletSelectors } from 'features/wallet'; //#region Schedule Timestamp export function* setCurrentScheduleTimestampSaga({ @@ -55,23 +73,21 @@ export const currentScheduleTimezone = takeLatest( export function* setGasLimitForSchedulingSaga({ payload: { value: useScheduling } }: types.SetSchedulingToggleAction): SagaIterator { - // setGasLimitForSchedulingSaga - const gasLimit = useScheduling - ? EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT - : EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_LIMIT_FALLBACK; - - yield put( - transactionFieldsActions.setGasLimitField({ - raw: gasLimit.toString(), - value: gasLimit - }) - ); - - // setDefaultTimeBounty if (useScheduling) { + // setDefaultTimeBounty yield put( actions.setCurrentTimeBounty(fromWei(EAC_SCHEDULING_CONFIG.TIME_BOUNTY_DEFAULT, 'ether')) ); + } else { + // setGasLimitForSchedulingSaga + const gasLimit = EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_LIMIT_FALLBACK; + + yield put( + transactionFieldsActions.setGasLimitField({ + raw: gasLimit.toString(), + value: gasLimit + }) + ); } } @@ -189,6 +205,8 @@ export function* shouldValidateParams(): SagaIterator { continue; } + yield call(estimateGasForScheduling); + yield call(checkSchedulingParametersValidity); } } @@ -221,9 +239,83 @@ function* checkSchedulingParametersValidity() { ); } +function* estimateGasForScheduling() { + const { transaction }: IGetTransaction = yield select(derivedSelectors.getSchedulingTransaction); + + const { gasLimit, gasPrice, nonce, chainId, ...rest }: IHexStrTransaction = yield call( + getTransactionFields, + transaction + ); + + yield put(networkActions.estimateGasRequested(rest)); +} + export const schedulingParamsValidity = fork(shouldValidateParams); //#endregion Params Validity +//#region Estimate Scheduling Gas +export function* estimateSchedulingGas(): SagaIterator { + const requestChan = yield actionChannel( + types.ScheduleActions.ESTIMATE_SCHEDULING_GAS_REQUESTED, + buffers.sliding(1) + ); + + while (true) { + const autoGasLimitEnabled: boolean = yield select(configMetaSelectors.getAutoGasLimitEnabled); + const isOffline = yield select(configMetaSelectors.getOffline); + + if (!autoGasLimitEnabled) { + continue; + } + + if (isOffline) { + const gasLimit = EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT; + const gasSetOptions = { + raw: gasLimit.toString(), + value: gasLimit + }; + + yield put(actions.setScheduleGasLimitField(gasSetOptions)); + + yield put(actions.estimateSchedulingGasSucceeded()); + + continue; + } + + const { payload }: types.EstimateSchedulingGasRequestedAction = yield take(requestChan); + // debounce 250 ms + yield call(delay, 250); + const node: INode = yield select(configNodesSelectors.getNodeLib); + const walletInst: IWallet = yield select(walletSelectors.getWalletInst); + try { + const from: string = yield apply(walletInst, walletInst.getAddressString); + const txObj = { ...payload, from }; + + const { gasLimit } = yield race({ + gasLimit: apply(node, node.estimateGas, [txObj]), + timeout: call(delay, 10000) + }); + if (gasLimit) { + const gasSetOptions = { + raw: gasLimit.toString(), + value: gasLimit + }; + + yield put(actions.setScheduleGasLimitField(gasSetOptions)); + + yield put(actions.estimateSchedulingGasSucceeded()); + } else { + yield put(actions.estimateSchedulingGasTimedout()); + yield call(localGasEstimation, payload); + } + } catch (e) { + yield put(actions.estimateSchedulingGasFailed()); + yield call(localGasEstimation, payload); + } + } +} +//#endregion Estimate Scheduling Gas + export function* scheduleSaga(): SagaIterator { yield all([ currentWindowSize, @@ -233,6 +325,7 @@ export function* scheduleSaga(): SagaIterator { currentSchedulingToggle, currentScheduleTimezone, mirrorTimeBountyToDeposit, + fork(estimateSchedulingGas), schedulingParamsValidity ]); } diff --git a/common/features/schedule/types.ts b/common/features/schedule/types.ts index fce1f10b167..b49b7e8b7a6 100644 --- a/common/features/schedule/types.ts +++ b/common/features/schedule/types.ts @@ -1,4 +1,5 @@ import { Wei } from 'libs/units'; +import { IHexStrTransaction } from 'libs/transaction'; export interface ScheduleState { schedulingToggle: SetSchedulingToggleAction['payload']; @@ -32,7 +33,11 @@ export enum ScheduleActions { TYPE_SET = 'SCHEDULE_TYPE_SET', TOGGLE_SET = 'SCHEDULING_TOGGLE_SET', DEPOSIT_FIELD_SET = 'SCHEDULE_DEPOSIT_FIELD_SET', - PARAMS_VALIDITY_SET = 'SCHEDULE_PARAMS_VALIDITY_SET' + PARAMS_VALIDITY_SET = 'SCHEDULE_PARAMS_VALIDITY_SET', + ESTIMATE_SCHEDULING_GAS_REQUESTED = 'ESTIMATE_SCHEDULING_GAS_REQUESTED', + ESTIMATE_SCHEDULING_GAS_SUCCEEDED = 'ESTIMATE_SCHEDULING_GAS_SUCCEEDED', + ESTIMATE_SCHEDULING_GAS_FAILED = 'ESTIMATE_SCHEDULING_GAS_FAILED', + ESTIMATE_SCHEDULING_GAS_TIMEDOUT = 'ESTIMATE_SCHEDULING_GAS_TIMEDOUT' } //#region Fields @@ -195,4 +200,23 @@ export interface SetCurrentWindowStartAction { export type WindowStartCurrentAction = SetCurrentWindowStartAction; //#endregion Window Size +//#region Estimate Scheduling Gas +export interface EstimateSchedulingGasRequestedAction { + type: ScheduleActions.ESTIMATE_SCHEDULING_GAS_REQUESTED; + payload: Partial; +} + +export interface EstimateSchedulingGasSucceededAction { + type: ScheduleActions.ESTIMATE_SCHEDULING_GAS_SUCCEEDED; +} + +export interface EstimateSchedulingGasFailedAction { + type: ScheduleActions.ESTIMATE_SCHEDULING_GAS_FAILED; +} + +export interface EstimateSchedulingGasTimeoutAction { + type: ScheduleActions.ESTIMATE_SCHEDULING_GAS_TIMEDOUT; +} +//#endregion Estimate Scheduling Gas + export type ScheduleAction = ScheduleFieldAction; diff --git a/common/features/selectors.ts b/common/features/selectors.ts index abc7a661471..1d7d0c73a0c 100644 --- a/common/features/selectors.ts +++ b/common/features/selectors.ts @@ -262,6 +262,7 @@ export const getSchedulingTransaction = (state: AppState): IGetTransaction => { const scheduleGasPrice = scheduleSelectors.getScheduleGasPrice(state); const scheduleGasLimit = scheduleSelectors.getScheduleGasLimit(state); const scheduleType = scheduleSelectors.getScheduleType(state); + const gasLimit = transactionFieldsSelectors.getGasLimit(state); const endowment = calcEACEndowment( scheduleGasLimit.value, @@ -303,7 +304,7 @@ export const getSchedulingTransaction = (state: AppState): IGetTransaction => { const transactionOptions = { to: getSchedulerAddress(scheduleType.value, configSelectors.getNetworkConfig(state)), data: transactionData, - gasLimit: EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT, + gasLimit: gasLimit.value || new BN('0'), gasPrice: gasPrice.value, nonce: Nonce('0'), value: endowment diff --git a/common/features/transaction/network/sagas.spec.ts b/common/features/transaction/network/sagas.spec.ts index ec01b279cc4..3f49f7d37ed 100644 --- a/common/features/transaction/network/sagas.spec.ts +++ b/common/features/transaction/network/sagas.spec.ts @@ -18,7 +18,7 @@ import { makeTransaction, getTransactionFields } from 'libs/transaction'; import * as derivedSelectors from 'features/selectors'; import { configMetaTypes, configMetaSelectors, configNodesSelectors } from 'features/config'; import { walletTypes, walletSelectors } from 'features/wallet'; -import { scheduleActions, scheduleSelectors } from 'features/schedule'; +import { scheduleSelectors } from 'features/schedule'; import { notificationsActions } from 'features/notifications'; import { transactionFieldsTypes, transactionFieldsActions } from '../fields'; import * as transactionTypes from '../types'; @@ -141,8 +141,14 @@ describe('Network Sagas', () => { expect(gen.next(tx).value).toEqual(call(getTransactionFields, transaction)); }); + it('should select isSchedulingEnabled', () => { + expect(gen.next(transactionFields).value).toEqual( + select(scheduleSelectors.isSchedulingEnabled) + ); + }); + it('should put estimatedGasRequested with rest', () => { - expect(gen.next(transactionFields).value).toEqual(put(actions.estimateGasRequested(rest))); + expect(gen.next(false).value).toEqual(put(actions.estimateGasRequested(rest))); }); }); @@ -241,28 +247,13 @@ describe('Network Sagas', () => { ); }); - it('should select isSchedulingEnabled', () => { + it('should put setGasLimitField', () => { gens.timeOutCase = gens.successCase.clone(); expect(gens.successCase.next(successfulGasEstimationResult).value).toEqual( - select(scheduleSelectors.isSchedulingEnabled) - ); - }); - - it('should put setGasLimitField', () => { - gens.scheduleCase = gens.successCase.clone(); - const notScheduling = null as any; - expect(gens.successCase.next(notScheduling).value).toEqual( put(transactionFieldsActions.setGasLimitField(gasSetOptions)) ); }); - it('should put setScheduleGasLimitField', () => { - const scheduling = { value: true } as any; - expect(gens.scheduleCase.next(scheduling).value).toEqual( - put(scheduleActions.setScheduleGasLimitField(gasSetOptions)) - ); - }); - it('should put estimateGasSucceeded', () => { expect(gens.successCase.next().value).toEqual(put(actions.estimateGasSucceeded())); }); diff --git a/common/features/transaction/network/sagas.ts b/common/features/transaction/network/sagas.ts index 6febc022b48..9ecbd7e8aac 100644 --- a/common/features/transaction/network/sagas.ts +++ b/common/features/transaction/network/sagas.ts @@ -115,7 +115,13 @@ export function* shouldEstimateGas(): SagaIterator { rest.to = undefined as any; } - yield put(actions.estimateGasRequested(rest)); + const scheduling: boolean = yield select(scheduleSelectors.isSchedulingEnabled); + + if (scheduling) { + yield put(scheduleActions.estimateSchedulingGasRequested(rest)); + } else { + yield put(actions.estimateGasRequested(rest)); + } } } @@ -152,13 +158,7 @@ export function* estimateGas(): SagaIterator { value: gasLimit }; - const scheduling: boolean = yield select(scheduleSelectors.isSchedulingEnabled); - - if (scheduling) { - yield put(scheduleActions.setScheduleGasLimitField(gasSetOptions)); - } else { - yield put(transactionFieldsActions.setGasLimitField(gasSetOptions)); - } + yield put(transactionFieldsActions.setGasLimitField(gasSetOptions)); yield put(actions.estimateGasSucceeded()); } else { diff --git a/common/libs/scheduling/index.ts b/common/libs/scheduling/index.ts index affac838213..6e1034ee71d 100644 --- a/common/libs/scheduling/index.ts +++ b/common/libs/scheduling/index.ts @@ -99,13 +99,14 @@ export const calcEACTotalCost = ( callGas: Wei, gasPrice: Wei, callGasPrice: Wei | null, - timeBounty: Wei | null + timeBounty: Wei | null, + gasLimit: Wei | null ) => { if (!callGasPrice) { callGasPrice = gasPriceToBase(EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_PRICE_FALLBACK); } - const deployCost = gasPrice.mul(EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT); + const deployCost = gasPrice.mul(gasLimit || new BN('0')); const futureExecutionCost = calcEACFutureExecutionCost(callGas, callGasPrice, timeBounty); From f071a83c45365bc9abfa516dc8927bcfd46ee82e Mon Sep 17 00:00:00 2001 From: Daniel Kmak Date: Mon, 1 Oct 2018 18:43:25 +0200 Subject: [PATCH 2/9] [BUGFIX] Fix send all functionality when scheduling is enabled. --- common/features/transaction/sagas.spec.ts | 9 ++++++- common/features/transaction/sagas.ts | 29 ++++++++++++++++++++++- common/libs/scheduling/index.ts | 3 ++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/common/features/transaction/sagas.spec.ts b/common/features/transaction/sagas.spec.ts index 600b4254c84..03e2c025ba2 100644 --- a/common/features/transaction/sagas.spec.ts +++ b/common/features/transaction/sagas.spec.ts @@ -17,6 +17,7 @@ import { transactionMetaActions, transactionMetaSelectors } from './meta'; import * as actions from './actions'; import * as sagas from './sagas'; import * as helpers from './helpers'; +import * as scheduleSelectors from 'features/schedule/selectors'; configuredStore.getState(); @@ -620,8 +621,14 @@ describe('transaction: Sagas', () => { const remainder = currentBalance.sub(totalCost); const rawVersion = fromWei(remainder, 'ether'); - it('should put setValueField', () => { + it('should select isSchedulingEnabled', () => { expect(gens.gen.next(totalCost).value).toEqual( + select(scheduleSelectors.isSchedulingEnabled) + ); + }); + + it('should put setValueField', () => { + expect(gens.gen.next(false).value).toEqual( put( transactionFieldsActions.setValueField({ raw: rawVersion, diff --git a/common/features/transaction/sagas.ts b/common/features/transaction/sagas.ts index a1b272ea80c..7072f838083 100644 --- a/common/features/transaction/sagas.ts +++ b/common/features/transaction/sagas.ts @@ -19,6 +19,7 @@ import { IGetTransaction, ICurrentValue } from 'features/types'; import { AppState } from 'features/reducers'; import * as derivedSelectors from 'features/selectors'; import * as configSelectors from 'features/config/selectors'; +import * as scheduleSelectors from 'features/schedule/selectors'; import { ensTypes, ensActions, ensSelectors } from 'features/ens'; import { walletTypes, walletSelectors } from 'features/wallet'; import { notificationsActions } from 'features/notifications'; @@ -35,6 +36,7 @@ import { transactionSignTypes, transactionSignSagas } from './sign'; import * as types from './types'; import * as actions from './actions'; import * as helpers from './helpers'; +import { calcEACFutureExecutionCost, EAC_SCHEDULING_CONFIG } from 'libs/scheduling'; //#region Current @@ -209,7 +211,32 @@ export function* handleSendEverything(): SagaIterator { return yield put(setter({ raw: '0', value: null })); } if (etherTransaction) { - const remainder = currentBalance.sub(totalCost); + let remainder = currentBalance.sub(totalCost); + + const isSchedulingEnabled: boolean = yield select(scheduleSelectors.isSchedulingEnabled); + + if (isSchedulingEnabled) { + const scheduleGasLimit = yield select(scheduleSelectors.getScheduleGasLimit); + const scheduleGasPrice = yield select(scheduleSelectors.getScheduleGasPrice); + const timeBounty = yield select(scheduleSelectors.getTimeBounty); + + let gasLimit = scheduleGasLimit.value as BN; + + if (gasLimit.gt(new BN('100000'))) { + gasLimit = gasLimit.add(EAC_SCHEDULING_CONFIG.SEND_ALL_ESTIMATION_MARGIN); + } else { + gasLimit = EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT; + } + + const futureExecutionSchedulingCost = calcEACFutureExecutionCost( + gasLimit, + scheduleGasPrice.value, + timeBounty.value + ); + + remainder = remainder.sub(futureExecutionSchedulingCost); + } + const rawVersion = fromWei(remainder, 'ether'); yield put(setter({ raw: rawVersion, value: remainder })); diff --git a/common/libs/scheduling/index.ts b/common/libs/scheduling/index.ts index 6e1034ee71d..439b981b8f0 100644 --- a/common/libs/scheduling/index.ts +++ b/common/libs/scheduling/index.ts @@ -38,7 +38,8 @@ export const EAC_SCHEDULING_CONFIG = { SCHEDULE_TIMESTAMP_FORMAT: 'YYYY-MM-DD HH:mm:ss', DEFAULT_SCHEDULING_METHOD: 'time', ALLOW_SCHEDULING_MIN_AFTER_NOW: 5, - BOUNTY_TO_DEPOSIT_MULTIPLIER: 2 + BOUNTY_TO_DEPOSIT_MULTIPLIER: 2, + SEND_ALL_ESTIMATION_MARGIN: Wei('30000') }; export const EAC_ADDRESSES: IEacAddresses = { From c33d7fbea2027ab09d9574b57a6a603f9501c234 Mon Sep 17 00:00:00 2001 From: Kuzirashi Date: Tue, 23 Oct 2018 12:31:06 +0200 Subject: [PATCH 3/9] [FEATURE] Enable scheduling token transfers. --- .../components/AwaitingMiningModal/index.scss | 9 + .../components/AwaitingMiningModal/index.tsx | 42 + .../components/Body/Body.tsx | 12 + .../components/Body/components/Addresses.tsx | 34 +- common/components/index.ts | 1 + .../SchedulingToggle/SchedulingToggle.tsx | 8 +- .../SendScheduleTransactionButton.tsx | 26 +- .../components/Fields/Fields.tsx | 8 +- common/features/config/selectors.ts | 10 +- common/features/schedule/actions.ts | 20 +- common/features/schedule/reducer.ts | 8 +- common/features/schedule/sagas.ts | 103 +- common/features/schedule/selectors.ts | 12 +- common/features/schedule/types.ts | 27 +- common/features/selectors.ts | 53 +- .../transaction/network/sagas.spec.ts | 5 +- common/features/transaction/network/sagas.ts | 7 +- common/libs/erc20.ts | 56 +- .../scheduling/contracts/RequestFactory.ts | 7 +- common/libs/scheduling/contracts/Scheduler.ts | 42 + common/libs/scheduling/index.ts | 45 +- common/translations/lang/en.json | 10 +- yarn.lock | 1917 +++++++++++++++++ 23 files changed, 2366 insertions(+), 96 deletions(-) create mode 100644 common/components/AwaitingMiningModal/index.scss create mode 100644 common/components/AwaitingMiningModal/index.tsx create mode 100644 common/libs/scheduling/contracts/Scheduler.ts diff --git a/common/components/AwaitingMiningModal/index.scss b/common/components/AwaitingMiningModal/index.scss new file mode 100644 index 00000000000..9f06357dbc3 --- /dev/null +++ b/common/components/AwaitingMiningModal/index.scss @@ -0,0 +1,9 @@ +@import 'common/sass/variables'; + +.AwaitingMiningModal { + &-content { + text-align: center; + max-width: 40rem; + word-break: break-word; + } +} diff --git a/common/components/AwaitingMiningModal/index.tsx b/common/components/AwaitingMiningModal/index.tsx new file mode 100644 index 00000000000..c4bf0350693 --- /dev/null +++ b/common/components/AwaitingMiningModal/index.tsx @@ -0,0 +1,42 @@ +import React, { ReactElement } from 'react'; +import translate, { translateRaw } from 'translations'; +import Modal from 'components/ui/Modal'; +import './index.scss'; +import { Spinner } from 'components/ui'; +import { ETHTxExplorer } from 'config'; + +interface Props { + isOpen: boolean; + message?: ReactElement; + transactionHash: string; +} + +export default class AwaitingMiningModal extends React.Component { + public render() { + return ( + {}} + > +
+ +
+
+ {translate('SCHEDULE_TRANSACTION_MINING_PART_1')} + + {translate('SCHEDULE_TRANSACTION_MINING_PART_2')} + +
+
+ {this.props.message} +
+
+ ); + } +} diff --git a/common/components/ConfirmationModal/components/Body/Body.tsx b/common/components/ConfirmationModal/components/Body/Body.tsx index a959e358a84..8008c82c0b5 100644 --- a/common/components/ConfirmationModal/components/Body/Body.tsx +++ b/common/components/ConfirmationModal/components/Body/Body.tsx @@ -9,12 +9,16 @@ import { Addresses } from './components/Addresses'; import { Amounts } from './components/Amounts'; import { Details } from './components/Details'; import './Body.scss'; +import { scheduleSelectors } from 'features/schedule'; +import * as selectors from 'features/selectors'; interface State { showDetails: boolean; } interface StateProps { + isToken: boolean; + isSchedulingEnabled: boolean; network: NetworkConfig; } @@ -37,6 +41,12 @@ class BodyClass extends React.Component { {this.props.network.isTestnet && (

Testnet Transaction

)} + {this.props.isSchedulingEnabled && + this.props.isToken && ( +

+ {translate('SCHEDULE_TOKEN_TRANSFER_NOTICE')} +

+ )} - + {!isOffline && ( + + )} ); } @@ -105,6 +109,7 @@ export default class TokenBalances extends React.PureComponent { custom={token.custom} decimal={token.decimal} tracked={trackedTokens[token.symbol]} + isOffline={isOffline} toggleTracked={!hasSavedWalletTokens && this.toggleTrack} onRemove={this.props.onRemoveCustomToken} /> @@ -112,6 +117,10 @@ export default class TokenBalances extends React.PureComponent { )} + ) : isOffline ? ( +
+ {translate('SCAN_TOKENS_OFFLINE')} +
) : (
{translate('SCAN_TOKENS_FAIL_NO_TOKENS')}
)} diff --git a/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx b/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx index 33515440421..fd5b34b0aa2 100644 --- a/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx +++ b/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx @@ -14,6 +14,7 @@ interface Props { custom?: boolean; decimal: number; tracked: boolean; + isOffline: boolean; toggleTracked: ToggleTrackedFn | false; onRemove(symbol: string): void; } @@ -27,7 +28,7 @@ export default class TokenRow extends React.PureComponent { }; public render() { - const { balance, symbol, custom, decimal, tracked } = this.props; + const { balance, symbol, custom, decimal, tracked, isOffline } = this.props; const { showLongBalance } = this.state; return ( @@ -43,20 +44,22 @@ export default class TokenRow extends React.PureComponent { /> )} - - - - - + {!isOffline && ( + + + + + + )} {symbol} {!!custom && ( diff --git a/common/components/BalanceSidebar/TokenBalances/index.scss b/common/components/BalanceSidebar/TokenBalances/index.scss index 0c0c324b99b..8372ec1f147 100644 --- a/common/components/BalanceSidebar/TokenBalances/index.scss +++ b/common/components/BalanceSidebar/TokenBalances/index.scss @@ -64,9 +64,4 @@ color: shade-dark(0.7); } } - - &-offline { - margin-bottom: 0; - text-align: center; - } } diff --git a/common/components/BalanceSidebar/TokenBalances/index.tsx b/common/components/BalanceSidebar/TokenBalances/index.tsx index 798dbba0124..081a25792c4 100644 --- a/common/components/BalanceSidebar/TokenBalances/index.tsx +++ b/common/components/BalanceSidebar/TokenBalances/index.tsx @@ -1,129 +1,127 @@ -import React from 'react'; -import { connect } from 'react-redux'; - -import translate from 'translations'; -import { Token } from 'types/network'; -import { AppState } from 'features/reducers'; -import * as selectors from 'features/selectors'; -import { configSelectors, configMetaSelectors } from 'features/config'; -import { customTokensActions } from 'features/customTokens'; -import { walletTypes, walletActions, walletSelectors } from 'features/wallet'; -import Spinner from 'components/ui/Spinner'; -import Balances from './Balances'; -import './index.scss'; - -interface StateProps { - wallet: AppState['wallet']['inst']; - walletConfig: AppState['wallet']['config']; - tokens: Token[]; - tokenBalances: walletTypes.TokenBalance[]; - tokensError: AppState['wallet']['tokensError']; - isTokensLoading: AppState['wallet']['isTokensLoading']; - hasSavedWalletTokens: AppState['wallet']['hasSavedWalletTokens']; - isOffline: AppState['config']['meta']['offline']; -} -interface ActionProps { - attemptAddCustomToken: customTokensActions.TAttemptAddCustomToken; - removeCustomToken: customTokensActions.TRemoveCustomToken; - scanWalletForTokens: walletActions.TScanWalletForTokens; - setWalletTokens: walletActions.TSetWalletTokens; - refreshTokenBalances: walletActions.TRefreshTokenBalances; -} -type Props = StateProps & ActionProps; - -class TokenBalances extends React.Component { - public render() { - const { - tokens, - walletConfig, - tokenBalances, - hasSavedWalletTokens, - isTokensLoading, - tokensError, - isOffline - } = this.props; - - const walletTokens = walletConfig ? walletConfig.tokens : []; - - let content; - if (isOffline) { - content = ( -
{translate('SCAN_TOKENS_OFFLINE')}
- ); - } else if (tokensError) { - content = ( -
-
{tokensError}
- -
- ); - } else if (isTokensLoading) { - content = ( -
- -
- ); - } else if (!walletTokens) { - content = ( - - ); - } else { - const shownBalances = tokenBalances.filter(t => walletTokens.includes(t.symbol)); - - content = ( - - ); - } - - return ( -
-
{translate('SIDEBAR_TOKENBAL')}
- {content} -
- ); - } - - private scanWalletForTokens = () => { - if (this.props.wallet) { - this.props.scanWalletForTokens(this.props.wallet); - this.setState({ hasScanned: true }); - } - }; -} - -function mapStateToProps(state: AppState): StateProps { - return { - wallet: walletSelectors.getWalletInst(state), - walletConfig: walletSelectors.getWalletConfig(state), - tokens: configSelectors.getAllTokens(state), - tokenBalances: selectors.getTokenBalances(state), - tokensError: state.wallet.tokensError, - isTokensLoading: state.wallet.isTokensLoading, - hasSavedWalletTokens: state.wallet.hasSavedWalletTokens, - isOffline: configMetaSelectors.getOffline(state) - }; -} - -export default connect(mapStateToProps, { - attemptAddCustomToken: customTokensActions.attemptAddCustomToken, - removeCustomToken: customTokensActions.removeCustomToken, - scanWalletForTokens: walletActions.scanWalletForTokens, - setWalletTokens: walletActions.setWalletTokens, - refreshTokenBalances: walletActions.refreshTokenBalances -})(TokenBalances); +import React from 'react'; +import { connect } from 'react-redux'; + +import translate from 'translations'; +import { Token } from 'types/network'; +import { AppState } from 'features/reducers'; +import * as selectors from 'features/selectors'; +import { configSelectors, configMetaSelectors } from 'features/config'; +import { customTokensActions } from 'features/customTokens'; +import { walletTypes, walletActions, walletSelectors } from 'features/wallet'; +import Spinner from 'components/ui/Spinner'; +import Balances from './Balances'; +import './index.scss'; + +interface StateProps { + wallet: AppState['wallet']['inst']; + walletConfig: AppState['wallet']['config']; + tokens: Token[]; + tokenBalances: walletTypes.TokenBalance[]; + tokensError: AppState['wallet']['tokensError']; + isTokensLoading: AppState['wallet']['isTokensLoading']; + hasSavedWalletTokens: AppState['wallet']['hasSavedWalletTokens']; + isOffline: AppState['config']['meta']['offline']; +} +interface ActionProps { + attemptAddCustomToken: customTokensActions.TAttemptAddCustomToken; + removeCustomToken: customTokensActions.TRemoveCustomToken; + scanWalletForTokens: walletActions.TScanWalletForTokens; + setWalletTokens: walletActions.TSetWalletTokens; + refreshTokenBalances: walletActions.TRefreshTokenBalances; +} +type Props = StateProps & ActionProps; + +class TokenBalances extends React.Component { + public render() { + const { + tokens, + walletConfig, + tokenBalances, + hasSavedWalletTokens, + isTokensLoading, + tokensError, + isOffline + } = this.props; + + const walletTokens = walletConfig ? walletConfig.tokens : []; + + let content; + if (tokensError) { + content = ( +
+
{tokensError}
+ +
+ ); + } else if (isTokensLoading) { + content = ( +
+ +
+ ); + } else if (!walletTokens && !isOffline) { + content = ( + + ); + } else { + const shownBalances = + (walletTokens && tokenBalances.filter(t => walletTokens.includes(t.symbol))) || []; + + content = ( + + ); + } + + return ( +
+
{translate('SIDEBAR_TOKENBAL')}
+ {content} +
+ ); + } + + private scanWalletForTokens = () => { + if (this.props.wallet) { + this.props.scanWalletForTokens(this.props.wallet); + this.setState({ hasScanned: true }); + } + }; +} + +function mapStateToProps(state: AppState): StateProps { + return { + wallet: walletSelectors.getWalletInst(state), + walletConfig: walletSelectors.getWalletConfig(state), + tokens: configSelectors.getAllTokens(state), + tokenBalances: selectors.getTokenBalances(state), + tokensError: state.wallet.tokensError, + isTokensLoading: state.wallet.isTokensLoading, + hasSavedWalletTokens: state.wallet.hasSavedWalletTokens, + isOffline: configMetaSelectors.getOffline(state) + }; +} + +export default connect(mapStateToProps, { + attemptAddCustomToken: customTokensActions.attemptAddCustomToken, + removeCustomToken: customTokensActions.removeCustomToken, + scanWalletForTokens: walletActions.scanWalletForTokens, + setWalletTokens: walletActions.setWalletTokens, + refreshTokenBalances: walletActions.refreshTokenBalances +})(TokenBalances); diff --git a/common/components/UnitDropDown/UnitDropDown.tsx b/common/components/UnitDropDown/UnitDropDown.tsx index 640534f0f6e..54ac6d1f89e 100644 --- a/common/components/UnitDropDown/UnitDropDown.tsx +++ b/common/components/UnitDropDown/UnitDropDown.tsx @@ -1,67 +1,67 @@ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { Option } from 'react-select'; - -import { AppState } from 'features/reducers'; -import * as selectors from 'features/selectors'; -import { transactionMetaActions } from 'features/transaction'; -import { configSelectors } from 'features/config'; -import { walletTypes } from 'features/wallet'; -import { Query } from 'components/renderCbs'; -import { Dropdown } from 'components/ui'; - -interface DispatchProps { - setUnitMeta: transactionMetaActions.TSetUnitMeta; -} - -interface StateProps { - unit: string; - tokens: walletTypes.TokenBalance[]; - allTokens: walletTypes.MergedToken[]; - showAllTokens?: boolean; - networkUnit: string; -} - -class UnitDropdownClass extends Component { - public render() { - const { tokens, allTokens, showAllTokens, unit, networkUnit } = this.props; - const focusedTokens = showAllTokens ? allTokens : tokens; - const options = [networkUnit, ...getTokenSymbols(focusedTokens)]; - return ( - ( - 10} - disabled={!!readOnly} - /> - )} - /> - ); - } - private handleOnChange = (unit: Option) => { - if (!unit.value) { - throw Error('No unit value found'); - } - this.props.setUnitMeta(unit.value); - }; -} -const getTokenSymbols = (tokens: (walletTypes.TokenBalance | walletTypes.MergedToken)[]) => - tokens.map(t => t.symbol); - -function mapStateToProps(state: AppState) { - return { - tokens: selectors.getShownTokenBalances(state, true), - allTokens: selectors.getTokens(state), - unit: selectors.getUnit(state), - networkUnit: configSelectors.getNetworkUnit(state) - }; -} - -export const UnitDropDown = connect(mapStateToProps, { - setUnitMeta: transactionMetaActions.setUnitMeta -})(UnitDropdownClass); +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Option } from 'react-select'; + +import { AppState } from 'features/reducers'; +import * as selectors from 'features/selectors'; +import { transactionMetaActions } from 'features/transaction'; +import { configSelectors } from 'features/config'; +import { walletTypes } from 'features/wallet'; +import { Query } from 'components/renderCbs'; +import { Dropdown } from 'components/ui'; + +interface DispatchProps { + setUnitMeta: transactionMetaActions.TSetUnitMeta; +} + +interface StateProps { + unit: string; + tokens: walletTypes.TokenBalance[]; + allTokens: walletTypes.MergedToken[]; + showAllTokens?: boolean; + networkUnit: string; +} + +class UnitDropdownClass extends Component { + public render() { + const { tokens, allTokens, showAllTokens, unit, networkUnit } = this.props; + const focusedTokens = showAllTokens ? allTokens : tokens; + const options = [networkUnit, ...getTokenSymbols(focusedTokens)]; + return ( + ( + 10} + disabled={!!readOnly} + /> + )} + /> + ); + } + private handleOnChange = (unit: Option) => { + if (!unit.value) { + throw Error('No unit value found'); + } + this.props.setUnitMeta(unit.value); + }; +} +const getTokenSymbols = (tokens: (walletTypes.TokenBalance | walletTypes.MergedToken)[]) => + tokens.map(t => t.symbol); + +function mapStateToProps(state: AppState) { + return { + tokens: selectors.getShownTokenBalances(state, true), + allTokens: selectors.getTokens(state), + unit: selectors.getUnit(state), + networkUnit: configSelectors.getNetworkUnit(state) + }; +} + +export const UnitDropDown = connect(mapStateToProps, { + setUnitMeta: transactionMetaActions.setUnitMeta +})(UnitDropdownClass); From d01c94b661a234752011e5e0fa44bd8f2d8dc3d7 Mon Sep 17 00:00:00 2001 From: Taylor Monahan <7924827+tayvano@users.noreply.github.com> Date: Tue, 27 Nov 2018 23:07:34 -0800 Subject: [PATCH 8/9] update trezor affiliate link --- common/components/Footer/NewFooter/components/Linkset.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/components/Footer/NewFooter/components/Linkset.tsx b/common/components/Footer/NewFooter/components/Linkset.tsx index c84c1249eec..062ab90d621 100644 --- a/common/components/Footer/NewFooter/components/Linkset.tsx +++ b/common/components/Footer/NewFooter/components/Linkset.tsx @@ -38,7 +38,7 @@ const LINK_COLUMNS = [ }, { title: 'TREZOR', - link: 'https://shop.trezor.io/?a=mycrypto.com' + link: 'https://shop.trezor.io/?offer_id=10&aff_id=1735' }, { title: 'ether.card', From ffb1a4fb93f1d5fb562e6ef3e71e3e9085a1a78f Mon Sep 17 00:00:00 2001 From: tayvano Date: Tue, 27 Nov 2018 23:09:29 -0800 Subject: [PATCH 9/9] update more trezor affiliate links --- common/config/data.tsx | 2 +- common/containers/OnboardingModal/slides/FourthSlide.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/config/data.tsx b/common/config/data.tsx index 155d1492435..5bb37d91b5b 100644 --- a/common/config/data.tsx +++ b/common/config/data.tsx @@ -54,7 +54,7 @@ export const MINIMUM_PASSWORD_LENGTH = 12; export const knowledgeBaseURL = 'https://support.mycrypto.com'; export const ledgerReferralURL = 'https://www.ledgerwallet.com/r/1985?path=/products/'; -export const trezorReferralURL = 'https://shop.trezor.io?a=mycrypto.com'; +export const trezorReferralURL = 'https://shop.trezor.io/?offer_id=10&aff_id=1735'; // TODO - Update url export const safeTReferralURL = 'https://www.archos.com/fr/products/crypto/archos_safetmini/index.html'; diff --git a/common/containers/OnboardingModal/slides/FourthSlide.tsx b/common/containers/OnboardingModal/slides/FourthSlide.tsx index 2057121a6ca..f962c04602a 100644 --- a/common/containers/OnboardingModal/slides/FourthSlide.tsx +++ b/common/containers/OnboardingModal/slides/FourthSlide.tsx @@ -16,7 +16,7 @@ export default function FourthSlide() {