mirror of
https://github.com/status-im/MyCrypto.git
synced 2025-02-03 23:03:40 +00:00
[FEATURE] Initial EAC integration.
This commit is contained in:
parent
ab2b559dd2
commit
f2afd78f4e
@ -10,7 +10,8 @@ import {
|
||||
InputDataAction,
|
||||
InputNonceAction,
|
||||
ResetAction,
|
||||
SetGasPriceFieldAction
|
||||
SetGasPriceFieldAction,
|
||||
SetWindowStartFieldAction
|
||||
} from '../actionTypes';
|
||||
import { TypeKeys } from 'actions/transaction/constants';
|
||||
|
||||
@ -80,6 +81,14 @@ const setGasPriceField = (payload: SetGasPriceFieldAction['payload']): SetGasPri
|
||||
payload
|
||||
});
|
||||
|
||||
type TSetWindowStartField = typeof setWindowStartField;
|
||||
const setWindowStartField = (
|
||||
payload: SetWindowStartFieldAction['payload']
|
||||
): SetWindowStartFieldAction => ({
|
||||
type: TypeKeys.WINDOW_START_FIELD_SET,
|
||||
payload
|
||||
});
|
||||
|
||||
type TReset = typeof reset;
|
||||
const reset = (payload: ResetAction['payload'] = { include: {}, exclude: {} }): ResetAction => ({
|
||||
type: TypeKeys.RESET,
|
||||
@ -98,6 +107,7 @@ export {
|
||||
TSetNonceField,
|
||||
TSetValueField,
|
||||
TSetGasPriceField,
|
||||
TSetWindowStartField,
|
||||
TReset,
|
||||
inputGasLimit,
|
||||
inputGasPrice,
|
||||
@ -110,5 +120,6 @@ export {
|
||||
setNonceField,
|
||||
setValueField,
|
||||
setGasPriceField,
|
||||
setWindowStartField,
|
||||
reset
|
||||
};
|
||||
|
@ -4,4 +4,5 @@ export * from './network';
|
||||
export * from './sign';
|
||||
export * from './broadcast';
|
||||
export * from './current';
|
||||
export * from './windowStart';
|
||||
export * from './sendEverything';
|
||||
|
12
common/actions/transaction/actionCreators/windowStart.ts
Normal file
12
common/actions/transaction/actionCreators/windowStart.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { SetCurrentWindowStartAction } from '../actionTypes/windowStart';
|
||||
import { TypeKeys } from '../';
|
||||
|
||||
type TSetCurrentWindowStart = typeof setCurrentWindowStart;
|
||||
const setCurrentWindowStart = (
|
||||
payload: SetCurrentWindowStartAction['payload']
|
||||
): SetCurrentWindowStartAction => ({
|
||||
type: TypeKeys.CURRENT_WINDOW_START_SET,
|
||||
payload
|
||||
});
|
||||
|
||||
export { setCurrentWindowStart, TSetCurrentWindowStart };
|
@ -75,6 +75,14 @@ interface SetValueFieldAction {
|
||||
};
|
||||
}
|
||||
|
||||
interface SetWindowStartFieldAction {
|
||||
type: TypeKeys.WINDOW_START_FIELD_SET;
|
||||
payload: {
|
||||
raw: string;
|
||||
value: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
type InputFieldAction = InputNonceAction | InputGasLimitAction | InputDataAction;
|
||||
|
||||
type FieldAction =
|
||||
@ -83,7 +91,8 @@ type FieldAction =
|
||||
| SetToFieldAction
|
||||
| SetNonceFieldAction
|
||||
| SetValueFieldAction
|
||||
| SetGasPriceFieldAction;
|
||||
| SetGasPriceFieldAction
|
||||
| SetWindowStartFieldAction;
|
||||
|
||||
export {
|
||||
InputGasLimitAction,
|
||||
@ -98,5 +107,6 @@ export {
|
||||
SetValueFieldAction,
|
||||
FieldAction,
|
||||
InputFieldAction,
|
||||
SetGasPriceFieldAction
|
||||
SetGasPriceFieldAction,
|
||||
SetWindowStartFieldAction
|
||||
};
|
||||
|
12
common/actions/transaction/actionTypes/windowStart.ts
Normal file
12
common/actions/transaction/actionTypes/windowStart.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { TypeKeys } from '../constants';
|
||||
|
||||
/* user input */
|
||||
|
||||
interface SetCurrentWindowStartAction {
|
||||
type: TypeKeys.CURRENT_WINDOW_START_SET;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
type CurrentAction = SetCurrentWindowStartAction;
|
||||
|
||||
export { SetCurrentWindowStartAction, CurrentAction };
|
@ -25,6 +25,7 @@ export enum TypeKeys {
|
||||
|
||||
CURRENT_VALUE_SET = 'CURRENT_VALUE_SET',
|
||||
CURRENT_TO_SET = 'CURRENT_TO_SET',
|
||||
CURRENT_WINDOW_START_SET = 'CURRENT_WINDOW_START_SET',
|
||||
|
||||
DATA_FIELD_INPUT = 'DATA_FIELD_INPUT',
|
||||
GAS_LIMIT_INPUT = 'GAS_LIMIT_INPUT',
|
||||
@ -38,6 +39,7 @@ export enum TypeKeys {
|
||||
VALUE_FIELD_SET = 'VALUE_FIELD_SET',
|
||||
NONCE_FIELD_SET = 'NONCE_FIELD_SET',
|
||||
GAS_PRICE_FIELD_SET = 'GAS_PRICE_FIELD_SET',
|
||||
WINDOW_START_FIELD_SET = 'WINDOW_START_FIELD_SET',
|
||||
|
||||
TOKEN_TO_META_SET = 'TOKEN_TO_META_SET',
|
||||
UNIT_META_SET = 'UNIT_META_SET',
|
||||
|
@ -61,6 +61,7 @@ class GenerateTransactionFactoryClass extends Component<Props> {
|
||||
|
||||
const isButtonDisabled =
|
||||
!isFullTransaction || networkRequestPending || !validGasPrice || !validGasLimit;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<WithSigner
|
||||
|
13
common/components/ScheduleTransaction.tsx
Normal file
13
common/components/ScheduleTransaction.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { ScheduleTransactionFactory } from './ScheduleTransactionFactory';
|
||||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
|
||||
export const ScheduleTransaction: React.SFC<{}> = () => (
|
||||
<ScheduleTransactionFactory
|
||||
withProps={({ disabled, isWeb3Wallet, onClick }) => (
|
||||
<button disabled={disabled} className="btn btn-info btn-block" onClick={onClick}>
|
||||
{isWeb3Wallet ? translate('SCHEDULE_schedule') : translate('DEP_signtx')}
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
);
|
20
common/components/ScheduleTransactionFactory/Container.tsx
Normal file
20
common/components/ScheduleTransactionFactory/Container.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { signTransactionRequested, TSignTransactionRequested } from 'actions/transaction';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface DispatchProps {
|
||||
signTransactionRequested: TSignTransactionRequested;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
isWeb3: boolean;
|
||||
withSigner(signer: TSignTransactionRequested): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
class Container extends Component<DispatchProps & OwnProps, {}> {
|
||||
public render() {
|
||||
return this.props.withSigner(this.props.signTransactionRequested);
|
||||
}
|
||||
}
|
||||
|
||||
export const WithSigner = connect(null, { signTransactionRequested })(Container);
|
@ -0,0 +1,88 @@
|
||||
import { WithSigner } from './Container';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import {
|
||||
getSchedulingTransaction,
|
||||
isNetworkRequestPending,
|
||||
isValidGasPrice,
|
||||
isValidGasLimit,
|
||||
getGasPrice,
|
||||
getCurrentTo,
|
||||
getCurrentValue
|
||||
} from 'selectors/transaction';
|
||||
import { getWalletType } from 'selectors/wallet';
|
||||
import { getWindowStart } from '../../selectors/transaction/fields';
|
||||
|
||||
export interface CallbackProps {
|
||||
disabled: boolean;
|
||||
isWeb3Wallet: boolean;
|
||||
onClick(): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
transaction: EthTx;
|
||||
networkRequestPending: boolean;
|
||||
isFullTransaction: boolean;
|
||||
isWeb3Wallet: boolean;
|
||||
validGasPrice: boolean;
|
||||
validGasLimit: boolean;
|
||||
isWindowStartValid: boolean;
|
||||
windowStart: any;
|
||||
gasPrice: any;
|
||||
currentTo: any;
|
||||
currentValue: any;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
class ScheduleTransactionFactoryClass extends Component<Props> {
|
||||
public render() {
|
||||
const {
|
||||
isFullTransaction,
|
||||
isWeb3Wallet,
|
||||
networkRequestPending,
|
||||
validGasPrice,
|
||||
validGasLimit,
|
||||
isWindowStartValid,
|
||||
transaction
|
||||
} = this.props;
|
||||
|
||||
const isButtonDisabled =
|
||||
!isWindowStartValid ||
|
||||
!isFullTransaction ||
|
||||
networkRequestPending ||
|
||||
!validGasPrice ||
|
||||
!validGasLimit;
|
||||
|
||||
return (
|
||||
<WithSigner
|
||||
isWeb3={isWeb3Wallet}
|
||||
withSigner={signer =>
|
||||
this.props.withProps({
|
||||
disabled: isButtonDisabled,
|
||||
isWeb3Wallet,
|
||||
onClick: () => signer(transaction)
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ScheduleTransactionFactory = connect((state: AppState) => ({
|
||||
...getSchedulingTransaction(state),
|
||||
networkRequestPending: isNetworkRequestPending(state),
|
||||
isWeb3Wallet: getWalletType(state).isWeb3Wallet,
|
||||
validGasPrice: isValidGasPrice(state),
|
||||
validGasLimit: isValidGasLimit(state),
|
||||
windowStart: getWindowStart(state),
|
||||
gasPrice: getGasPrice(state),
|
||||
currentTo: getCurrentTo(state),
|
||||
currentValue: getCurrentValue(state)
|
||||
}))(ScheduleTransactionFactoryClass);
|
1
common/components/ScheduleTransactionFactory/index.ts
Normal file
1
common/components/ScheduleTransactionFactory/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './ScheduleTransactionFactory';
|
@ -50,6 +50,7 @@ interface OwnProps {
|
||||
disableToggle?: boolean;
|
||||
advancedGasOptions?: AdvancedOptions;
|
||||
className?: string;
|
||||
scheduling?: boolean;
|
||||
}
|
||||
|
||||
type Props = DispatchProps & OwnProps & StateProps;
|
||||
@ -90,7 +91,7 @@ class TXMetaDataPanel extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { offline, disableToggle, advancedGasOptions, className = '' } = this.props;
|
||||
const { offline, disableToggle, advancedGasOptions, className = '', scheduling } = this.props;
|
||||
const { gasPrice } = this.state;
|
||||
const showAdvanced = this.state.sliderState === 'advanced' || offline;
|
||||
return (
|
||||
@ -100,12 +101,14 @@ class TXMetaDataPanel extends React.Component<Props, State> {
|
||||
gasPrice={gasPrice}
|
||||
inputGasPrice={this.props.inputGasPrice}
|
||||
options={advancedGasOptions}
|
||||
scheduling={scheduling}
|
||||
/>
|
||||
) : (
|
||||
<SimpleGas
|
||||
gasPrice={gasPrice}
|
||||
inputGasPrice={this.handleGasPriceInput}
|
||||
setGasPrice={this.props.inputGasPrice}
|
||||
scheduling={scheduling}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -11,6 +11,8 @@ import { getAutoGasLimitEnabled } from 'selectors/config';
|
||||
import { isValidGasPrice } from 'selectors/transaction';
|
||||
import { sanitizeNumericalInput } from 'libs/values';
|
||||
import { Input } from 'components/ui';
|
||||
import SchedulingFeeSummary from './SchedulingFeeSummary';
|
||||
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
|
||||
|
||||
export interface AdvancedOptions {
|
||||
gasPriceField?: boolean;
|
||||
@ -24,6 +26,7 @@ interface OwnProps {
|
||||
inputGasPrice: TInputGasPrice;
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
options?: AdvancedOptions;
|
||||
scheduling?: boolean;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
@ -54,8 +57,9 @@ class AdvancedGas extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { autoGasLimitEnabled, gasPrice, validGasPrice } = this.props;
|
||||
const { autoGasLimitEnabled, gasPrice, validGasPrice, scheduling } = this.props;
|
||||
const { gasPriceField, gasLimitField, nonceField, dataField, feeSummary } = this.state.options;
|
||||
|
||||
return (
|
||||
<div className="AdvancedGas row form-group">
|
||||
<div className="AdvancedGas-calculate-limit">
|
||||
@ -107,18 +111,39 @@ class AdvancedGas extends React.Component<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{feeSummary && (
|
||||
<div className="AdvancedGas-fee-summary">
|
||||
<FeeSummary
|
||||
gasPrice={gasPrice}
|
||||
render={({ gasPriceWei, gasLimit, fee, usd }) => (
|
||||
<span>
|
||||
{gasPriceWei} * {gasLimit} = {fee} {usd && <span>~= ${usd} USD</span>}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!scheduling &&
|
||||
feeSummary && (
|
||||
<div className="AdvancedGas-fee-summary">
|
||||
<FeeSummary
|
||||
gasPrice={gasPrice}
|
||||
render={({ gasPriceWei, gasLimit, fee, usd }) => (
|
||||
<span>
|
||||
{gasPriceWei} * {gasLimit} = {fee} {usd && <span>~= ${usd} USD</span>}
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{scheduling &&
|
||||
feeSummary && (
|
||||
<div className="AdvancedGas-fee-summary">
|
||||
<SchedulingFeeSummary
|
||||
gasPrice={gasPrice}
|
||||
render={({ gasPriceWei, gasLimit, fee, usd }) => (
|
||||
<div>
|
||||
<span>
|
||||
{EAC_SCHEDULING_CONFIG.PAYMENT} + {gasPriceWei} * ({
|
||||
EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT
|
||||
}{' '}
|
||||
+ {EAC_SCHEDULING_CONFIG.FUTURE_EXECUTION_COST} + {gasLimit}) = {fee}{' '}
|
||||
{usd && <span>~= ${usd} USD</span>}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import BN from 'bn.js';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { getNetworkConfig, getOffline } from 'selectors/config';
|
||||
import { getIsEstimating } from 'selectors/gas';
|
||||
import { getGasLimit } from 'selectors/transaction';
|
||||
import { UnitDisplay, Spinner } from 'components/ui';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import './FeeSummary.scss';
|
||||
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
|
||||
|
||||
interface RenderData {
|
||||
gasPriceWei: string;
|
||||
gasPriceGwei: string;
|
||||
gasLimit: string;
|
||||
fee: React.ReactElement<string>;
|
||||
usd: React.ReactElement<string> | null;
|
||||
}
|
||||
|
||||
interface ReduxStateProps {
|
||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
||||
rates: AppState['rates']['rates'];
|
||||
network: NetworkConfig;
|
||||
isOffline: AppState['config']['meta']['offline'];
|
||||
isGasEstimating: AppState['gas']['isEstimating'];
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
render(data: RenderData): React.ReactElement<string> | string;
|
||||
}
|
||||
|
||||
type Props = OwnProps & ReduxStateProps;
|
||||
|
||||
class SchedulingFeeSummary extends React.Component<Props> {
|
||||
public render() {
|
||||
const { gasPrice, gasLimit, rates, network, isOffline, isGasEstimating } = this.props;
|
||||
|
||||
if (isGasEstimating) {
|
||||
return (
|
||||
<div className="FeeSummary is-loading">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const feeBig =
|
||||
gasPrice.value &&
|
||||
gasLimit.value &&
|
||||
gasPrice.value
|
||||
.mul(
|
||||
gasLimit.value
|
||||
.add(new BN(EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT))
|
||||
.add(new BN(EAC_SCHEDULING_CONFIG.FUTURE_EXECUTION_COST))
|
||||
)
|
||||
.add(new BN(EAC_SCHEDULING_CONFIG.PAYMENT));
|
||||
|
||||
const fee = (
|
||||
<UnitDisplay
|
||||
value={feeBig}
|
||||
unit="ether"
|
||||
symbol={network.unit}
|
||||
displayShortBalance={6}
|
||||
checkOffline={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const usdBig = network.isTestnet
|
||||
? new BN(0)
|
||||
: feeBig && rates[network.unit] && feeBig.muln(rates[network.unit].USD);
|
||||
const usd = isOffline ? null : (
|
||||
<UnitDisplay
|
||||
value={usdBig}
|
||||
unit="ether"
|
||||
displayShortBalance={2}
|
||||
displayTrailingZeroes={true}
|
||||
checkOffline={true}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="FeeSummary">
|
||||
{this.props.render({
|
||||
gasPriceWei: gasPrice.value.toString(),
|
||||
gasPriceGwei: gasPrice.raw,
|
||||
fee,
|
||||
usd,
|
||||
gasLimit: gasLimit.raw
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState): ReduxStateProps {
|
||||
return {
|
||||
gasLimit: getGasLimit(state),
|
||||
rates: state.rates.rates,
|
||||
network: getNetworkConfig(state),
|
||||
isOffline: getOffline(state),
|
||||
isGasEstimating: getIsEstimating(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SchedulingFeeSummary);
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import Slider, { createSliderWithTooltip } from 'rc-slider';
|
||||
import translate from 'translations';
|
||||
import FeeSummary from './FeeSummary';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import './SimpleGas.scss';
|
||||
import { AppState } from 'reducers';
|
||||
import {
|
||||
@ -17,11 +16,14 @@ import { Wei, fromWei } from 'libs/units';
|
||||
import { gasPriceDefaults } from 'config';
|
||||
import { InlineSpinner } from 'components/ui/InlineSpinner';
|
||||
import { TInputGasPrice } from 'actions/transaction';
|
||||
import SchedulingFeeSummary from './SchedulingFeeSummary';
|
||||
const SliderWithTooltip = createSliderWithTooltip(Slider);
|
||||
|
||||
interface OwnProps {
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
setGasPrice: TInputGasPrice;
|
||||
scheduling?: boolean;
|
||||
|
||||
inputGasPrice(rawGas: string): void;
|
||||
}
|
||||
|
||||
@ -112,7 +114,7 @@ class SimpleGas extends React.Component<Props> {
|
||||
<span>{translate('TX_FEE_SCALE_RIGHT')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<FeeSummary
|
||||
<SchedulingFeeSummary
|
||||
gasPrice={gasPrice}
|
||||
render={({ fee, usd }) => (
|
||||
<span>
|
||||
|
29
common/components/WindowStartField.tsx
Normal file
29
common/components/WindowStartField.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { Input } from 'components/ui';
|
||||
import { WindowStartFieldFactory } from './WindowStartFieldFactory';
|
||||
|
||||
interface Props {
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export const WindowStartField: React.SFC<Props> = ({ isReadOnly }) => (
|
||||
<WindowStartFieldFactory
|
||||
withProps={({ currentWindowStart, isValid, onChange, readOnly }) => (
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('SCHEDULE_block')}</div>
|
||||
<Input
|
||||
className={`input-group-input ${isValid ? '' : 'invalid'}`}
|
||||
type="text"
|
||||
value={currentWindowStart.raw}
|
||||
placeholder={translate('SCHEDULE_block_placeholder', true)}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
spellCheck={false}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
@ -0,0 +1,63 @@
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { setCurrentWindowStart, TSetCurrentWindowStart } from 'actions/transaction';
|
||||
import { WindowStartInputFactory } from './WindowStartInputFactory';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ICurrentWindowStart } from 'selectors/transaction';
|
||||
|
||||
interface DispatchProps {
|
||||
setCurrentWindowStart: TSetCurrentWindowStart;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
windowStart: string | null;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
export interface CallbackProps {
|
||||
isValid: boolean;
|
||||
readOnly: boolean;
|
||||
currentWindowStart: ICurrentWindowStart;
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
type Props = DispatchProps & OwnProps;
|
||||
|
||||
class WindowStartFieldFactoryClass extends React.Component<Props> {
|
||||
public componentDidMount() {
|
||||
const { windowStart } = this.props;
|
||||
if (windowStart) {
|
||||
this.props.setCurrentWindowStart(windowStart);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<WindowStartInputFactory onChange={this.setWindowStart} withProps={this.props.withProps} />
|
||||
);
|
||||
}
|
||||
|
||||
private setWindowStart = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const { value } = ev.currentTarget;
|
||||
this.props.setCurrentWindowStart(value);
|
||||
};
|
||||
}
|
||||
|
||||
const WindowStartFieldFactory = connect(null, { setCurrentWindowStart })(
|
||||
WindowStartFieldFactoryClass
|
||||
);
|
||||
|
||||
interface DefaultWindowStartFieldProps {
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
const DefaultWindowStartField: React.SFC<DefaultWindowStartFieldProps> = ({ withProps }) => (
|
||||
<Query
|
||||
params={['windowStart']}
|
||||
withQuery={({ windowStart }) => (
|
||||
<WindowStartFieldFactory windowStart={windowStart} withProps={withProps} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
export { DefaultWindowStartField as WindowStartFieldFactory };
|
@ -0,0 +1,54 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import {
|
||||
getCurrentWindowStart,
|
||||
isValidCurrentWindowStart,
|
||||
ICurrentWindowStart
|
||||
} from 'selectors/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { CallbackProps } from 'components/WindowStartFieldFactory';
|
||||
import { getResolvingDomain } from 'selectors/ens';
|
||||
|
||||
interface StateProps {
|
||||
currentWindowStart: ICurrentWindowStart;
|
||||
isValid: boolean;
|
||||
isResolving: boolean;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
class WindowStartInputFactoryClass extends Component<Props> {
|
||||
public render() {
|
||||
const { currentWindowStart, onChange, isValid, withProps } = this.props;
|
||||
|
||||
return (
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-11">
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) =>
|
||||
withProps({
|
||||
currentWindowStart,
|
||||
isValid,
|
||||
onChange,
|
||||
readOnly: !!readOnly || this.props.isResolving
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const WindowStartInputFactory = connect((state: AppState) => ({
|
||||
currentWindowStart: getCurrentWindowStart(state),
|
||||
isResolving: getResolvingDomain(state),
|
||||
isValid: isValidCurrentWindowStart(state)
|
||||
}))(WindowStartInputFactoryClass);
|
1
common/components/WindowStartFieldFactory/index.ts
Normal file
1
common/components/WindowStartFieldFactory/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './WindowStartFieldFactory';
|
@ -8,6 +8,8 @@ export * from './CurrentCustomMessage';
|
||||
export * from './GenerateTransaction';
|
||||
export * from './SendButton';
|
||||
export * from './SigningStatus';
|
||||
export * from './WindowStartField';
|
||||
export * from './ScheduleTransaction';
|
||||
export { default as NonceField } from './NonceField';
|
||||
export { default as Header } from './Header';
|
||||
export { default as Footer } from './Footer';
|
||||
|
@ -13,7 +13,15 @@ interface IQueryResults {
|
||||
[key: string]: string | null;
|
||||
}
|
||||
|
||||
export type Param = 'to' | 'data' | 'readOnly' | 'tokenSymbol' | 'value' | 'gaslimit' | 'limit';
|
||||
export type Param =
|
||||
| 'to'
|
||||
| 'data'
|
||||
| 'readOnly'
|
||||
| 'tokenSymbol'
|
||||
| 'value'
|
||||
| 'gaslimit'
|
||||
| 'limit'
|
||||
| 'windowStart';
|
||||
|
||||
interface Props extends RouteComponentProps<{}> {
|
||||
params: Param[];
|
||||
|
@ -0,0 +1,90 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { isAnyOfflineWithWeb3 } from 'selectors/derived';
|
||||
import {
|
||||
AddressField,
|
||||
AmountField,
|
||||
TXMetaDataPanel,
|
||||
SendEverything,
|
||||
CurrentCustomMessage,
|
||||
ScheduleTransaction,
|
||||
SendButton,
|
||||
SigningStatus,
|
||||
WindowStartField
|
||||
} from 'components';
|
||||
import { OnlyUnlocked, WhenQueryExists } from 'components/renderCbs';
|
||||
import translate from 'translations';
|
||||
|
||||
import { AppState } from 'reducers';
|
||||
import { NonStandardTransaction } from './components';
|
||||
|
||||
const content = (
|
||||
<div className="Tab-content-pane">
|
||||
<AddressField />
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-12">
|
||||
<AmountField hasUnitDropdown={true} />
|
||||
<SendEverything />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-12">
|
||||
<WindowStartField />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-12">
|
||||
<TXMetaDataPanel scheduling={true} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CurrentCustomMessage />
|
||||
<NonStandardTransaction />
|
||||
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-12 clearfix">
|
||||
<ScheduleTransaction />
|
||||
</div>
|
||||
</div>
|
||||
<SigningStatus />
|
||||
<div className="row form-group">
|
||||
<SendButton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const QueryWarning: React.SFC<{}> = () => (
|
||||
<WhenQueryExists
|
||||
whenQueryExists={
|
||||
<div className="alert alert-info">
|
||||
<p>{translate('WARN_Send_Link')}</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
interface StateProps {
|
||||
shouldDisplay: boolean;
|
||||
}
|
||||
|
||||
class SchedulingFieldsClass extends Component<StateProps> {
|
||||
public render() {
|
||||
const { shouldDisplay } = this.props;
|
||||
return (
|
||||
<OnlyUnlocked
|
||||
whenUnlocked={
|
||||
<React.Fragment>
|
||||
<QueryWarning />
|
||||
{shouldDisplay ? content : null}
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const SchedulingFields = connect((state: AppState) => ({
|
||||
shouldDisplay: !isAnyOfflineWithWeb3(state)
|
||||
}))(SchedulingFieldsClass);
|
@ -1 +1,2 @@
|
||||
export * from './Fields';
|
||||
export * from './SchedulingFields';
|
||||
|
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { SchedulingFields, UnavailableWallets } from 'containers/Tabs/SendTransaction/components';
|
||||
|
||||
export default function() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SchedulingFields />
|
||||
<UnavailableWallets />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
@ -3,4 +3,4 @@ export * from './UnavailableWallets';
|
||||
export * from './SideBar';
|
||||
export { default as WalletInfo } from './WalletInfo';
|
||||
export { default as RequestPayment } from './RequestPayment';
|
||||
export { default as RecentTransactions } from './RecentTransactions';
|
||||
export { default as SchedulePayment } from './SchedulePayment';
|
||||
|
@ -13,11 +13,12 @@ import {
|
||||
RecentTransactions,
|
||||
Fields,
|
||||
UnavailableWallets,
|
||||
SideBar
|
||||
} from './components';
|
||||
SchedulePayment
|
||||
} from 'containers/Tabs/SendTransaction/components';
|
||||
import SubTabs, { Tab } from 'components/SubTabs';
|
||||
import { RouteNotFound } from 'components/RouteNotFound';
|
||||
import { isNetworkUnit } from 'selectors/config/wallet';
|
||||
import { getNetworkConfig } from 'selectors/config/networks';
|
||||
|
||||
const Send = () => (
|
||||
<React.Fragment>
|
||||
@ -29,6 +30,7 @@ const Send = () => (
|
||||
interface StateProps {
|
||||
wallet: AppState['wallet']['inst'];
|
||||
requestDisabled: boolean;
|
||||
scheduleDisabled: boolean;
|
||||
}
|
||||
|
||||
type Props = StateProps & RouteComponentProps<{}>;
|
||||
@ -43,9 +45,14 @@ class SendTransaction extends React.Component<Props> {
|
||||
name: translate('NAV_SENDETHER'),
|
||||
disabled: !!wallet && !!wallet.isReadOnly
|
||||
},
|
||||
{
|
||||
path: 'schedule',
|
||||
name: translate('NAV_SchedulePayment'),
|
||||
disabled: (!!wallet && !!wallet.isReadOnly) || this.props.scheduleDisabled
|
||||
},
|
||||
{
|
||||
path: 'request',
|
||||
name: translate('NAV_REQUESTPAYMENT'),
|
||||
name: translate('Request Payment'),
|
||||
disabled: this.props.requestDisabled
|
||||
},
|
||||
{
|
||||
@ -97,9 +104,15 @@ class SendTransaction extends React.Component<Props> {
|
||||
render={() => <RequestPayment wallet={wallet} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${currentPath}/recent-txs`}
|
||||
path={`${currentPath}/schedule`}
|
||||
exact={true}
|
||||
render={() => <RecentTransactions wallet={wallet} />}
|
||||
render={() => {
|
||||
return wallet.isReadOnly || this.props.scheduleDisabled ? (
|
||||
<Redirect to={`${currentPath}/info`} />
|
||||
) : (
|
||||
<SchedulePayment />
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<RouteNotFound />
|
||||
</Switch>
|
||||
@ -115,5 +128,6 @@ class SendTransaction extends React.Component<Props> {
|
||||
|
||||
export default connect((state: AppState) => ({
|
||||
wallet: getWalletInst(state),
|
||||
requestDisabled: !isNetworkUnit(state, 'ETH')
|
||||
requestDisabled: !isNetworkUnit(state, 'ETH'),
|
||||
scheduleDisabled: getNetworkConfig(state).name !== 'Kovan'
|
||||
}))(SendTransaction);
|
||||
|
7
common/libs/scheduling.ts
Normal file
7
common/libs/scheduling.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const EAC_SCHEDULING_CONFIG = {
|
||||
SCHEDULING_GAS_LIMIT: 1500000,
|
||||
FUTURE_EXECUTION_COST: 180000,
|
||||
FEE_MULTIPLIER: 2,
|
||||
PAYMENT: 10000000,
|
||||
WINDOW_SIZE_IN_BLOCKS: 90
|
||||
};
|
@ -18,6 +18,7 @@ const INITIAL_STATE: State = {
|
||||
data: { raw: '', value: null },
|
||||
nonce: { raw: '', value: null },
|
||||
value: { raw: '', value: null },
|
||||
windowStart: { raw: '', value: null },
|
||||
gasLimit: { raw: '21000', value: new BN(21000) },
|
||||
gasPrice: { raw: '20', value: gasPricetoBase(20) }
|
||||
};
|
||||
@ -69,6 +70,8 @@ export const fields = (
|
||||
return updateField('nonce')(state, action);
|
||||
case TK.GAS_PRICE_FIELD_SET:
|
||||
return updateField('gasPrice')(state, action);
|
||||
case TK.WINDOW_START_FIELD_SET:
|
||||
return updateField('windowStart')(state, action);
|
||||
case TK.TOKEN_TO_ETHER_SWAP:
|
||||
return tokenToEther(state, action);
|
||||
case TK.ETHER_TO_TOKEN_SWAP:
|
||||
|
@ -2,7 +2,8 @@ import {
|
||||
SetToFieldAction,
|
||||
SetDataFieldAction,
|
||||
SetNonceFieldAction,
|
||||
SetGasLimitFieldAction
|
||||
SetGasLimitFieldAction,
|
||||
SetWindowStartFieldAction
|
||||
} from 'actions/transaction';
|
||||
import { Wei } from 'libs/units';
|
||||
|
||||
@ -10,6 +11,7 @@ export interface State {
|
||||
to: SetToFieldAction['payload'];
|
||||
data: SetDataFieldAction['payload'];
|
||||
nonce: SetNonceFieldAction['payload'];
|
||||
windowStart: SetWindowStartFieldAction['payload'];
|
||||
value: { raw: string; value: Wei | null }; // TODO: fix this workaround since some of the payload is optional
|
||||
gasLimit: SetGasLimitFieldAction['payload'];
|
||||
gasPrice: { raw: string; value: Wei };
|
||||
|
25
common/sagas/transaction/current/currentWindowStart.ts
Normal file
25
common/sagas/transaction/current/currentWindowStart.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { setWindowStartField } from 'actions/transaction/actionCreators/fields';
|
||||
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||
import { SagaIterator } from 'redux-saga';
|
||||
import { TypeKeys } from 'actions/transaction/constants';
|
||||
import { SetWindowStartFieldAction } from 'actions/transaction';
|
||||
import { SetCurrentWindowStartAction } from 'actions/transaction/actionTypes/windowStart';
|
||||
|
||||
export function* setCurrentWindowStart({
|
||||
payload: raw
|
||||
}: SetCurrentWindowStartAction): SagaIterator {
|
||||
let value: number | null = null;
|
||||
|
||||
value = parseInt(raw, 10);
|
||||
|
||||
yield call(setField, { value, raw });
|
||||
}
|
||||
|
||||
export function* setField(payload: SetWindowStartFieldAction['payload']) {
|
||||
yield put(setWindowStartField(payload));
|
||||
}
|
||||
|
||||
export const currentWindowStart = takeLatest(
|
||||
[TypeKeys.CURRENT_WINDOW_START_SET],
|
||||
setCurrentWindowStart
|
||||
);
|
@ -1,3 +1,5 @@
|
||||
import { currentTo } from './currentTo';
|
||||
import { currentValue } from './currentValue';
|
||||
export const current = [currentTo, ...currentValue];
|
||||
import { currentWindowStart } from './currentWindowStart';
|
||||
|
||||
export const current = [currentTo, ...currentValue, currentWindowStart];
|
||||
|
@ -10,6 +10,7 @@ const getGasLimit = (state: AppState) => getFields(state).gasLimit;
|
||||
const getGasPrice = (state: AppState) => getFields(state).gasPrice;
|
||||
const getValue = (state: AppState) => getFields(state).value;
|
||||
const getNonce = (state: AppState) => getFields(state).nonce;
|
||||
const getWindowStart = (state: AppState) => getFields(state).windowStart;
|
||||
|
||||
const getDataExists = (state: AppState) => {
|
||||
const { value } = getData(state);
|
||||
@ -35,5 +36,6 @@ export {
|
||||
getNonce,
|
||||
getGasPrice,
|
||||
getDataExists,
|
||||
getValidGasCost
|
||||
getValidGasCost,
|
||||
getWindowStart
|
||||
};
|
||||
|
@ -28,11 +28,15 @@ export const isFullTx = (
|
||||
) => {
|
||||
const { data, value, to, ...rest } = transactionFields;
|
||||
const partialParamsToCheck = { ...rest };
|
||||
|
||||
delete partialParamsToCheck.windowStart;
|
||||
|
||||
const validPartialParams = Object.values(partialParamsToCheck).reduce<boolean>(
|
||||
(isValid, v: AppState['transaction']['fields'] & ICurrentTo & ICurrentValue) =>
|
||||
isValid && !!v.value,
|
||||
true
|
||||
);
|
||||
|
||||
if (isNetworkUnit(state, unit)) {
|
||||
// if theres data we can have no current value, and we dont have to check for a to address
|
||||
if (dataExists && validGasCost && !currentValue.value && currentValue.raw === '') {
|
||||
@ -55,3 +59,12 @@ export const isFullTx = (
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const isWindowStartValid = (
|
||||
transactionFields: AppState['transaction']['fields'],
|
||||
latestBlock: string
|
||||
) => {
|
||||
const { windowStart } = transactionFields;
|
||||
|
||||
return Boolean(windowStart && windowStart.value && windowStart.value > parseInt(latestBlock, 10));
|
||||
};
|
||||
|
@ -4,4 +4,5 @@ export * from './fields';
|
||||
export * from './meta';
|
||||
export * from './sign';
|
||||
export * from './current';
|
||||
export * from './windowStart';
|
||||
export * from './network';
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { AppState } from 'reducers';
|
||||
import { getCurrentTo, getCurrentValue } from './current';
|
||||
import { getFields } from './fields';
|
||||
import { getFields, getData, getWindowStart, getNonce } from './fields';
|
||||
import { makeTransaction, IHexStrTransaction } from 'libs/transaction';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import { getUnit } from 'selectors/transaction/meta';
|
||||
import { reduceToValues, isFullTx } from 'selectors/transaction/helpers';
|
||||
import { reduceToValues, isFullTx, isWindowStartValid } from 'selectors/transaction/helpers';
|
||||
import {
|
||||
getGasPrice,
|
||||
getGasLimit,
|
||||
@ -13,9 +13,12 @@ import {
|
||||
getValidGasCost,
|
||||
isEtherTransaction
|
||||
} from 'selectors/transaction';
|
||||
import { Wei } from 'libs/units';
|
||||
import { Wei, Address } from 'libs/units';
|
||||
import { getTransactionFields } from 'libs/transaction/utils/ether';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { getNetworkConfig, getLatestBlock } from 'selectors/config';
|
||||
import BN from 'bn.js';
|
||||
import abi from 'ethereumjs-abi';
|
||||
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
|
||||
|
||||
const getTransactionState = (state: AppState) => state.transaction;
|
||||
|
||||
@ -24,6 +27,12 @@ export interface IGetTransaction {
|
||||
isFullTransaction: boolean; //if the user has filled all the fields
|
||||
}
|
||||
|
||||
export interface IGetSchedulingTransaction {
|
||||
transaction: EthTx;
|
||||
isFullTransaction: boolean;
|
||||
isWindowStartValid: boolean;
|
||||
}
|
||||
|
||||
const getTransaction = (state: AppState): IGetTransaction => {
|
||||
const currentTo = getCurrentTo(state);
|
||||
const currentValue = getCurrentValue(state);
|
||||
@ -46,6 +55,132 @@ const getTransaction = (state: AppState): IGetTransaction => {
|
||||
return { transaction, isFullTransaction };
|
||||
};
|
||||
|
||||
const getScheduleData = (
|
||||
toAddress: string,
|
||||
callData = '',
|
||||
callGas: number,
|
||||
callValue: BN | null,
|
||||
windowSize: number,
|
||||
windowStart: any,
|
||||
gasPrice: BN | null,
|
||||
fee: number,
|
||||
payment: any,
|
||||
requiredDeposit: any
|
||||
) => {
|
||||
if (!callValue || !gasPrice || !windowStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
return abi.simpleEncode('schedule(address,bytes,uint[8]):(address)', toAddress, callData, [
|
||||
callGas,
|
||||
callValue,
|
||||
windowSize,
|
||||
windowStart,
|
||||
gasPrice,
|
||||
fee,
|
||||
payment,
|
||||
requiredDeposit
|
||||
]);
|
||||
};
|
||||
|
||||
const calcEACEndowment = (
|
||||
callGas: number | string | BN,
|
||||
callValue: number | string | BN,
|
||||
gasPrice: number | string | BN,
|
||||
fee: number | string | BN,
|
||||
payment: number | string | BN
|
||||
) => {
|
||||
const callGasBN = new BN(callGas);
|
||||
const callValueBN = new BN(callValue);
|
||||
const gasPriceBN = new BN(gasPrice);
|
||||
const feeBN = new BN(fee);
|
||||
const paymentBN = new BN(payment);
|
||||
|
||||
return paymentBN
|
||||
.add(feeBN.mul(new BN(2)))
|
||||
.add(callGasBN.mul(gasPriceBN))
|
||||
.add(gasPriceBN.mul(new BN(EAC_SCHEDULING_CONFIG.FUTURE_EXECUTION_COST)))
|
||||
.add(callValueBN);
|
||||
};
|
||||
|
||||
const getSchedulingTransaction = (state: AppState): IGetSchedulingTransaction => {
|
||||
const currentTo = getCurrentTo(state);
|
||||
const currentValue = getCurrentValue(state);
|
||||
const transactionFields = getFields(state);
|
||||
const unit = getUnit(state);
|
||||
const dataExists = getDataExists(state);
|
||||
const callData = getData(state);
|
||||
const validGasCost = getValidGasCost(state);
|
||||
const windowStart = getWindowStart(state);
|
||||
const gasLimit = getGasLimit(state);
|
||||
const nonce = getNonce(state);
|
||||
const gasPrice = getGasPrice(state);
|
||||
|
||||
const isFullTransaction = isFullTx(
|
||||
state,
|
||||
transactionFields,
|
||||
currentTo,
|
||||
currentValue,
|
||||
dataExists,
|
||||
validGasCost,
|
||||
unit
|
||||
);
|
||||
|
||||
const WINDOW_SIZE_IN_BLOCKS = EAC_SCHEDULING_CONFIG.WINDOW_SIZE_IN_BLOCKS;
|
||||
const SCHEDULING_GAS_LIMIT = new BN(EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT);
|
||||
const EAC_FEE = 0;
|
||||
const PAYMENT = EAC_SCHEDULING_CONFIG.PAYMENT;
|
||||
const REQUIRED_DEPOSIT = 0;
|
||||
|
||||
const EAC_ADDRESSES = {
|
||||
KOVAN: {
|
||||
blockScheduler: '0x1afc19a7e642761ba2b55d2a45b32c7ef08269d1'
|
||||
}
|
||||
};
|
||||
|
||||
const transactionData = getScheduleData(
|
||||
currentTo.raw,
|
||||
callData.raw,
|
||||
parseInt(gasLimit.raw, 10),
|
||||
currentValue.value,
|
||||
WINDOW_SIZE_IN_BLOCKS,
|
||||
windowStart.value,
|
||||
gasPrice.value,
|
||||
EAC_FEE,
|
||||
PAYMENT,
|
||||
REQUIRED_DEPOSIT
|
||||
);
|
||||
|
||||
const endowment = calcEACEndowment(
|
||||
gasLimit.value || 21000,
|
||||
currentValue.value || 0,
|
||||
gasPrice.value,
|
||||
EAC_FEE,
|
||||
PAYMENT
|
||||
);
|
||||
|
||||
const transactionOptions = {
|
||||
to: Address(EAC_ADDRESSES.KOVAN.blockScheduler),
|
||||
data: transactionData,
|
||||
gasLimit: SCHEDULING_GAS_LIMIT,
|
||||
gasPrice: gasPrice.value,
|
||||
nonce: new BN(0),
|
||||
value: endowment
|
||||
};
|
||||
|
||||
if (nonce) {
|
||||
transactionOptions.nonce = new BN(nonce.raw);
|
||||
}
|
||||
|
||||
const transaction: EthTx = makeTransaction(transactionOptions);
|
||||
|
||||
return {
|
||||
transaction,
|
||||
isFullTransaction,
|
||||
isWindowStartValid: isWindowStartValid(transactionFields, getLatestBlock(state))
|
||||
};
|
||||
};
|
||||
|
||||
const nonStandardTransaction = (state: AppState): boolean => {
|
||||
const etherTransaction = isEtherTransaction(state);
|
||||
const { isFullTransaction } = getTransaction(state);
|
||||
@ -89,9 +224,11 @@ const serializedAndTransactionFieldsMatch = (state: AppState, isLocallySigned: b
|
||||
};
|
||||
|
||||
export {
|
||||
getSchedulingTransaction,
|
||||
getTransaction,
|
||||
getTransactionState,
|
||||
getGasCost,
|
||||
nonStandardTransaction,
|
||||
serializedAndTransactionFieldsMatch
|
||||
serializedAndTransactionFieldsMatch,
|
||||
getScheduleData
|
||||
};
|
||||
|
22
common/selectors/transaction/windowStart.ts
Normal file
22
common/selectors/transaction/windowStart.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { AppState } from 'reducers';
|
||||
import { getWindowStart } from './fields';
|
||||
import { getLatestBlock } from '../config';
|
||||
|
||||
interface ICurrentWindowStart {
|
||||
raw: string;
|
||||
value: number | null;
|
||||
}
|
||||
|
||||
const isValidCurrentWindowStart = (state: AppState) => {
|
||||
const currentWindowStart = getWindowStart(state);
|
||||
|
||||
if (!currentWindowStart.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentWindowStart.value > parseInt(getLatestBlock(state), 10);
|
||||
};
|
||||
|
||||
const getCurrentWindowStart = (state: AppState): ICurrentWindowStart => getWindowStart(state);
|
||||
|
||||
export { getCurrentWindowStart, ICurrentWindowStart, isValidCurrentWindowStart };
|
@ -105,6 +105,27 @@
|
||||
"SCAN_TOKENS_FAIL": "Failed to fetch token values",
|
||||
"SCAN_TOKENS_FAIL_NO_TOKENS": "No tokens found",
|
||||
"SCAN_TOKENS_OFFLINE": "Token balances are unavailable offline",
|
||||
"SCHEDULING_TOGGLE": "Send Later ",
|
||||
"SCHEDULING_TITLE": "Scheduled Transaction Settings",
|
||||
"SCHEDULING_DESCRIPTION": "This allows you to schedule a transaction for sending at a later time. Due to unforeseen circumstances (like the state of the network), we cannot 100% guarantee that your transaction is sent in the time period you specify.",
|
||||
"SCHEDULE_BLOCK": "Block Number ",
|
||||
"SCHEDULE_BLOCK_PLACEHOLDER": "Enter Block Number ",
|
||||
"SCHEDULE_DEPOSIT": "Require Deposit (optional) ",
|
||||
"SCHEDULE_DEPOSIT_PLACEHOLDER": "0.00001 ",
|
||||
"SCHEDULE_DEPOSIT_TOOLTIP": "Require TimeNode to deposit a given amount of ETH in order to gain an exclusive time window for execution.",
|
||||
"SCHEDULE_TIMESTAMP": "Date & Time ",
|
||||
"SCHEDULE_TIMEZONE": "Timezone ",
|
||||
"SCHEDULE_TIMEBOUNTY": "Time Bounty ",
|
||||
"SCHEDULE_TIMEBOUNTY_PLACEHOLDER": "Enter Time Bounty ",
|
||||
"SCHEDULE_TIMEBOUNTY_TOOLTIP": "The amount of ETH you wish to offer to TimeNodes in exchange for execution. The higher the Time Bounty, the likelier your transaction will get executed. ",
|
||||
"SCHEDULE_CHECK": "Check on Chronos ",
|
||||
"SCHEDULE_SCHEDULE": "Schedule transaction ",
|
||||
"SCHEDULE_TYPE_TIME": "Minutes ",
|
||||
"SCHEDULE_TYPE_BLOCK": "Blocks ",
|
||||
"SCHEDULE_GAS_PRICE": "Future Gas Price",
|
||||
"SCHEDULE_GAS_LIMIT": "Future Gas Limit",
|
||||
"SCHEDULE_WINDOW_SIZE": "Window ",
|
||||
"SCHEDULE_WINDOW_SIZE_TOOLTIP": "Window ",
|
||||
"SEND_GAS": "Gas ",
|
||||
"SEND_TRANSFERTOTAL": "Send Entire Balance ",
|
||||
"SEND_GENERATE": "Generate Transaction ",
|
||||
|
4
common/typescript/ethereumjs-abi.d.ts
vendored
4
common/typescript/ethereumjs-abi.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
declare module 'ethereumjs-abi' {
|
||||
import BN from 'bn.js';
|
||||
type Values = (string | number | BN)[];
|
||||
type Values = (string | number | BN | Array<any>)[];
|
||||
type Types = string[];
|
||||
export function eventID(name: string, types: Types): Buffer;
|
||||
export function methodID(name: string, types: Types): Buffer;
|
||||
@ -9,7 +9,7 @@ declare module 'ethereumjs-abi' {
|
||||
types: Types,
|
||||
data: string | Buffer
|
||||
): (Buffer | boolean | number | BN | string)[];
|
||||
export function simpleEncode(method: string, values: Values): Buffer;
|
||||
export function simpleEncode(method: string, ...values: Values): Buffer;
|
||||
export function simpleDecode(
|
||||
method: string,
|
||||
data: string | Buffer
|
||||
|
3
shared/types/eac.js-lib.d.ts
vendored
Normal file
3
shared/types/eac.js-lib.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
declare module 'eac.js-lib' {
|
||||
export default function(web3: any): any;
|
||||
}
|
@ -42,5 +42,6 @@ exports[`render snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
requestDisabled={false}
|
||||
scheduleDisabled={true}
|
||||
/>
|
||||
`;
|
||||
|
@ -11,7 +11,8 @@ describe('fields reducer', () => {
|
||||
nonce: { raw: '', value: null },
|
||||
value: { raw: '', value: null },
|
||||
gasLimit: { raw: '21000', value: new BN(21000) },
|
||||
gasPrice: { raw: '20', value: gasPricetoBase(20) }
|
||||
gasPrice: { raw: '20', value: gasPricetoBase(20) },
|
||||
windowStart: { raw: '', value: null }
|
||||
};
|
||||
const testPayload = { raw: 'test', value: null };
|
||||
|
||||
|
@ -39,6 +39,10 @@ describe('fields selector', () => {
|
||||
gasPrice: {
|
||||
raw: '1500',
|
||||
value: Wei('1500')
|
||||
},
|
||||
windowStart: {
|
||||
raw: '',
|
||||
value: null
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -43,6 +43,10 @@ describe('helpers selector', () => {
|
||||
gasPrice: {
|
||||
raw: '1500',
|
||||
value: Wei('1500')
|
||||
},
|
||||
windowStart: {
|
||||
raw: '',
|
||||
value: null
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -54,7 +58,8 @@ describe('helpers selector', () => {
|
||||
gasPrice: Wei('1500'),
|
||||
nonce: new BN('0'),
|
||||
to: new Buffer([0, 1, 2, 3]),
|
||||
value: Wei('1000000000')
|
||||
value: Wei('1000000000'),
|
||||
windowStart: null
|
||||
};
|
||||
expect(reduceToValues(state.transaction.fields)).toEqual(values);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user