Merge pull request #623 from MyEtherWallet/develop

Tag Release 0.0.6
This commit is contained in:
Daniel Ternyak 2017-12-18 20:16:41 -06:00 committed by GitHub
commit 6944889fe3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
420 changed files with 10753 additions and 6532 deletions

9
.prettierrc Normal file
View File

@ -0,0 +1,9 @@
{
"printWidth": 100,
"singleQuote": true,
"useTabs": false,
"semi": true,
"tabWidth": 2,
"trailingComma":
"none"
}

View File

@ -2,22 +2,36 @@ dist: trusty
sudo: required
language: node_js
cache:
directories:
- node_modules
services:
- docker
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- docker pull dternyak/eth-priv-to-addr:latest
install:
- npm install --silent
jobs:
include:
- stage: test
script: npm run test
- stage: test
script: npm run tslint
- stage: test
script: npm run tscheck
- stage: test
script: npm run freezer
- stage: test
script: npm run freezer:validate
notifications:
email:
on_success: never
on_failure: never
script:
- npm run test
- npm run tslint
- npm run tscheck
- npm run freezer
- npm run freezer:validate
on_failure: never

View File

@ -16,12 +16,18 @@ npm run build # build app
It generates app in `dist` folder.
#### Test:
#### Unit Tests:
```bash
npm run test # run tests with Jest
```
#### Integration Tests:
```bash
npm run test:int # run tests with Jest
```
#### Dev (HTTPS):
1. Create your own SSL Certificate (Heroku has a [nice guide here](https://devcenter.heroku.com/articles/ssl-certificate-self))
@ -32,7 +38,13 @@ npm run test # run tests with Jest
npm run dev:https
```
#### Derivation Check:
#### Address Derivation Checker:
EthereumJS-Util previously contained a bug that would incorrectly derive addresses from private keys with a 1/128 probability of occurring. A summary of this issue can be found [here](https://www.reddit.com/r/ethereum/comments/48rt6n/using_myetherwalletcom_just_burned_me_for/d0m4c6l/).
As a reactionary measure, the address derivation checker was created.
To test for correct address derivation, the address derivation checker uses multiple sources of address derivation (EthereumJS and PyEthereum) to ensure that multiple official implementations derive the same address for any given private key.
##### The derivation checker utility assumes that you have:
1. Docker installed/available
2. [dternyak/eth-priv-to-addr](https://hub.docker.com/r/dternyak/eth-priv-to-addr/) pulled from DockerHub
@ -41,9 +53,12 @@ npm run dev:https
1. Install docker (on macOS, [Docker for Mac](https://docs.docker.com/docker-for-mac/) is suggested)
2. `docker pull dternyak/eth-priv-to-addr`
##### Run Derivation Checker
The derivation checker utility runs as part of the integration test suite.
```bash
npm run derivation-checker
npm run test:int
```
## Folder structure:

View File

@ -8,10 +8,9 @@ import GenerateWallet from 'containers/Tabs/GenerateWallet';
import Help from 'containers/Tabs/Help';
import SendTransaction from 'containers/Tabs/SendTransaction';
import Swap from 'containers/Tabs/Swap';
import ViewWallet from 'containers/Tabs/ViewWallet';
import SignAndVerifyMessage from 'containers/Tabs/SignAndVerifyMessage';
import BroadcastTx from 'containers/Tabs/BroadcastTx';
import RestoreKeystore from 'containers/Tabs/RestoreKeystore';
import ErrorScreen from 'components/ErrorScreen';
// TODO: fix this
interface Props {
@ -19,26 +18,43 @@ interface Props {
history: any;
}
export default class Root extends Component<Props, {}> {
interface State {
error: Error | null;
}
export default class Root extends Component<Props, State> {
public state = {
error: null
};
public componentDidCatch(error) {
this.setState({ error });
}
public render() {
const { store, history } = this.props;
const { error } = this.state;
if (error) {
return <ErrorScreen error={error} />;
}
// key={Math.random()} = hack for HMR from https://github.com/webpack/webpack-dev-server/issues/395
return (
<Provider store={store} key={Math.random()}>
<Router history={history} key={Math.random()}>
<div>
<Route exact={true} path="/" component={GenerateWallet} />
<Route path="/view-wallet" component={ViewWallet} />
<Route path="/help" component={Help} />
<Route path="/swap" component={Swap} />
<Route path="/account" component={SendTransaction}>
<Route path="send" component={SendTransaction} />
<Route path="info" component={SendTransaction} />
</Route>
<Route path="/send-transaction" component={SendTransaction} />
<Route path="/contracts" component={Contracts} />
<Route path="/ens" component={ENS} />
<Route path="/utilities" component={RestoreKeystore} />
<Route
path="/sign-and-verify-message"
component={SignAndVerifyMessage}
/>
<Route path="/sign-and-verify-message" component={SignAndVerifyMessage} />
<Route path="/pushTx" component={BroadcastTx} />
<LegacyRoutes />
</div>
@ -71,7 +87,7 @@ const LegacyRoutes = withRouter(props => {
history.push('/ens');
break;
case '#view-wallet-info':
history.push('/view-wallet');
history.push('/account/info');
break;
case '#check-tx-status':
history.push('/check-tx-status');
@ -83,6 +99,7 @@ const LegacyRoutes = withRouter(props => {
<Switch>
<Redirect from="/signmsg.html" to="/sign-and-verify-message" />
<Redirect from="/helpers.html" to="/helpers" />
<Redirect from="/send-transaction" to="/account/send" />
</Switch>
);
});

View File

@ -25,24 +25,13 @@ export function changeLanguage(sign: string): interfaces.ChangeLanguageAction {
}
export type TChangeNode = typeof changeNode;
export function changeNode(
nodeSelection: string,
node: NodeConfig
): interfaces.ChangeNodeAction {
export function changeNode(nodeSelection: string, node: NodeConfig): interfaces.ChangeNodeAction {
return {
type: TypeKeys.CONFIG_NODE_CHANGE,
payload: { nodeSelection, node }
};
}
export type TChangeGasPrice = typeof changeGasPrice;
export function changeGasPrice(value: number): interfaces.ChangeGasPriceAction {
return {
type: TypeKeys.CONFIG_GAS_PRICE,
payload: value
};
}
export type TPollOfflineStatus = typeof pollOfflineStatus;
export function pollOfflineStatus(): interfaces.PollOfflineStatus {
return {
@ -51,9 +40,7 @@ export function pollOfflineStatus(): interfaces.PollOfflineStatus {
}
export type TChangeNodeIntent = typeof changeNodeIntent;
export function changeNodeIntent(
payload: string
): interfaces.ChangeNodeIntentAction {
export function changeNodeIntent(payload: string): interfaces.ChangeNodeIntentAction {
return {
type: TypeKeys.CONFIG_NODE_CHANGE_INTENT,
payload
@ -61,9 +48,7 @@ export function changeNodeIntent(
}
export type TAddCustomNode = typeof addCustomNode;
export function addCustomNode(
payload: CustomNodeConfig
): interfaces.AddCustomNodeAction {
export function addCustomNode(payload: CustomNodeConfig): interfaces.AddCustomNodeAction {
return {
type: TypeKeys.CONFIG_ADD_CUSTOM_NODE,
payload
@ -71,9 +56,7 @@ export function addCustomNode(
}
export type TRemoveCustomNode = typeof removeCustomNode;
export function removeCustomNode(
payload: CustomNodeConfig
): interfaces.RemoveCustomNodeAction {
export function removeCustomNode(payload: CustomNodeConfig): interfaces.RemoveCustomNodeAction {
return {
type: TypeKeys.CONFIG_REMOVE_CUSTOM_NODE,
payload
@ -81,9 +64,7 @@ export function removeCustomNode(
}
export type TAddCustomNetwork = typeof addCustomNetwork;
export function addCustomNetwork(
payload: CustomNetworkConfig
): interfaces.AddCustomNetworkAction {
export function addCustomNetwork(payload: CustomNetworkConfig): interfaces.AddCustomNetworkAction {
return {
type: TypeKeys.CONFIG_ADD_CUSTOM_NETWORK,
payload
@ -101,9 +82,7 @@ export function removeCustomNetwork(
}
export type TSetLatestBlock = typeof setLatestBlock;
export function setLatestBlock(
payload: string
): interfaces.SetLatestBlockAction {
export function setLatestBlock(payload: string): interfaces.SetLatestBlockAction {
return {
type: TypeKeys.CONFIG_SET_LATEST_BLOCK,
payload

View File

@ -27,12 +27,6 @@ export interface ChangeNodeAction {
};
}
/*** Change gas price ***/
export interface ChangeGasPriceAction {
type: TypeKeys.CONFIG_GAS_PRICE;
payload: number;
}
/*** Poll offline status ***/
export interface PollOfflineStatus {
type: TypeKeys.CONFIG_POLL_OFFLINE_STATUS;
@ -83,7 +77,6 @@ export interface Web3UnsetNodeAction {
export type ConfigAction =
| ChangeNodeAction
| ChangeLanguageAction
| ChangeGasPriceAction
| ToggleOfflineAction
| PollOfflineStatus
| ForceOfflineAction

View File

@ -2,7 +2,6 @@ export enum TypeKeys {
CONFIG_LANGUAGE_CHANGE = 'CONFIG_LANGUAGE_CHANGE',
CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE',
CONFIG_NODE_CHANGE_INTENT = 'CONFIG_NODE_CHANGE_INTENT',
CONFIG_GAS_PRICE = 'CONFIG_GAS_PRICE',
CONFIG_TOGGLE_OFFLINE = 'CONFIG_TOGGLE_OFFLINE',
CONFIG_FORCE_OFFLINE = 'CONFIG_FORCE_OFFLINE',
CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS',

View File

@ -1,2 +1,3 @@
export * from './actionCreators';
export * from './actionTypes';
export * from './constants';

View File

@ -3,9 +3,7 @@ import * as interfaces from './actionTypes';
import { TypeKeys } from './constants';
export type TAddCustomToken = typeof addCustomToken;
export function addCustomToken(
payload: Token
): interfaces.AddCustomTokenAction {
export function addCustomToken(payload: Token): interfaces.AddCustomTokenAction {
return {
type: TypeKeys.CUSTOM_TOKEN_ADD,
payload
@ -14,9 +12,7 @@ export function addCustomToken(
export type TRemoveCustomToken = typeof removeCustomToken;
export function removeCustomToken(
payload: string
): interfaces.RemoveCustomTokenAction {
export function removeCustomToken(payload: string): interfaces.RemoveCustomTokenAction {
return {
type: TypeKeys.CUSTOM_TOKEN_REMOVE,
payload

View File

@ -1,2 +1,3 @@
export * from './actionCreators';
export * from './actionTypes';
export * from './constants';

View File

@ -26,9 +26,7 @@ export function setDeterministicWallets(
};
}
export function setDesiredToken(
token: string | undefined
): interfaces.SetDesiredTokenAction {
export function setDesiredToken(token: string | undefined): interfaces.SetDesiredTokenAction {
return {
type: TypeKeys.DW_SET_DESIRED_TOKEN,
payload: token

View File

@ -3,9 +3,7 @@ import * as interfaces from './actionTypes';
import { TypeKeys } from './constants';
export type TGenerateNewWallet = typeof generateNewWallet;
export function generateNewWallet(
password: string
): interfaces.GenerateNewWalletAction {
export function generateNewWallet(password: string): interfaces.GenerateNewWalletAction {
return {
type: TypeKeys.GENERATE_WALLET_GENERATE_WALLET,
wallet: generate(),

View File

@ -20,9 +20,7 @@ export function showNotification(
}
export type TCloseNotification = typeof closeNotification;
export function closeNotification(
notification: types.Notification
): types.CloseNotificationAction {
export function closeNotification(notification: types.Notification): types.CloseNotificationAction {
return {
type: TypeKeys.CLOSE_NOTIFICATION,
payload: notification

View File

@ -23,6 +23,4 @@ export interface ShowNotificationAction {
}
/*** Union Type ***/
export type NotificationsAction =
| ShowNotificationAction
| CloseNotificationAction;
export type NotificationsAction = ShowNotificationAction | CloseNotificationAction;

View File

@ -11,9 +11,7 @@ export function fetchCCRates(symbols: string[] = []): interfaces.FetchCCRates {
}
export type TFetchCCRatesSucceeded = typeof fetchCCRatesSucceeded;
export function fetchCCRatesSucceeded(
payload: CCResponse
): interfaces.FetchCCRatesSucceeded {
export function fetchCCRatesSucceeded(payload: CCResponse): interfaces.FetchCCRatesSucceeded {
return {
type: TypeKeys.RATES_FETCH_CC_SUCCEEDED,
payload

View File

@ -36,10 +36,12 @@ export const fetchRates = (symbols: string[] = []): Promise<CCResponse> =>
// to their respective rates via ETH.
return symbols.reduce(
(eqRates, sym) => {
eqRates[sym] = rateSymbols.reduce((symRates, rateSym) => {
symRates[rateSym] = 1 / rates[sym] * rates[rateSym];
return symRates;
}, {});
if (rates[sym]) {
eqRates[sym] = rateSymbols.reduce((symRates, rateSym) => {
symRates[rateSym] = 1 / rates[sym] * rates[rateSym];
return symRates;
}, {});
}
return eqRates;
},
{

View File

@ -17,7 +17,4 @@ export interface FetchCCRatesFailed {
}
/*** Union Type ***/
export type RatesAction =
| FetchCCRates
| FetchCCRatesSucceeded
| FetchCCRatesFailed;
export type RatesAction = FetchCCRates | FetchCCRatesSucceeded | FetchCCRatesFailed;

View File

@ -2,58 +2,24 @@ import * as interfaces from './actionTypes';
import { TypeKeys } from './constants';
export type TChangeStepSwap = typeof changeStepSwap;
export function changeStepSwap(
payload: number
): interfaces.ChangeStepSwapAction {
export function changeStepSwap(payload: number): interfaces.ChangeStepSwapAction {
return {
type: TypeKeys.SWAP_STEP,
payload
};
}
export type TOriginKindSwap = typeof originKindSwap;
export function originKindSwap(
payload: string
): interfaces.OriginKindSwapAction {
export type TInitSwap = typeof initSwap;
export function initSwap(payload: interfaces.SwapInputs): interfaces.InitSwap {
return {
type: TypeKeys.SWAP_ORIGIN_KIND,
payload
};
}
export type TDestinationKindSwap = typeof destinationKindSwap;
export function destinationKindSwap(
payload: string
): interfaces.DestinationKindSwapAction {
return {
type: TypeKeys.SWAP_DESTINATION_KIND,
payload
};
}
export type TOriginAmountSwap = typeof originAmountSwap;
export function originAmountSwap(
payload?: number | null
): interfaces.OriginAmountSwapAction {
return {
type: TypeKeys.SWAP_ORIGIN_AMOUNT,
payload
};
}
export type TDestinationAmountSwap = typeof destinationAmountSwap;
export function destinationAmountSwap(
payload?: number | null
): interfaces.DestinationAmountSwapAction {
return {
type: TypeKeys.SWAP_DESTINATION_AMOUNT,
type: TypeKeys.SWAP_INIT,
payload
};
}
export type TLoadBityRatesSucceededSwap = typeof loadBityRatesSucceededSwap;
export function loadBityRatesSucceededSwap(
payload: interfaces.Pairs
payload: interfaces.ApiResponse
): interfaces.LoadBityRatesSucceededSwapAction {
return {
type: TypeKeys.SWAP_LOAD_BITY_RATES_SUCCEEDED,
@ -62,9 +28,7 @@ export function loadBityRatesSucceededSwap(
}
export type TDestinationAddressSwap = typeof destinationAddressSwap;
export function destinationAddressSwap(
payload?: string
): interfaces.DestinationAddressSwapAction {
export function destinationAddressSwap(payload?: string): interfaces.DestinationAddressSwapAction {
return {
type: TypeKeys.SWAP_DESTINATION_ADDRESS,
payload
@ -93,9 +57,7 @@ export function stopLoadBityRatesSwap(): interfaces.StopLoadBityRatesSwapAction
}
export type TOrderTimeSwap = typeof orderTimeSwap;
export function orderTimeSwap(
payload: number
): interfaces.OrderSwapTimeSwapAction {
export function orderTimeSwap(payload: number): interfaces.OrderSwapTimeSwapAction {
return {
type: TypeKeys.SWAP_ORDER_TIME,
payload

View File

@ -1,4 +1,5 @@
import { TypeKeys } from './constants';
export interface Pairs {
ETHBTC: number;
ETHREP: number;
@ -6,29 +7,38 @@ export interface Pairs {
BTCREP: number;
}
export interface OriginKindSwapAction {
type: TypeKeys.SWAP_ORIGIN_KIND;
payload: string;
export interface SwapInput {
id: string;
amount: number;
}
export interface DestinationKindSwapAction {
type: TypeKeys.SWAP_DESTINATION_KIND;
payload: string;
export interface SwapInputs {
origin: SwapInput;
destination: SwapInput;
}
export interface OriginAmountSwapAction {
type: TypeKeys.SWAP_ORIGIN_AMOUNT;
payload?: number | null;
export interface InitSwap {
type: TypeKeys.SWAP_INIT;
payload: SwapInputs;
}
export interface DestinationAmountSwapAction {
type: TypeKeys.SWAP_DESTINATION_AMOUNT;
payload?: number | null;
export interface Option {
id: string;
}
export interface ApiResponseObj {
id: string;
options: Option[];
rate: number;
}
export interface ApiResponse {
[name: string]: ApiResponseObj;
}
export interface LoadBityRatesSucceededSwapAction {
type: TypeKeys.SWAP_LOAD_BITY_RATES_SUCCEEDED;
payload: Pairs;
payload: ApiResponse;
}
export interface DestinationAddressSwapAction {
@ -135,10 +145,7 @@ export interface StopPollBityOrderStatusAction {
/*** Action Type Union ***/
export type SwapAction =
| ChangeStepSwapAction
| OriginKindSwapAction
| DestinationKindSwapAction
| OriginAmountSwapAction
| DestinationAmountSwapAction
| InitSwap
| LoadBityRatesSucceededSwapAction
| DestinationAddressSwapAction
| RestartSwapAction

View File

@ -1,9 +1,6 @@
export enum TypeKeys {
SWAP_STEP = 'SWAP_STEP',
SWAP_ORIGIN_KIND = 'SWAP_ORIGIN_KIND',
SWAP_DESTINATION_KIND = 'SWAP_DESTINATION_KIND',
SWAP_ORIGIN_AMOUNT = 'SWAP_ORIGIN_AMOUNT',
SWAP_DESTINATION_AMOUNT = 'SWAP_DESTINATION_AMOUNT',
SWAP_INIT = 'SWAP_INIT',
SWAP_LOAD_BITY_RATES_SUCCEEDED = 'SWAP_LOAD_BITY_RATES_SUCCEEDED',
SWAP_DESTINATION_ADDRESS = 'SWAP_DESTINATION_ADDRESS',
SWAP_RESTART = 'SWAP_RESTART',

View File

@ -0,0 +1,55 @@
import {
BroadcastLocalTransactionRequestedAction,
BroadcastWeb3TransactionRequestedAction,
BroadcastTransactionFailedAction,
BroadcastTransactionSucceededAction,
BroadcastTransactionQueuedAction
} from '../actionTypes';
import { TypeKeys } from '../constants';
type TBroadcastLocalTransactionRequested = typeof broadcastLocalTransactionRequested;
const broadcastLocalTransactionRequested = (): BroadcastLocalTransactionRequestedAction => ({
type: TypeKeys.BROADCAST_LOCAL_TRANSACTION_REQUESTED
});
type TBroadcastWeb3TransactionRequested = typeof broadcastWeb3TransactionRequested;
const broadcastWeb3TransactionRequested = (): BroadcastWeb3TransactionRequestedAction => ({
type: TypeKeys.BROADCAST_WEB3_TRANSACTION_REQUESTED
});
type TBroadcastTransactionSucceeded = typeof broadcastTransactionSucceeded;
const broadcastTransactionSucceeded = (
payload: BroadcastTransactionSucceededAction['payload']
): BroadcastTransactionSucceededAction => ({
type: TypeKeys.BROADCAST_TRANSACTION_SUCCEEDED,
payload
});
type TBroadcastTransactionFailed = typeof broadcastTransactionFailed;
const broadcastTransactionFailed = (
payload: BroadcastTransactionFailedAction['payload']
): BroadcastTransactionFailedAction => ({
type: TypeKeys.BROADCAST_TRASACTION_FAILED,
payload
});
type TBroadcastTransactionQueued = typeof broadcastTransactionQueued;
const broadcastTransactionQueued = (
payload: BroadcastTransactionQueuedAction['payload']
): BroadcastTransactionQueuedAction => ({
type: TypeKeys.BROADCAST_TRANSACTION_QUEUED,
payload
});
export {
broadcastLocalTransactionRequested,
broadcastWeb3TransactionRequested,
broadcastTransactionSucceeded,
broadcastTransactionFailed,
broadcastTransactionQueued,
TBroadcastLocalTransactionRequested,
TBroadcastWeb3TransactionRequested,
TBroadcastTransactionSucceeded,
TBroadcastTransactionFailed,
TBroadcastTransactionQueued
};

View File

@ -0,0 +1,16 @@
import { SetCurrentToAction, SetCurrentValueAction } from '../actionTypes/current';
import { TypeKeys } from '../';
type TSetCurrentValue = typeof setCurrentValue;
const setCurrentValue = (payload: SetCurrentValueAction['payload']): SetCurrentValueAction => ({
type: TypeKeys.CURRENT_VALUE_SET,
payload
});
type TSetCurrentTo = typeof setCurrentTo;
const setCurrentTo = (payload: SetCurrentToAction['payload']): SetCurrentToAction => ({
type: TypeKeys.CURRENT_TO_SET,
payload
});
export { setCurrentValue, setCurrentTo, TSetCurrentTo, TSetCurrentValue };

View File

@ -0,0 +1,93 @@
import {
SetGasLimitFieldAction,
SetDataFieldAction,
SetToFieldAction,
SetNonceFieldAction,
SetValueFieldAction,
InputGasLimitAction,
InputDataAction,
InputNonceAction,
ResetAction,
SetGasPriceFieldAction
} from '../actionTypes';
import { TypeKeys } from 'actions/transaction/constants';
type TInputGasLimit = typeof inputGasLimit;
const inputGasLimit = (payload: InputGasLimitAction['payload']) => ({
type: TypeKeys.GAS_LIMIT_INPUT,
payload
});
type TInputNonce = typeof inputNonce;
const inputNonce = (payload: InputNonceAction['payload']) => ({
type: TypeKeys.NONCE_INPUT,
payload
});
type TInputData = typeof inputData;
const inputData = (payload: InputDataAction['payload']) => ({
type: TypeKeys.DATA_FIELD_INPUT,
payload
});
type TSetGasLimitField = typeof setGasLimitField;
const setGasLimitField = (payload: SetGasLimitFieldAction['payload']): SetGasLimitFieldAction => ({
type: TypeKeys.GAS_LIMIT_FIELD_SET,
payload
});
type TSetDataField = typeof setDataField;
const setDataField = (payload: SetDataFieldAction['payload']): SetDataFieldAction => ({
type: TypeKeys.DATA_FIELD_SET,
payload
});
type TSetToField = typeof setToField;
const setToField = (payload: SetToFieldAction['payload']): SetToFieldAction => ({
type: TypeKeys.TO_FIELD_SET,
payload
});
type TSetNonceField = typeof setNonceField;
const setNonceField = (payload: SetNonceFieldAction['payload']): SetNonceFieldAction => ({
type: TypeKeys.NONCE_FIELD_SET,
payload
});
type TSetValueField = typeof setValueField;
const setValueField = (payload: SetValueFieldAction['payload']): SetValueFieldAction => ({
type: TypeKeys.VALUE_FIELD_SET,
payload
});
type TSetGasPriceField = typeof setGasPriceField;
const setGasPriceField = (payload: SetGasPriceFieldAction['payload']): SetGasPriceFieldAction => ({
type: TypeKeys.GAS_PRICE_FIELD_SET,
payload
});
type TReset = typeof reset;
const reset = (): ResetAction => ({ type: TypeKeys.RESET });
export {
TInputGasLimit,
TInputNonce,
TInputData,
TSetGasLimitField,
TSetDataField,
TSetToField,
TSetNonceField,
TSetValueField,
TSetGasPriceField,
TReset,
inputGasLimit,
inputNonce,
inputData,
setGasLimitField,
setDataField,
setToField,
setNonceField,
setValueField,
setGasPriceField,
reset
};

View File

@ -0,0 +1,7 @@
export * from './fields';
export * from './meta';
export * from './network';
export * from './sign';
export * from './broadcast';
export * from './current';
export * from './sendEverything';

View File

@ -0,0 +1,27 @@
import {
TypeKeys,
SetUnitMetaAction,
SetTokenValueMetaAction,
SetTokenToMetaAction
} from 'actions/transaction';
type TSetTokenBalance = typeof setTokenValue;
type TSetUnitMeta = typeof setUnitMeta;
type TSetTokenTo = typeof setTokenTo;
const setTokenTo = (payload: SetTokenToMetaAction['payload']): SetTokenToMetaAction => ({
type: TypeKeys.TOKEN_TO_META_SET,
payload
});
const setTokenValue = (payload: SetTokenValueMetaAction['payload']): SetTokenValueMetaAction => ({
type: TypeKeys.TOKEN_VALUE_META_SET,
payload
});
const setUnitMeta = (payload: SetUnitMetaAction['payload']): SetUnitMetaAction => ({
type: TypeKeys.UNIT_META_SET,
payload
});
export { TSetUnitMeta, TSetTokenBalance, TSetTokenTo, setUnitMeta, setTokenValue, setTokenTo };

View File

@ -0,0 +1,82 @@
import {
EstimateGasFailedAction,
EstimateGasRequestedAction,
TypeKeys,
EstimateGasSucceededAction,
GetFromRequestedAction,
GetFromSucceededAction,
GetFromFailedAction,
GetNonceRequestedAction,
GetNonceSucceededAction,
GetNonceFailedAction
} from 'actions/transaction';
type TEstimateGasRequested = typeof estimateGasRequested;
const estimateGasRequested = (
payload: EstimateGasRequestedAction['payload']
): EstimateGasRequestedAction => ({
type: TypeKeys.ESTIMATE_GAS_REQUESTED,
payload
});
type TEstimateGasSucceeded = typeof estimateGasSucceeded;
const estimateGasSucceeded = (): EstimateGasSucceededAction => ({
type: TypeKeys.ESTIMATE_GAS_SUCCEEDED
});
type TEstimateGasFailed = typeof estimateGasFailed;
const estimateGasFailed = (): EstimateGasFailedAction => ({
type: TypeKeys.ESTIMATE_GAS_FAILED
});
type TGetFromRequested = typeof getFromRequested;
const getFromRequested = (): GetFromRequestedAction => ({
type: TypeKeys.GET_FROM_REQUESTED
});
type TGetFromSucceeded = typeof getFromSucceeded;
const getFromSucceeded = (payload: GetFromSucceededAction['payload']): GetFromSucceededAction => ({
type: TypeKeys.GET_FROM_SUCCEEDED,
payload
});
type TGetFromFailed = typeof getFromFailed;
const getFromFailed = (): GetFromFailedAction => ({
type: TypeKeys.GET_FROM_FAILED
});
type TGetNonceRequested = typeof getNonceRequested;
const getNonceRequested = (): GetNonceRequestedAction => ({
type: TypeKeys.GET_NONCE_REQUESTED
});
type TGetNonceSucceeded = typeof getNonceSucceeded;
const getNonceSucceeded = (
payload: GetNonceSucceededAction['payload']
): GetNonceSucceededAction => ({ type: TypeKeys.GET_NONCE_SUCCEEDED, payload });
type TGetNonceFailed = typeof getNonceFailed;
const getNonceFailed = (): GetNonceFailedAction => ({
type: TypeKeys.GET_NONCE_FAILED
});
export {
estimateGasRequested,
estimateGasFailed,
estimateGasSucceeded,
getFromRequested,
getFromSucceeded,
getFromFailed,
getNonceRequested,
getNonceFailed,
getNonceSucceeded,
TEstimateGasRequested,
TEstimateGasFailed,
TEstimateGasSucceeded,
TGetFromRequested,
TGetFromSucceeded,
TGetNonceRequested,
TGetNonceSucceeded,
TGetNonceFailed,
TGetFromFailed
};

View File

@ -0,0 +1,30 @@
import {
SendEverythingFailedAction,
SendEverythingRequestedAction,
SendEverythingSucceededAction
} from '../actionTypes/sendEverything';
import { TypeKeys } from 'actions/transaction';
type TSendEverythingRequested = typeof sendEverythingRequested;
const sendEverythingRequested = (): SendEverythingRequestedAction => ({
type: TypeKeys.SEND_EVERYTHING_REQUESTED
});
type TSendEverythingFailed = typeof sendEverythingFailed;
const sendEverythingFailed = (): SendEverythingFailedAction => ({
type: TypeKeys.SEND_EVERYTHING_FAILED
});
type TSendEverythingSucceeded = typeof sendEverythingSucceeded;
const sendEverythingSucceeded = (): SendEverythingSucceededAction => ({
type: TypeKeys.SEND_EVERYTHING_SUCCEEDED
});
export {
TSendEverythingRequested,
TSendEverythingFailed,
TSendEverythingSucceeded,
sendEverythingRequested,
sendEverythingFailed,
sendEverythingSucceeded
};

View File

@ -0,0 +1,58 @@
import {
SignTransactionFailedAction,
SignLocalTransactionRequestedAction,
SignWeb3TransactionRequestedAction,
SignLocalTransactionSucceededAction,
SignWeb3TransactionSucceededAction
} from '../actionTypes';
import { TypeKeys } from '../constants';
type TSignTransactionFailed = typeof signTransactionFailed;
const signTransactionFailed = (): SignTransactionFailedAction => ({
type: TypeKeys.SIGN_TRANSACTION_FAILED
});
type TSignLocalTransactionSucceeded = typeof signLocalTransactionSucceeded;
const signLocalTransactionSucceeded = (
payload: SignLocalTransactionSucceededAction['payload']
): SignLocalTransactionSucceededAction => ({
type: TypeKeys.SIGN_LOCAL_TRANSACTION_SUCCEEDED,
payload
});
type TSignLocalTransactionRequested = typeof signLocalTransactionRequested;
const signLocalTransactionRequested = (
payload: SignLocalTransactionRequestedAction['payload']
): SignLocalTransactionRequestedAction => ({
type: TypeKeys.SIGN_LOCAL_TRANSACTION_REQUESTED,
payload
});
type TSignWeb3TransactionSucceeded = typeof signWeb3TransactionSucceeded;
const signWeb3TransactionSucceeded = (
payload: SignWeb3TransactionSucceededAction['payload']
): SignWeb3TransactionSucceededAction => ({
type: TypeKeys.SIGN_WEB3_TRANSACTION_SUCCEEDED,
payload
});
type TSignWeb3TransactionRequested = typeof signWeb3TransactionRequested;
const signWeb3TransactionRequested = (
payload: SignWeb3TransactionRequestedAction['payload']
): SignWeb3TransactionRequestedAction => ({
type: TypeKeys.SIGN_WEB3_TRANSACTION_REQUESTED,
payload
});
export {
signTransactionFailed,
signLocalTransactionSucceeded,
signLocalTransactionRequested,
signWeb3TransactionSucceeded,
signWeb3TransactionRequested,
TSignLocalTransactionSucceeded,
TSignLocalTransactionRequested,
TSignWeb3TransactionSucceeded,
TSignWeb3TransactionRequested,
TSignTransactionFailed
};

View File

@ -0,0 +1,33 @@
import {
SwapEtherToTokenAction,
SwapTokenToEtherAction,
SwapTokenToTokenAction
} from '../actionTypes';
import { TypeKeys } from '../constants';
type TSwapTokenToEther = typeof swapTokenToEther;
const swapTokenToEther = (payload: SwapTokenToEtherAction['payload']): SwapTokenToEtherAction => ({
type: TypeKeys.TOKEN_TO_ETHER_SWAP,
payload
});
type TSwapEtherToToken = typeof swapEtherToToken;
const swapEtherToToken = (payload: SwapEtherToTokenAction['payload']): SwapEtherToTokenAction => ({
payload,
type: TypeKeys.ETHER_TO_TOKEN_SWAP
});
type TSwapTokenToToken = typeof swapTokenToToken;
const swapTokenToToken = (payload: SwapTokenToTokenAction['payload']): SwapTokenToTokenAction => ({
payload,
type: TypeKeys.TOKEN_TO_TOKEN_SWAP
});
export {
swapEtherToToken,
swapTokenToEther,
swapTokenToToken,
TSwapTokenToEther,
TSwapEtherToToken,
TSwapTokenToToken
};

View File

@ -0,0 +1,34 @@
import { TypeKeys } from '../constants';
import { BroadcastAction } from './broadcast';
import { FieldAction, InputFieldAction } from './fields';
import { MetaAction } from './meta';
import { NetworkAction } from './network';
import { SignAction } from './sign';
import { SwapAction } from './swap';
import { CurrentAction } from './current';
import { SendEverythingAction } from './sendEverything';
export * from './broadcast';
export * from './fields';
export * from './meta';
export * from './network';
export * from './sign';
export * from './swap';
export * from './current';
export * from './sendEverything';
export interface ResetAction {
type: TypeKeys.RESET;
}
export type TransactionAction =
| InputFieldAction
| BroadcastAction
| FieldAction
| MetaAction
| NetworkAction
| SignAction
| SwapAction
| ResetAction
| CurrentAction
| SendEverythingAction;

View File

@ -0,0 +1,35 @@
import { TypeKeys } from 'actions/transaction';
/* Broadcasting actions */
interface BroadcastLocalTransactionRequestedAction {
type: TypeKeys.BROADCAST_LOCAL_TRANSACTION_REQUESTED;
}
interface BroadcastWeb3TransactionRequestedAction {
type: TypeKeys.BROADCAST_WEB3_TRANSACTION_REQUESTED;
}
interface BroadcastTransactionSucceededAction {
type: TypeKeys.BROADCAST_TRANSACTION_SUCCEEDED;
payload: { indexingHash: string; broadcastedHash: string };
}
interface BroadcastTransactionQueuedAction {
type: TypeKeys.BROADCAST_TRANSACTION_QUEUED;
payload: { indexingHash: string; serializedTransaction: Buffer };
}
interface BroadcastTransactionFailedAction {
type: TypeKeys.BROADCAST_TRASACTION_FAILED;
payload: { indexingHash: string };
}
type BroadcastAction =
| BroadcastLocalTransactionRequestedAction
| BroadcastTransactionSucceededAction
| BroadcastWeb3TransactionRequestedAction
| BroadcastTransactionQueuedAction
| BroadcastTransactionFailedAction;
export {
BroadcastLocalTransactionRequestedAction,
BroadcastTransactionSucceededAction,
BroadcastWeb3TransactionRequestedAction,
BroadcastTransactionQueuedAction,
BroadcastTransactionFailedAction,
BroadcastAction
};

View File

@ -0,0 +1,17 @@
import { TypeKeys } from '../constants';
/* user input */
interface SetCurrentValueAction {
type: TypeKeys.CURRENT_VALUE_SET;
payload: string;
}
interface SetCurrentToAction {
type: TypeKeys.CURRENT_TO_SET;
payload: string;
}
type CurrentAction = SetCurrentValueAction | SetCurrentToAction;
export { SetCurrentValueAction, SetCurrentToAction, CurrentAction };

View File

@ -0,0 +1,92 @@
import { TypeKeys } from 'actions/transaction/constants';
import { Wei, Data, Address, Nonce } from 'libs/units';
/* User Input */
interface InputGasLimitAction {
type: TypeKeys.GAS_LIMIT_INPUT;
payload: string;
}
interface InputDataAction {
type: TypeKeys.DATA_FIELD_INPUT;
payload: string;
}
interface InputNonceAction {
type: TypeKeys.NONCE_INPUT;
payload: string;
}
/*Field Actions -- Reducer input*/
// We can compute field validity by checking if the value is null
interface SetGasLimitFieldAction {
type: TypeKeys.GAS_LIMIT_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
interface SetGasPriceFieldAction {
type: TypeKeys.GAS_PRICE_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
interface SetDataFieldAction {
type: TypeKeys.DATA_FIELD_SET;
payload: {
raw: string;
value: Data | null;
};
}
interface SetToFieldAction {
type: TypeKeys.TO_FIELD_SET;
payload: {
raw: string;
value: Address | null;
};
}
interface SetNonceFieldAction {
type: TypeKeys.NONCE_FIELD_SET;
payload: {
raw: string;
value: Nonce | null;
};
}
interface SetValueFieldAction {
type: TypeKeys.VALUE_FIELD_SET;
payload: {
raw: string;
value: Wei | null;
};
}
type InputFieldAction = InputNonceAction | InputGasLimitAction | InputDataAction;
type FieldAction =
| SetGasLimitFieldAction
| SetDataFieldAction
| SetToFieldAction
| SetNonceFieldAction
| SetValueFieldAction
| SetGasPriceFieldAction;
export {
InputGasLimitAction,
InputDataAction,
InputNonceAction,
SetGasLimitFieldAction,
SetDataFieldAction,
SetToFieldAction,
SetNonceFieldAction,
SetValueFieldAction,
FieldAction,
InputFieldAction,
SetGasPriceFieldAction
};

View File

@ -0,0 +1 @@
export * from './actionTypes';

View File

@ -0,0 +1,28 @@
import { TypeKeys } from 'actions/transaction/constants';
import { Address, TokenValue } from 'libs/units';
/*Meta Actions*/
interface SetTokenToMetaAction {
type: TypeKeys.TOKEN_TO_META_SET;
payload: {
raw: string;
value: Address | null;
};
}
interface SetUnitMetaAction {
type: TypeKeys.UNIT_META_SET;
payload: string;
}
interface SetTokenValueMetaAction {
type: TypeKeys.TOKEN_VALUE_META_SET;
payload: {
raw: string;
value: TokenValue | null;
};
}
type MetaAction = SetUnitMetaAction | SetTokenValueMetaAction | SetTokenToMetaAction;
export { MetaAction, SetUnitMetaAction, SetTokenToMetaAction, SetTokenValueMetaAction };

View File

@ -0,0 +1,57 @@
import { TypeKeys } from 'actions/transaction/constants';
import { IHexStrTransaction } from 'libs/transaction';
/* Network actions */
interface EstimateGasRequestedAction {
type: TypeKeys.ESTIMATE_GAS_REQUESTED;
payload: Partial<IHexStrTransaction>;
}
interface EstimateGasSucceededAction {
type: TypeKeys.ESTIMATE_GAS_SUCCEEDED;
}
interface EstimateGasFailedAction {
type: TypeKeys.ESTIMATE_GAS_FAILED;
}
interface GetFromRequestedAction {
type: TypeKeys.GET_FROM_REQUESTED;
}
interface GetFromSucceededAction {
type: TypeKeys.GET_FROM_SUCCEEDED;
payload: string;
}
interface GetFromFailedAction {
type: TypeKeys.GET_FROM_FAILED;
}
interface GetNonceRequestedAction {
type: TypeKeys.GET_NONCE_REQUESTED;
}
interface GetNonceSucceededAction {
type: TypeKeys.GET_NONCE_SUCCEEDED;
payload: string;
}
interface GetNonceFailedAction {
type: TypeKeys.GET_NONCE_FAILED;
}
type NetworkAction =
| EstimateGasFailedAction
| EstimateGasRequestedAction
| EstimateGasSucceededAction
| GetFromRequestedAction
| GetFromSucceededAction
| GetFromFailedAction
| GetNonceRequestedAction
| GetNonceSucceededAction
| GetNonceFailedAction;
export {
EstimateGasRequestedAction,
EstimateGasSucceededAction,
EstimateGasFailedAction,
GetFromRequestedAction,
GetFromSucceededAction,
GetFromFailedAction,
GetNonceRequestedAction,
GetNonceSucceededAction,
GetNonceFailedAction,
NetworkAction
};

View File

@ -0,0 +1,23 @@
import { TypeKeys } from 'actions/transaction';
interface SendEverythingRequestedAction {
type: TypeKeys.SEND_EVERYTHING_REQUESTED;
}
interface SendEverythingSucceededAction {
type: TypeKeys.SEND_EVERYTHING_SUCCEEDED;
}
interface SendEverythingFailedAction {
type: TypeKeys.SEND_EVERYTHING_FAILED;
}
type SendEverythingAction =
| SendEverythingRequestedAction
| SendEverythingSucceededAction
| SendEverythingFailedAction;
export {
SendEverythingAction,
SendEverythingSucceededAction,
SendEverythingFailedAction,
SendEverythingRequestedAction
};

View File

@ -0,0 +1,46 @@
import EthTx from 'ethereumjs-tx';
import { TypeKeys } from 'actions/transaction/constants';
/*
* Difference between the web3/local is that a local sign will actually sign the tx
* While a web3 sign just gathers the rest of the nessesary parameters of the ethereum tx
* to do the sign + broadcast in 1 step later on
*/
/* Signing / Async actions */
interface SignLocalTransactionRequestedAction {
type: TypeKeys.SIGN_LOCAL_TRANSACTION_REQUESTED;
payload: EthTx;
}
interface SignLocalTransactionSucceededAction {
type: TypeKeys.SIGN_LOCAL_TRANSACTION_SUCCEEDED;
payload: { signedTransaction: Buffer; indexingHash: string; noVerify?: boolean }; // dont verify against fields, for pushTx
}
interface SignWeb3TransactionRequestedAction {
type: TypeKeys.SIGN_WEB3_TRANSACTION_REQUESTED;
payload: EthTx;
}
interface SignWeb3TransactionSucceededAction {
type: TypeKeys.SIGN_WEB3_TRANSACTION_SUCCEEDED;
payload: { transaction: Buffer; indexingHash: string; noVerify?: boolean };
}
interface SignTransactionFailedAction {
type: TypeKeys.SIGN_TRANSACTION_FAILED;
}
type SignAction =
| SignLocalTransactionRequestedAction
| SignLocalTransactionSucceededAction
| SignWeb3TransactionRequestedAction
| SignWeb3TransactionSucceededAction
| SignTransactionFailedAction;
export {
SignLocalTransactionRequestedAction,
SignLocalTransactionSucceededAction,
SignWeb3TransactionRequestedAction,
SignWeb3TransactionSucceededAction,
SignTransactionFailedAction,
SignAction
};

View File

@ -0,0 +1,40 @@
import { TypeKeys } from 'actions/transaction/constants';
import {
SetToFieldAction,
SetValueFieldAction,
SetTokenToMetaAction,
SetTokenValueMetaAction,
SetDataFieldAction
} from 'actions/transaction';
/* Swapping actions */
interface SwapTokenToEtherAction {
type: TypeKeys.TOKEN_TO_ETHER_SWAP;
payload: {
to: SetToFieldAction['payload'];
value: SetValueFieldAction['payload'];
decimal: number;
};
}
interface SwapEtherToTokenAction {
type: TypeKeys.ETHER_TO_TOKEN_SWAP;
payload: {
to: SetToFieldAction['payload'];
data: SetDataFieldAction['payload'];
tokenTo: SetTokenToMetaAction['payload'];
tokenValue: SetTokenValueMetaAction['payload'];
decimal: number;
};
}
interface SwapTokenToTokenAction {
type: TypeKeys.TOKEN_TO_TOKEN_SWAP;
payload: {
to: SetToFieldAction['payload'];
data: SetDataFieldAction['payload'];
tokenValue: SetTokenValueMetaAction['payload'];
decimal: number;
};
}
type SwapAction = SwapEtherToTokenAction | SwapTokenToEtherAction | SwapTokenToTokenAction;
export { SwapTokenToEtherAction, SwapEtherToTokenAction, SwapAction, SwapTokenToTokenAction };

View File

@ -0,0 +1,53 @@
export enum TypeKeys {
ESTIMATE_GAS_REQUESTED = 'ESTIMATE_GAS_REQUESTED',
ESTIMATE_GAS_SUCCEEDED = 'ESTIMATE_GAS_SUCCEEDED',
ESTIMATE_GAS_FAILED = 'ESTIMATE_GAS_FAILED',
GET_FROM_REQUESTED = 'GET_FROM_REQUESTED',
GET_FROM_SUCCEEDED = 'GET_FROM_SUCCEEDED',
GET_FROM_FAILED = 'GET_FROM_FAILED',
GET_NONCE_REQUESTED = 'GET_NONCE_REQUESTED',
GET_NONCE_SUCCEEDED = 'GET_NONCE_SUCCEEDED',
GET_NONCE_FAILED = 'GET_NONCE_FAILED',
SIGN_WEB3_TRANSACTION_REQUESTED = 'SIGN_WEB3_TRANSACTION_REQUESTED',
SIGN_WEB3_TRANSACTION_SUCCEEDED = 'SIGN_WEB3_TRANSACTION_SUCCEEDED',
SIGN_LOCAL_TRANSACTION_REQUESTED = 'SIGN_LOCAL_TRANSACTION_REQUESTED',
SIGN_LOCAL_TRANSACTION_SUCCEEDED = 'SIGN_LOCAL_TRANSACTION_SUCCEEDED',
SIGN_TRANSACTION_FAILED = 'SIGN_TRANSACTION_FAILED',
BROADCAST_WEB3_TRANSACTION_REQUESTED = 'BROADCAST_WEB3_TRANSACTION_REQUESTED',
BROADCAST_TRANSACTION_SUCCEEDED = 'BROADCAST_TRANSACTION_SUCCEEDED',
BROADCAST_LOCAL_TRANSACTION_REQUESTED = 'BROADCAST_LOCAL_TRANSACTION_REQUESTED',
BROADCAST_TRANSACTION_QUEUED = 'BROADCAST_TRANSACTION_QUEUED',
BROADCAST_TRASACTION_FAILED = 'BROADCAST_TRASACTION_FAILED',
CURRENT_VALUE_SET = 'CURRENT_VALUE_SET',
CURRENT_TO_SET = 'CURRENT_TO_SET',
DATA_FIELD_INPUT = 'DATA_FIELD_INPUT',
GAS_LIMIT_INPUT = 'GAS_LIMIT_INPUT',
NONCE_INPUT = 'NONCE_INPUT',
DATA_FIELD_SET = 'DATA_FIELD_SET',
GAS_LIMIT_FIELD_SET = 'GAS_LIMIT_FIELD_SET',
TO_FIELD_SET = 'TO_FIELD_SET',
VALUE_FIELD_SET = 'VALUE_FIELD_SET',
NONCE_FIELD_SET = 'NONCE_FIELD_SET',
GAS_PRICE_FIELD_SET = 'GAS_PRICE_FIELD_SET',
TOKEN_TO_META_SET = 'TOKEN_TO_META_SET',
UNIT_META_SET = 'UNIT_META_SET',
TOKEN_VALUE_META_SET = 'TOKEN_VALUE_META_SET',
TOKEN_TO_ETHER_SWAP = 'TOKEN_TO_ETHER_SWAP',
ETHER_TO_TOKEN_SWAP = 'ETHER_TO_TOKEN_SWAP',
TOKEN_TO_TOKEN_SWAP = 'TOKEN_TO_TOKEN_SWAP',
SEND_EVERYTHING_REQUESTED = 'SEND_EVERYTHING_REQUESTED',
SEND_EVERYTHING_SUCCEEDED = 'SEND_EVERYTHING_SUCCEEDED',
SEND_EVERYTHING_FAILED = 'SEND_EVERYTHING_FAILED',
RESET = 'RESET'
}

View File

@ -0,0 +1,3 @@
export * from './actionCreators';
export * from './constants';
export * from './actionTypes';

View File

@ -1,5 +1,5 @@
import { Wei, TokenValue } from 'libs/units';
import { IWallet } from 'libs/wallet/IWallet';
import { IWallet, WalletConfig } from 'libs/wallet';
import * as types from './actionTypes';
import { TypeKeys } from './constants';
export type TUnlockPrivateKey = typeof unlockPrivateKey;
@ -13,9 +13,7 @@ export function unlockPrivateKey(
}
export type TUnlockKeystore = typeof unlockKeystore;
export function unlockKeystore(
value: types.KeystoreUnlockParams
): types.UnlockKeystoreAction {
export function unlockKeystore(value: types.KeystoreUnlockParams): types.UnlockKeystoreAction {
return {
type: TypeKeys.WALLET_UNLOCK_KEYSTORE,
payload: value
@ -23,9 +21,7 @@ export function unlockKeystore(
}
export type TUnlockMnemonic = typeof unlockMnemonic;
export function unlockMnemonic(
value: types.MnemonicUnlockParams
): types.UnlockMnemonicAction {
export function unlockMnemonic(value: types.MnemonicUnlockParams): types.UnlockMnemonicAction {
return {
type: TypeKeys.WALLET_UNLOCK_MNEMONIC,
payload: value
@ -54,9 +50,7 @@ export function setBalancePending(): types.SetBalancePendingAction {
}
export type TSetBalance = typeof setBalanceFullfilled;
export function setBalanceFullfilled(
value: Wei
): types.SetBalanceFullfilledAction {
export function setBalanceFullfilled(value: Wei): types.SetBalanceFullfilledAction {
return {
type: TypeKeys.WALLET_SET_BALANCE_FULFILLED,
payload: value
@ -69,56 +63,44 @@ export function setBalanceRejected(): types.SetBalanceRejectedAction {
};
}
export type TSetTokenBalances = typeof setTokenBalances;
export function setTokenBalances(payload: {
export function setTokenBalancesPending(): types.SetTokenBalancesPendingAction {
return {
type: TypeKeys.WALLET_SET_TOKEN_BALANCES_PENDING
};
}
export type TSetTokenBalancesFulfilled = typeof setTokenBalancesFulfilled;
export function setTokenBalancesFulfilled(payload: {
[key: string]: {
balance: TokenValue;
error: string | null;
};
}): types.SetTokenBalancesAction {
}): types.SetTokenBalancesFulfilledAction {
return {
type: TypeKeys.WALLET_SET_TOKEN_BALANCES,
type: TypeKeys.WALLET_SET_TOKEN_BALANCES_FULFILLED,
payload
};
}
export type TBroadcastTx = typeof broadcastTx;
export function broadcastTx(
signedTx: string
): types.BroadcastTxRequestedAction {
export function setTokenBalancesRejected(): types.SetTokenBalancesRejectedAction {
return {
type: TypeKeys.WALLET_BROADCAST_TX_REQUESTED,
payload: {
signedTx
}
type: TypeKeys.WALLET_SET_TOKEN_BALANCES_REJECTED
};
}
export type TBroadcastTxSucceded = typeof broadcastTxSucceded;
export function broadcastTxSucceded(
txHash: string,
signedTx: string
): types.BroadcastTxSuccededAction {
export type TScanWalletForTokens = typeof scanWalletForTokens;
export function scanWalletForTokens(wallet: IWallet): types.ScanWalletForTokensAction {
return {
type: TypeKeys.WALLET_BROADCAST_TX_SUCCEEDED,
payload: {
txHash,
signedTx
}
type: TypeKeys.WALLET_SCAN_WALLET_FOR_TOKENS,
payload: wallet
};
}
export type TBroadCastTxFailed = typeof broadCastTxFailed;
export function broadCastTxFailed(
signedTx: string,
errorMsg: string
): types.BroadcastTxFailedAction {
export type TSetWalletTokens = typeof setWalletTokens;
export function setWalletTokens(tokens: string[]): types.SetWalletTokensAction {
return {
type: TypeKeys.WALLET_BROADCAST_TX_FAILED,
payload: {
signedTx,
error: errorMsg
}
type: TypeKeys.WALLET_SET_WALLET_TOKENS,
payload: tokens
};
}
@ -128,3 +110,11 @@ export function resetWallet(): types.ResetWalletAction {
type: TypeKeys.WALLET_RESET
};
}
export type TSetWalletConfig = typeof setWalletConfig;
export function setWalletConfig(config: WalletConfig): types.SetWalletConfigAction {
return {
type: TypeKeys.WALLET_SET_CONFIG,
payload: config
};
}

View File

@ -1,5 +1,5 @@
import { Wei, TokenValue } from 'libs/units';
import { IWallet } from 'libs/wallet/IWallet';
import { IWallet, WalletConfig } from 'libs/wallet';
import { TypeKeys } from './constants';
/*** Unlock Private Key ***/
@ -45,8 +45,12 @@ export interface SetBalanceRejectedAction {
}
/*** Set Token Balance ***/
export interface SetTokenBalancesAction {
type: TypeKeys.WALLET_SET_TOKEN_BALANCES;
export interface SetTokenBalancesPendingAction {
type: TypeKeys.WALLET_SET_TOKEN_BALANCES_PENDING;
}
export interface SetTokenBalancesFulfilledAction {
type: TypeKeys.WALLET_SET_TOKEN_BALANCES_FULFILLED;
payload: {
[key: string]: {
balance: TokenValue;
@ -55,12 +59,18 @@ export interface SetTokenBalancesAction {
};
}
/*** Broadcast Tx ***/
export interface BroadcastTxRequestedAction {
type: TypeKeys.WALLET_BROADCAST_TX_REQUESTED;
payload: {
signedTx: string;
};
export interface SetTokenBalancesRejectedAction {
type: TypeKeys.WALLET_SET_TOKEN_BALANCES_REJECTED;
}
export interface ScanWalletForTokensAction {
type: TypeKeys.WALLET_SCAN_WALLET_FOR_TOKENS;
payload: IWallet;
}
export interface SetWalletTokensAction {
type: TypeKeys.WALLET_SET_WALLET_TOKENS;
payload: string[];
}
/*** Unlock Mnemonic ***/
@ -82,20 +92,9 @@ export interface UnlockKeystoreAction {
payload: KeystoreUnlockParams;
}
export interface BroadcastTxSuccededAction {
type: TypeKeys.WALLET_BROADCAST_TX_SUCCEEDED;
payload: {
txHash: string;
signedTx: string;
};
}
export interface BroadcastTxFailedAction {
type: TypeKeys.WALLET_BROADCAST_TX_FAILED;
payload: {
signedTx: string;
error: string;
};
export interface SetWalletConfigAction {
type: TypeKeys.WALLET_SET_CONFIG;
payload: WalletConfig;
}
/*** Union Type ***/
@ -106,7 +105,9 @@ export type WalletAction =
| SetBalancePendingAction
| SetBalanceFullfilledAction
| SetBalanceRejectedAction
| SetTokenBalancesAction
| BroadcastTxRequestedAction
| BroadcastTxFailedAction
| BroadcastTxSuccededAction;
| SetTokenBalancesPendingAction
| SetTokenBalancesFulfilledAction
| SetTokenBalancesRejectedAction
| ScanWalletForTokensAction
| SetWalletTokensAction
| SetWalletConfigAction;

View File

@ -7,9 +7,11 @@ export enum TypeKeys {
WALLET_SET_BALANCE_PENDING = 'WALLET_SET_BALANCE_PENDING',
WALLET_SET_BALANCE_FULFILLED = 'WALLET_SET_BALANCE_FULFILLED',
WALLET_SET_BALANCE_REJECTED = 'WALLET_SET_BALANCE_REJECTED',
WALLET_SET_TOKEN_BALANCES = 'WALLET_SET_TOKEN_BALANCES',
WALLET_BROADCAST_TX_REQUESTED = 'WALLET_BROADCAST_TX_REQUESTED',
WALLET_BROADCAST_TX_FAILED = 'WALLET_BROADCAST_TX_FAILED',
WALLET_BROADCAST_TX_SUCCEEDED = 'WALLET_BROADCAST_TX_SUCCEEDED',
WALLET_SET_TOKEN_BALANCES_PENDING = 'WALLET_SET_TOKEN_BALANCES_PENDING',
WALLET_SET_TOKEN_BALANCES_FULFILLED = 'WALLET_SET_TOKEN_BALANCES_FULFILLED',
WALLET_SET_TOKEN_BALANCES_REJECTED = 'WALLET_SET_TOKEN_BALANCES_REJECTED',
WALLET_SCAN_WALLET_FOR_TOKENS = 'WALLET_SCAN_WALLET_FOR_TOKENS',
WALLET_SET_WALLET_TOKENS = 'WALLET_SET_WALLET_TOKENS',
WALLET_SET_CONFIG = 'WALLET_SET_CONFIG',
WALLET_RESET = 'WALLET_RESET'
}

View File

@ -1,23 +1,31 @@
import bityConfig from 'config/bity';
import { checkHttpStatus, parseJSON } from './utils';
import bityConfig, { WhitelistedCoins } from 'config/bity';
import { checkHttpStatus, parseJSON, filter } from './utils';
const isCryptoPair = (from: string, to: string, arr: WhitelistedCoins[]) => {
return filter(from, arr) && filter(to, arr);
};
export function getAllRates() {
const mappedRates = {};
return _getAllRates().then(bityRates => {
bityRates.objects.forEach(each => {
const pairName = each.pair;
mappedRates[pairName] = parseFloat(each.rate_we_sell);
const from = { id: pairName.substring(0, 3) };
const to = { id: pairName.substring(3, 6) };
// Check if rate exists= && check if the pair only crypto to crypto, not crypto to fiat, or any other combination
if (parseFloat(each.rate_we_sell) && isCryptoPair(from.id, to.id, ['BTC', 'ETH', 'REP'])) {
mappedRates[pairName] = {
id: pairName,
options: [from, to],
rate: parseFloat(each.rate_we_sell)
};
}
});
return mappedRates;
});
}
export function postOrder(
amount: number,
destAddress: string,
mode: number,
pair: string
) {
export function postOrder(amount: number, destAddress: string, mode: number, pair: string) {
return fetch(`${bityConfig.serverURL}/order`, {
method: 'post',
body: JSON.stringify({

View File

@ -1,3 +1,9 @@
import { indexOf } from 'lodash';
export const filter = (i: any, arr: any[]) => {
return -1 !== indexOf(arr, i) ? true : false;
};
export function checkHttpStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;

View File

@ -0,0 +1,43 @@
import { Query } from 'components/renderCbs';
import { setCurrentTo, TSetCurrentTo } from 'actions/transaction';
import { AddressInput } from './AddressInput';
import React from 'react';
import { connect } from 'react-redux';
interface DispatchProps {
setCurrentTo: TSetCurrentTo;
}
interface OwnProps {
to: string | null;
}
type Props = DispatchProps & DispatchProps & OwnProps;
//TODO: add ens resolving
class AddressFieldClass extends React.Component<Props, {}> {
public componentDidMount() {
// this 'to' parameter can be either token or actual field related
const { to } = this.props;
if (to) {
this.props.setCurrentTo(to);
}
}
public render() {
return <AddressInput onChange={this.setAddress} />;
}
private setAddress = (ev: React.FormEvent<HTMLInputElement>) => {
const { value } = ev.currentTarget;
this.props.setCurrentTo(value);
};
}
const AddressField = connect(null, { setCurrentTo })(AddressFieldClass);
const DefaultAddressField: React.SFC<{}> = () => (
<Query params={['to']} withQuery={({ to }) => <AddressField to={to} />} />
);
export { DefaultAddressField as AddressField };

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react';
import { Identicon } from 'components/ui';
import translate from 'translations';
//import { EnsAddress } from './components';
import { Query } from 'components/renderCbs';
import { donationAddressMap } from 'config/data';
import { ICurrentTo, getCurrentTo, isValidCurrentTo } from 'selectors/transaction';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
interface StateProps {
currentTo: ICurrentTo;
isValid: boolean;
}
interface OwnProps {
onChange(ev: React.FormEvent<HTMLInputElement>): void;
}
type Props = OwnProps & StateProps;
//TODO: ENS handling
class AddressInputClass extends Component<Props> {
public render() {
const { currentTo, onChange, isValid } = this.props;
const { raw } = currentTo;
return (
<div className="row form-group">
<div className="col-xs-11">
<label>{translate('SEND_addr')}:</label>
<Query
params={['readOnly']}
withQuery={({ readOnly }) => (
<input
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
type="text"
value={raw}
placeholder={donationAddressMap.ETH}
readOnly={!!readOnly}
onChange={onChange}
/>
)}
/>
{/*<EnsAddress ensAddress={ensAddress} />*/}
</div>
<div className="col-xs-1" style={{ padding: 0 }}>
<Identicon address={/*ensAddress ||*/ raw} />
</div>
</div>
);
}
}
export const AddressInput = connect((state: AppState) => ({
currentTo: getCurrentTo(state),
isValid: isValidCurrentTo(state)
}))(AddressInputClass);

View File

@ -0,0 +1,36 @@
import React from 'react';
/*
public onChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
const newValue = (e.target as HTMLInputElement).value;
const { onChange } = this.props;
if (!onChange) {
return;
}
// FIXME debounce?
if (isValidENSAddress(newValue)) {
this.props.resolveEnsName(newValue);
}
onChange(newValue);
};
}
function mapStateToProps(state: AppState, props: PublicProps) {
return {
ensAddress: getEnsAddress(state, props.value)
};
}
export default connect(mapStateToProps, { resolveEnsName })(AddressField);
*/
interface EnsAddressProps {
ensAddress: string | null;
}
export const EnsAddress: React.SFC<EnsAddressProps> = ({ ensAddress }) =>
(!!ensAddress && (
<p className="ens-response">
<span className="mono">{ensAddress}</span>
</p>
)) ||
null;

View File

@ -0,0 +1 @@
export * from './EnsAddress';

View File

@ -0,0 +1 @@
export * from './AddressField';

View File

@ -25,23 +25,19 @@ export default class AlphaAgreement extends React.Component<{}, State> {
<div className="AlphaAgreement-content">
<h2>This is an Unstable Version of MEW</h2>
<p>
You are about to access an alpha version of MyEtherWallet that is
currently in development. In its current state, it should only be
used for testing, not for important transactions.
You are about to access an alpha version of MyEtherWallet that is currently in
development. In its current state, it should only be used for testing, not for important
transactions.
</p>
<p>
Any wallets you generate should not hold a significant value, and
any transactions you make should be for small amounts. MEW does not
claim responsibility for any issues that happen while using the
alpha version.
Any wallets you generate should not hold a significant value, and any transactions you
make should be for small amounts. MEW does not claim responsibility for any issues that
happen while using the alpha version.
</p>
<p>Are you sure you would like to continue?</p>
<div className="AlphaAgreement-content-buttons">
<button
className="AlphaAgreement-content-buttons-btn is-reject"
onClick={this.reject}
>
<button className="AlphaAgreement-content-buttons-btn is-reject" onClick={this.reject}>
No, Take Me to v3
</button>
<button

View File

@ -0,0 +1,31 @@
import React from 'react';
import { AmountFieldFactory } from './AmountFieldFactory';
import { Aux } from 'components/ui';
import { UnitDropDown } from 'components';
import translate, { translateRaw } from 'translations';
interface Props {
hasUnitDropdown?: boolean;
}
export const AmountField: React.SFC<Props> = ({ hasUnitDropdown }) => (
<AmountFieldFactory
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
<Aux>
<label>{translate('SEND_amount')}</label>
<div className="input-group">
<input
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
type="number"
placeholder={translateRaw('SEND_amount_short')}
value={raw}
readOnly={!!readOnly}
onChange={onChange}
/>
{hasUnitDropdown && <UnitDropDown />}
</div>
</Aux>
)}
/>
);

View File

@ -0,0 +1,59 @@
import React, { Component } from 'react';
import { Query } from 'components/renderCbs';
import { setCurrentValue, TSetCurrentValue } from 'actions/transaction';
import { connect } from 'react-redux';
import { AmountInput } from './AmountInputFactory';
import { AppState } from 'reducers';
export interface CallbackProps {
isValid: boolean;
readOnly: boolean;
currentValue:
| AppState['transaction']['fields']['value']
| AppState['transaction']['meta']['tokenValue'];
onChange(ev: React.FormEvent<HTMLInputElement>);
}
interface DispatchProps {
setCurrentValue: TSetCurrentValue;
}
interface OwnProps {
value: string | null;
withProps(props: CallbackProps): React.ReactElement<any> | null;
}
type Props = DispatchProps & OwnProps;
class AmountFieldClass extends Component<Props, {}> {
public componentDidMount() {
const { value } = this.props;
if (value) {
this.props.setCurrentValue(value);
}
}
public render() {
return <AmountInput onChange={this.setValue} withProps={this.props.withProps} />;
}
private setValue = (ev: React.FormEvent<HTMLInputElement>) => {
const { value } = ev.currentTarget;
this.props.setCurrentValue(value);
};
}
const AmountField = connect(null, { setCurrentValue })(AmountFieldClass);
interface DefaultAmountFieldProps {
withProps(props: CallbackProps): React.ReactElement<any> | null;
}
const DefaultAmountField: React.SFC<DefaultAmountFieldProps> = ({ withProps }) => (
<Query
params={['value']}
withQuery={({ value }) => <AmountField value={value} withProps={withProps} />}
/>
);
export { DefaultAmountField as AmountFieldFactory };

View File

@ -0,0 +1,43 @@
import React, { Component } from 'react';
import { Query } from 'components/renderCbs';
import { ICurrentValue, getCurrentValue, isValidAmount } from 'selectors/transaction';
import { AppState } from 'reducers';
import { connect } from 'react-redux';
import { CallbackProps } from 'components/AmountFieldFactory';
interface OwnProps {
onChange(ev: React.FormEvent<HTMLInputElement>);
withProps(props: CallbackProps): React.ReactElement<any> | null;
}
interface StateProps {
currentValue: ICurrentValue;
validAmount: boolean;
}
type Props = OwnProps & StateProps;
class AmountInputClass extends Component<Props> {
public render() {
const { currentValue, onChange, withProps, validAmount } = this.props;
return (
<Query
params={['readOnly']}
withQuery={({ readOnly }) =>
withProps({
currentValue,
isValid: validAmount,
readOnly: !!readOnly,
onChange
})
}
/>
);
}
}
export const AmountInput = connect((state: AppState) => ({
currentValue: getCurrentValue(state),
validAmount: isValidAmount(state)
}))(AmountInputClass);

View File

@ -0,0 +1 @@
export * from './AmountFieldFactory';

View File

@ -37,8 +37,7 @@ export default class AccountInfo extends React.Component<Props, State> {
this.setAddressFromWallet();
}
// TODO: don't use any;
public toggleShowLongBalance = (e: any) => {
public toggleShowLongBalance = (e: React.FormEvent<HTMLSpanElement>) => {
e.preventDefault();
this.setState(state => {
return {
@ -55,9 +54,7 @@ export default class AccountInfo extends React.Component<Props, State> {
return (
<div className="AccountInfo">
<div className="AccountInfo-section">
<h5 className="AccountInfo-section-header">
{translate('sidebar_AccountAddr')}
</h5>
<h5 className="AccountInfo-section-header">{translate('sidebar_AccountAddr')}</h5>
<div className="AccountInfo-address">
<div className="AccountInfo-address-icon">
<Identicon address={address} size="100%" />
@ -67,9 +64,7 @@ export default class AccountInfo extends React.Component<Props, State> {
</div>
<div className="AccountInfo-section">
<h5 className="AccountInfo-section-header">
{translate('sidebar_AccountBal')}
</h5>
<h5 className="AccountInfo-section-header">{translate('sidebar_AccountBal')}</h5>
<ul className="AccountInfo-list">
<li className="AccountInfo-list-item">
<span
@ -83,23 +78,18 @@ export default class AccountInfo extends React.Component<Props, State> {
value={balance.wei}
unit={'ether'}
displayShortBalance={!showLongBalance}
checkOffline={true}
/>
)}
</span>
{!balance.isPending ? (
balance.wei ? (
<span> {network.name}</span>
) : null
) : null}
{!balance.isPending ? balance.wei ? <span> {network.name}</span> : null : null}
</li>
</ul>
</div>
{(!!blockExplorer || !!tokenExplorer) && (
<div className="AccountInfo-section">
<h5 className="AccountInfo-section-header">
{translate('sidebar_TransHistory')}
</h5>
<h5 className="AccountInfo-section-header">{translate('sidebar_TransHistory')}</h5>
<ul className="AccountInfo-list">
{!!blockExplorer && (
<li className="AccountInfo-list-item">

View File

@ -5,6 +5,7 @@ import { State } from 'reducers/rates';
import { rateSymbols, TFetchCCRates } from 'actions/rates';
import { TokenBalance } from 'selectors/wallet';
import { Balance } from 'libs/wallet';
import { ETH_DECIMAL, convertTokenBase } from 'libs/units';
import Spinner from 'components/ui/Spinner';
import UnitDisplay from 'components/ui/UnitDisplay';
import './EquivalentValues.scss';
@ -28,7 +29,8 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
currency: ALL_OPTION
};
private balanceLookup: { [key: string]: Balance['wei'] | undefined } = {};
private requestedCurrencies: string[] = [];
private decimalLookup: { [key: string]: number } = {};
private requestedCurrencies: string[] | null = null;
public constructor(props) {
super(props);
@ -41,10 +43,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
public componentWillReceiveProps(nextProps) {
const { balance, tokenBalances } = this.props;
if (
nextProps.balance !== balance ||
nextProps.tokenBalances !== tokenBalances
) {
if (nextProps.balance !== balance || nextProps.tokenBalances !== tokenBalances) {
this.makeBalanceLookup(nextProps);
this.fetchRates(nextProps);
}
@ -57,10 +56,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
// There are a bunch of reasons why the incorrect balances might be rendered
// while we have incomplete data that's being fetched.
const isFetching =
!balance ||
balance.isPending ||
!tokenBalances ||
Object.keys(rates).length === 0;
!balance || balance.isPending || !tokenBalances || Object.keys(rates).length === 0;
let valuesEl;
if (!isFetching && (rates[currency] || currency === ALL_OPTION)) {
@ -72,14 +68,13 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
return (
<li className="EquivalentValues-values-currency" key={key}>
<span className="EquivalentValues-values-currency-label">
{key}:
</span>{' '}
<span className="EquivalentValues-values-currency-label">{key}:</span>{' '}
<span className="EquivalentValues-values-currency-value">
<UnitDisplay
unit={'ether'}
value={values[key]}
displayShortBalance={3}
checkOffline={true}
/>
</span>
</li>
@ -110,7 +105,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
<option value="ETH">ETH</option>
{tokenBalances &&
tokenBalances.map(tk => {
if (!tk.balance || tk.balance.isZero()) {
if (!tk.balance || tk.balance.isZero() || !rates[tk.symbol]) {
return;
}
const sym = tk.symbol;
@ -137,10 +132,10 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
const tokenBalances = props.tokenBalances || [];
this.balanceLookup = tokenBalances.reduce(
(prev, tk) => {
return {
...prev,
[tk.symbol]: tk.balance
};
// Piggy-back off of this reduce to add to decimal lookup
this.decimalLookup[tk.symbol] = tk.decimal;
prev[tk.symbol] = tk.balance;
return prev;
},
{ ETH: props.balance && props.balance.wei }
);
@ -159,7 +154,7 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
.sort();
// If it's the same currencies as we have, skip it
if (currencies.join() === this.requestedCurrencies.join()) {
if (this.requestedCurrencies && currencies.join() === this.requestedCurrencies.join()) {
return;
}
@ -175,12 +170,10 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
} {
// Recursively call on all currencies
if (currency === ALL_OPTION) {
return ['ETH'].concat(this.requestedCurrencies).reduce(
return ['ETH'].concat(this.requestedCurrencies || []).reduce(
(prev, curr) => {
const currValues = this.getEquivalentValues(curr);
rateSymbols.forEach(
sym => (prev[sym] = prev[sym].add(currValues[sym] || new BN(0)))
);
rateSymbols.forEach(sym => (prev[sym] = prev[sym].add(currValues[sym] || new BN(0))));
return prev;
},
rateSymbols.reduce((prev, sym) => {
@ -197,8 +190,13 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
return {};
}
// Tokens with non-ether like decimals need to be adjusted to match
const decimal =
this.decimalLookup[currency] === undefined ? ETH_DECIMAL : this.decimalLookup[currency];
const adjustedBalance = convertTokenBase(balance, decimal, ETH_DECIMAL);
return rateSymbols.reduce((prev, sym) => {
prev[sym] = balance ? balance.muln(rates[currency][sym]) : null;
prev[sym] = adjustedBalance.muln(rates[currency][sym]);
return prev;
}, {});
}

View File

@ -1,8 +1,5 @@
import React from 'react';
import {
forceOfflineConfig as dForceOfflineConfig,
TForceOfflineConfig
} from 'actions/config';
import { forceOfflineConfig as dForceOfflineConfig, TForceOfflineConfig } from 'actions/config';
import OfflineSymbol from 'components/ui/OfflineSymbol';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
@ -24,10 +21,10 @@ class OfflineToggle extends React.Component<OfflineToggleProps, {}> {
<div>
{!offline ? (
<div className="row text-center">
<div className="col-md-3">
<div className="col-xs-3">
<OfflineSymbol offline={offline || forceOffline} size={size} />
</div>
<div className="col-md-6">
<div className="col-xs-6">
<button className="btn-xs btn-info" onClick={forceOfflineConfig}>
{forceOffline ? 'Go Online' : 'Go Offline'}
</button>

View File

@ -1,29 +1,23 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { knowledgeBaseURL } from 'config/data';
import './Promos.scss';
const promos = [
{
isExternal: true,
color: '#6e9a3e',
href:
'https://myetherwallet.groovehq.com/knowledge_base/topics/protecting-yourself-and-your-funds',
href: `${knowledgeBaseURL}/security/securing-your-ethereum`,
texts: [<h6 key="1">Learn more about protecting your funds.</h6>],
images: [
require('assets/images/logo-ledger.svg'),
require('assets/images/logo-trezor.svg')
]
images: [require('assets/images/logo-ledger.svg'), require('assets/images/logo-trezor.svg')]
},
{
isExternal: true,
color: '#2b71b1',
href:
'https://buy.coinbase.com?code=a6e1bd98-6464-5552-84dd-b27f0388ac7d&address=0xA7DeFf12461661212734dB35AdE9aE7d987D648c&crypto_currency=ETH&currency=USD',
texts: [
<p key="1">Its now easier to get more ETH</p>,
<h5 key="2">Buy ETH with USD</h5>
],
texts: [<p key="1">Its now easier to get more ETH</p>, <h5 key="2">Buy ETH with USD</h5>],
images: [require('assets/images/logo-coinbase.svg')]
},
{
@ -87,9 +81,7 @@ export default class Promos extends React.Component<{}, State> {
{promos.map((_, index) => {
return (
<button
className={`Promos-nav-btn ${index === activePromo
? 'is-active'
: ''}`}
className={`Promos-nav-btn ${index === activePromo ? 'is-active' : ''}`}
key={index}
onClick={this.navigateToPromo(index)}
/>

View File

@ -0,0 +1,33 @@
@import "common/sass/variables";
.AddCustom {
&-field {
&-error {
font-size: 13px;
font-weight: normal;
color: $brand-danger;
}
}
&-buttons {
padding-top: 10px;
&-help {
display: block;
font-size: 13px;
margin-bottom: 10px;
}
&-btn {
margin-right: 10px;
&.btn-primary {
width: 120px;
}
&.btn-default {
width: 110px;
}
}
}
}

View File

@ -1,14 +1,19 @@
import React from 'react';
import classnames from 'classnames';
import { Token } from 'config/data';
import { isPositiveIntegerOrZero, isValidETHAddress } from 'libs/validators';
import React from 'react';
import translate from 'translations';
import NewTabLink from 'components/ui/NewTabLink';
import './AddCustomTokenForm.scss';
interface Props {
allTokens: Token[];
onSave(params: Token): void;
toggleForm(): void;
}
interface State {
tokenSymbolLookup: { [symbol: string]: boolean };
address: string;
symbol: string;
decimal: string;
@ -16,27 +21,36 @@ interface State {
export default class AddCustomTokenForm extends React.Component<Props, State> {
public state = {
tokenSymbolLookup: {},
address: '',
symbol: '',
decimal: ''
};
constructor(props: Props) {
super(props);
this.state = {
...this.state,
tokenSymbolLookup: this.generateSymbolLookup(props.allTokens)
};
}
public render() {
const { address, symbol, decimal } = this.state;
const inputClasses = 'AddCustom-field-input form-control input-sm';
const errors = this.getErrors();
const fields = [
{
name: 'address',
value: address,
label: translate('TOKEN_Addr')
},
{
name: 'symbol',
value: symbol,
label: translate('TOKEN_Symbol')
},
{
name: 'address',
value: address,
label: translate('TOKEN_Addr')
},
{
name: 'decimal',
value: decimal,
@ -53,50 +67,67 @@ export default class AddCustomTokenForm extends React.Component<Props, State> {
<input
className={classnames(
inputClasses,
errors[field.name] ? 'is-invalid' : 'is-valid'
errors[field.name] ? 'is-invalid' : field.value ? 'is-valid' : ''
)}
type="text"
name={field.name}
value={field.value}
onChange={this.onFieldChange}
/>
{typeof errors[field.name] === 'string' && (
<div className="AddCustom-field-error">{errors[field.name]}</div>
)}
</label>
);
})}
<button
className="btn btn-primary btn-sm btn-block"
disabled={!this.isValid()}
>
{translate('x_Save')}
</button>
<div className="AddCustom-buttons">
<NewTabLink
href="https://myetherwallet.github.io/knowledge-base/send/adding-new-token-and-sending-custom-tokens.html"
className="AddCustom-buttons-help"
>
{translate('Need help? Learn how to add custom tokens.')}
</NewTabLink>
<button
className="AddCustom-buttons-btn btn btn-primary btn-sm"
disabled={!this.isValid()}
>
{translate('x_Save')}
</button>
<button
className="AddCustom-buttons-btn btn btn-sm btn-default"
onClick={this.props.toggleForm}
>
{translate('x_Cancel')}
</button>
</div>
</form>
);
}
public getErrors() {
const { address, symbol, decimal } = this.state;
const errors = {
decimal: false,
address: false,
symbol: false
};
const errors: { [key: string]: boolean | string } = {};
if (!isPositiveIntegerOrZero(parseInt(decimal, 10))) {
// Formatting errors
if (decimal && !isPositiveIntegerOrZero(parseInt(decimal, 10))) {
errors.decimal = true;
}
if (!isValidETHAddress(address)) {
if (address && !isValidETHAddress(address)) {
errors.address = true;
}
if (!symbol) {
errors.symbol = true;
// Message errors
if (symbol && this.state.tokenSymbolLookup[symbol]) {
errors.symbol = 'A token with this symbol already exists';
}
return errors;
}
public isValid() {
return !Object.keys(this.getErrors()).length;
const { address, symbol, decimal } = this.state;
return !Object.keys(this.getErrors()).length && address && symbol && decimal;
}
public onFieldChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
@ -115,4 +146,11 @@ export default class AddCustomTokenForm extends React.Component<Props, State> {
const { address, symbol, decimal } = this.state;
this.props.onSave({ address, symbol, decimal: parseInt(decimal, 10) });
};
private generateSymbolLookup(tokens: Token[]) {
return tokens.reduce((prev, tk) => {
prev[tk.symbol] = true;
return prev;
}, {});
}
}

View File

@ -0,0 +1,131 @@
import React from 'react';
import translate from 'translations';
import { Token } from 'config/data';
import { TokenBalance } from 'selectors/wallet';
import AddCustomTokenForm from './AddCustomTokenForm';
import TokenRow from './TokenRow';
interface Props {
allTokens: Token[];
tokenBalances: TokenBalance[];
hasSavedWalletTokens: boolean;
scanWalletForTokens(): any;
setWalletTokens(tokens: string[]): any;
onAddCustomToken(token: Token): any;
onRemoveCustomToken(symbol: string): any;
}
interface State {
trackedTokens: { [symbol: string]: boolean };
showCustomTokenForm: boolean;
}
export default class TokenBalances extends React.Component<Props, State> {
public state = {
trackedTokens: {},
showCustomTokenForm: false
};
public componentWillReceiveProps(nextProps: Props) {
if (nextProps.tokenBalances !== this.props.tokenBalances) {
const trackedTokens = nextProps.tokenBalances.reduce((prev, t) => {
prev[t.symbol] = !t.balance.isZero();
return prev;
}, {});
this.setState({ trackedTokens });
}
}
public render() {
const { allTokens, tokenBalances, hasSavedWalletTokens } = this.props;
const { showCustomTokenForm, trackedTokens } = this.state;
let bottom;
if (!hasSavedWalletTokens) {
bottom = (
<div className="TokenBalances-buttons">
<button className="btn btn-primary btn-block" onClick={this.handleSetWalletTokens}>
<span>{translate('x_Save')}</span>
</button>
<p className="TokenBalances-buttons-help">
{translate('Missing tokens? You can add custom tokens next.')}
</p>
</div>
);
} else if (showCustomTokenForm) {
bottom = (
<div className="TokenBalances-form">
<AddCustomTokenForm
allTokens={allTokens}
onSave={this.addCustomToken}
toggleForm={this.toggleShowCustomTokenForm}
/>
</div>
);
} else {
bottom = (
<div className="TokenBalances-buttons">
<button className="btn btn-default btn-xs" onClick={this.toggleShowCustomTokenForm}>
<span>{translate('SEND_custom')}</span>
</button>{' '}
<button className="btn btn-default btn-xs" onClick={this.props.scanWalletForTokens}>
<span>Scan for New Tokens</span>
</button>
</div>
);
}
return (
<div>
{!hasSavedWalletTokens && (
<p className="TokenBalances-help">Select which tokens you would like to keep track of</p>
)}
<table className="TokenBalances-rows">
<tbody>
{tokenBalances.map(
token =>
token ? (
<TokenRow
key={token.symbol}
balance={token.balance}
symbol={token.symbol}
custom={token.custom}
decimal={token.decimal}
tracked={trackedTokens[token.symbol]}
toggleTracked={!hasSavedWalletTokens && this.toggleTrack}
onRemove={this.props.onRemoveCustomToken}
/>
) : null
)}
</tbody>
</table>
{bottom}
</div>
);
}
private toggleTrack = (symbol: string) => {
this.setState({
trackedTokens: {
...this.state.trackedTokens,
[symbol]: !this.state.trackedTokens[symbol]
}
});
};
private toggleShowCustomTokenForm = () => {
this.setState({
showCustomTokenForm: !this.state.showCustomTokenForm
});
};
private addCustomToken = (token: Token) => {
this.props.onAddCustomToken(token);
this.setState({ showCustomTokenForm: false });
};
private handleSetWalletTokens = () => {
const { trackedTokens } = this.state;
const desiredTokens = Object.keys(trackedTokens).filter(t => trackedTokens[t]);
this.props.setWalletTokens(desiredTokens);
};
}

View File

@ -11,21 +11,25 @@
&-balance {
@include mono;
}
&-symbol {
position: relative;
font-weight: 300;
&-remove {
margin-left: -32px;
margin-right: 20px;
height: 12px;
position: absolute;
top: 50%;
right: 4px;
float: right;
font-size: 18px;
cursor: pointer;
opacity: 0.4;
transform: translateY(-50%);
&:hover {
opacity: 1;
}
}
}
&-symbol {
font-weight: 300;
}
}

View File

@ -4,11 +4,15 @@ import { TokenValue } from 'libs/units';
import { UnitDisplay } from 'components/ui';
import './TokenRow.scss';
type ToggleTrackedFn = (symbol: string) => void;
interface Props {
balance: TokenValue;
symbol: string;
custom?: boolean;
decimal: number;
tracked: boolean;
toggleTracked: ToggleTrackedFn | false;
onRemove(symbol: string): void;
}
interface State {
@ -21,16 +25,32 @@ export default class TokenRow extends React.Component<Props, State> {
};
public render() {
const { balance, symbol, custom, decimal } = this.props;
const { balance, symbol, custom, decimal, tracked } = this.props;
const { showLongBalance } = this.state;
return (
<tr className="TokenRow">
<tr className="TokenRow" onClick={this.handleToggleTracked}>
{this.props.toggleTracked && (
<td className="TokenRow-toggled">
<input type="checkbox" checked={tracked} />
</td>
)}
<td
className="TokenRow-balance"
title={`${balance.toString()} (Double-Click)`}
onDoubleClick={this.toggleShowLongBalance}
>
<span>
<UnitDisplay
value={balance}
decimal={decimal}
displayShortBalance={!showLongBalance}
checkOffline={true}
/>
</span>
</td>
<td className="TokenRow-symbol">
{symbol}
{!!custom && (
<img
src={removeIcon}
@ -40,23 +60,12 @@ export default class TokenRow extends React.Component<Props, State> {
tabIndex={0}
/>
)}
<span>
<UnitDisplay
value={balance}
decimal={decimal}
displayShortBalance={!showLongBalance}
/>
</span>
</td>
<td className="TokenRow-symbol">{symbol}</td>
</tr>
);
}
public toggleShowLongBalance = (
// TODO: don't use any
e: any
) => {
public toggleShowLongBalance = (e: React.SyntheticEvent<HTMLTableDataCellElement>) => {
e.preventDefault();
this.setState(state => {
return {
@ -65,7 +74,13 @@ export default class TokenRow extends React.Component<Props, State> {
});
};
public onRemove = () => {
private onRemove = () => {
this.props.onRemove(this.props.symbol);
};
private handleToggleTracked = () => {
if (this.props.toggleTracked) {
this.props.toggleTracked(this.props.symbol);
}
};
}

View File

@ -5,14 +5,35 @@
margin-top: 0;
}
&-help {
font-size: $font-size-small;
margin-bottom: 15px;
}
&-scan {
margin-top: 10px;
}
&-loader {
padding: 25px 0;
text-align: center;
}
&-rows {
width: 100%;
margin-bottom: $space;
}
&-form {
margin-top: $space * 2;
padding-top: $space;
border-top: 1px solid $gray-lighter;
margin-top: $space;
}
&-buttons {
&-help {
padding-top: 10px;
text-align: center;
font-size: $font-size-xs;
color: $gray;
}
}
}

View File

@ -1,93 +1,120 @@
import { Token } from 'config/data';
import React from 'react';
import { TokenBalance } from 'selectors/wallet';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import {
addCustomToken,
removeCustomToken,
TAddCustomToken,
TRemoveCustomToken
} from 'actions/customTokens';
import {
scanWalletForTokens,
TScanWalletForTokens,
setWalletTokens,
TSetWalletTokens
} from 'actions/wallet';
import { getAllTokens } from 'selectors/config';
import { getTokenBalances, getWalletInst, getWalletConfig, TokenBalance } from 'selectors/wallet';
import { Token } from 'config/data';
import translate from 'translations';
import AddCustomTokenForm from './AddCustomTokenForm';
import Balances from './Balances';
import Spinner from 'components/ui/Spinner';
import './index.scss';
import TokenRow from './TokenRow';
interface Props {
tokens: TokenBalance[];
onAddCustomToken(token: Token): any;
onRemoveCustomToken(symbol: string): any;
interface StateProps {
wallet: AppState['wallet']['inst'];
walletConfig: AppState['wallet']['config'];
tokens: Token[];
tokenBalances: TokenBalance[];
tokensError: AppState['wallet']['tokensError'];
isTokensLoading: AppState['wallet']['isTokensLoading'];
hasSavedWalletTokens: AppState['wallet']['hasSavedWalletTokens'];
}
interface State {
showAllTokens: boolean;
showCustomTokenForm: boolean;
interface ActionProps {
addCustomToken: TAddCustomToken;
removeCustomToken: TRemoveCustomToken;
scanWalletForTokens: TScanWalletForTokens;
setWalletTokens: TSetWalletTokens;
}
export default class TokenBalances extends React.Component<Props, State> {
public state = {
showAllTokens: false,
showCustomTokenForm: false
};
type Props = StateProps & ActionProps;
class TokenBalances extends React.Component<Props, {}> {
public render() {
const { tokens } = this.props;
const shownTokens = tokens.filter(
token => !token.balance.eqn(0) || token.custom || this.state.showAllTokens
);
const {
tokens,
walletConfig,
tokenBalances,
hasSavedWalletTokens,
isTokensLoading,
tokensError
} = this.props;
const walletTokens = walletConfig ? walletConfig.tokens : [];
let content;
if (tokensError) {
content = <h5>{tokensError}</h5>;
} else if (isTokensLoading) {
content = (
<div className="TokenBalances-loader">
<Spinner size="x3" />
</div>
);
} else if (!walletTokens) {
content = (
<button
className="TokenBalances-scan btn btn-primary btn-block"
onClick={this.scanWalletForTokens}
>
{translate('Scan for my Tokens')}
</button>
);
} else {
const shownBalances = tokenBalances.filter(t => walletTokens.includes(t.symbol));
content = (
<Balances
allTokens={tokens}
tokenBalances={shownBalances}
hasSavedWalletTokens={hasSavedWalletTokens}
scanWalletForTokens={this.scanWalletForTokens}
setWalletTokens={this.props.setWalletTokens}
onAddCustomToken={this.props.addCustomToken}
onRemoveCustomToken={this.props.removeCustomToken}
/>
);
}
return (
<section className="TokenBalances">
<h5 className="TokenBalances-title">{translate('sidebar_TokenBal')}</h5>
<table className="TokenBalances-rows">
<tbody>
{shownTokens.map(token => (
<TokenRow
key={token.symbol}
balance={token.balance}
symbol={token.symbol}
custom={token.custom}
decimal={token.decimal}
onRemove={this.props.onRemoveCustomToken}
/>
))}
</tbody>
</table>
<div className="TokenBalances-buttons">
<button
className="btn btn-default btn-xs"
onClick={this.toggleShowAllTokens}
>
{!this.state.showAllTokens ? 'Show All Tokens' : 'Hide Tokens'}
</button>{' '}
<button
className="btn btn-default btn-xs"
onClick={this.toggleShowCustomTokenForm}
>
<span>{translate('SEND_custom')}</span>
</button>
</div>
{this.state.showCustomTokenForm && (
<div className="TokenBalances-form">
<AddCustomTokenForm onSave={this.addCustomToken} />
</div>
)}
{content}
</section>
);
}
public toggleShowAllTokens = () => {
this.setState(state => {
return {
showAllTokens: !state.showAllTokens
};
});
};
public toggleShowCustomTokenForm = () => {
this.setState(state => {
return {
showCustomTokenForm: !state.showCustomTokenForm
};
});
};
public addCustomToken = (token: Token) => {
this.props.onAddCustomToken(token);
this.setState({ showCustomTokenForm: false });
private scanWalletForTokens = () => {
if (this.props.wallet) {
this.props.scanWalletForTokens(this.props.wallet);
}
};
}
function mapStateToProps(state: AppState): StateProps {
return {
wallet: getWalletInst(state),
walletConfig: getWalletConfig(state),
tokens: getAllTokens(state),
tokenBalances: getTokenBalances(state),
tokensError: state.wallet.tokensError,
isTokensLoading: state.wallet.isTokensLoading,
hasSavedWalletTokens: state.wallet.hasSavedWalletTokens
};
}
export default connect(mapStateToProps, {
addCustomToken,
removeCustomToken,
scanWalletForTokens,
setWalletTokens
})(TokenBalances);

View File

@ -1,22 +1,11 @@
import {
addCustomToken,
removeCustomToken,
TAddCustomToken,
TRemoveCustomToken
} from 'actions/customTokens';
import { showNotification, TShowNotification } from 'actions/notifications';
import { fetchCCRates as dFetchCCRates, TFetchCCRates } from 'actions/rates';
import { fetchCCRates, TFetchCCRates } from 'actions/rates';
import { NetworkConfig } from 'config/data';
import { IWallet, Balance } from 'libs/wallet';
import React from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getNetworkConfig } from 'selectors/config';
import {
getTokenBalances,
getWalletInst,
TokenBalance
} from 'selectors/wallet';
import { getShownTokenBalances, getWalletInst, TokenBalance } from 'selectors/wallet';
import AccountInfo from './AccountInfo';
import EquivalentValues from './EquivalentValues';
import Promos from './Promos';
@ -30,9 +19,6 @@ interface Props {
tokenBalances: TokenBalance[];
rates: AppState['rates']['rates'];
ratesError: AppState['rates']['ratesError'];
showNotification: TShowNotification;
addCustomToken: TAddCustomToken;
removeCustomToken: TRemoveCustomToken;
fetchCCRates: TFetchCCRates;
}
@ -44,15 +30,8 @@ interface Block {
export class BalanceSidebar extends React.Component<Props, {}> {
public render() {
const {
wallet,
balance,
network,
tokenBalances,
rates,
ratesError,
fetchCCRates
} = this.props;
const { wallet, balance, network, tokenBalances, rates, ratesError } = this.props;
if (!wallet) {
return null;
}
@ -64,9 +43,7 @@ export class BalanceSidebar extends React.Component<Props, {}> {
},
{
name: 'Account Info',
content: (
<AccountInfo wallet={wallet} balance={balance} network={network} />
)
content: <AccountInfo wallet={wallet} balance={balance} network={network} />
},
{
name: 'Promos',
@ -75,13 +52,7 @@ export class BalanceSidebar extends React.Component<Props, {}> {
},
{
name: 'Token Balances',
content: (
<TokenBalances
tokens={tokenBalances}
onAddCustomToken={this.props.addCustomToken}
onRemoveCustomToken={this.props.removeCustomToken}
/>
)
content: <TokenBalances />
},
{
name: 'Equivalent Values',
@ -91,7 +62,7 @@ export class BalanceSidebar extends React.Component<Props, {}> {
tokenBalances={tokenBalances}
rates={rates}
ratesError={ratesError}
fetchCCRates={fetchCCRates}
fetchCCRates={this.props.fetchCCRates}
/>
)
}
@ -100,10 +71,7 @@ export class BalanceSidebar extends React.Component<Props, {}> {
return (
<aside>
{blocks.map(block => (
<section
className={`Block ${block.isFullWidth ? 'is-full-width' : ''}`}
key={block.name}
>
<section className={`Block ${block.isFullWidth ? 'is-full-width' : ''}`} key={block.name}>
{block.content}
</section>
))}
@ -116,7 +84,7 @@ function mapStateToProps(state: AppState) {
return {
wallet: getWalletInst(state),
balance: state.wallet.balance,
tokenBalances: getTokenBalances(state),
tokenBalances: getShownTokenBalances(state, true),
network: getNetworkConfig(state),
rates: state.rates.rates,
ratesError: state.rates.ratesError
@ -124,8 +92,5 @@ function mapStateToProps(state: AppState) {
}
export default connect(mapStateToProps, {
addCustomToken,
removeCustomToken,
showNotification,
fetchCCRates: dFetchCCRates
fetchCCRates
})(BalanceSidebar);

View File

@ -0,0 +1,142 @@
import Modal, { IButton } from 'components/ui/Modal';
import Spinner from 'components/ui/Spinner';
import { Details, Summary } from './components';
import React from 'react';
import { connect } from 'react-redux';
import { getWalletType, IWalletType } from 'selectors/wallet';
import { getLanguageSelection } from 'selectors/config';
import {
broadcastLocalTransactionRequested,
TBroadcastLocalTransactionRequested,
broadcastWeb3TransactionRequested,
TBroadcastWeb3TransactionRequested
} from 'actions/transaction';
import {
currentTransactionBroadcasting,
currentTransactionBroadcasted,
currentTransactionFailed
} from 'selectors/transaction';
import translate, { translateRaw } from 'translations';
import './ConfirmationModal.scss';
import { AppState } from 'reducers';
interface DispatchProps {
broadcastLocalTransactionRequested: TBroadcastLocalTransactionRequested;
broadcastWeb3TransactionRequested: TBroadcastWeb3TransactionRequested;
}
interface StateProps {
lang: string;
walletTypes: IWalletType;
transactionBroadcasting: boolean;
transactionBroadcasted: boolean;
transactionFailed: boolean;
}
interface OwnProps {
onClose(): void;
}
interface State {
retryingFailedBroadcast: boolean;
timeToRead: number;
}
type Props = DispatchProps & StateProps & OwnProps;
class ConfirmationModalClass extends React.Component<Props, State> {
private readTimer = 0;
public constructor(props: Props) {
super(props);
const { transactionFailed } = props;
this.state = {
timeToRead: 5,
retryingFailedBroadcast: transactionFailed
};
}
public componentDidUpdate() {
if (this.props.transactionBroadcasted && !this.state.retryingFailedBroadcast) {
this.props.onClose();
}
}
// Count down 5 seconds before allowing them to confirm
public componentDidMount() {
this.readTimer = window.setInterval(() => {
if (this.state.timeToRead > 0) {
this.setState({ timeToRead: this.state.timeToRead - 1 });
} else {
window.clearInterval(this.readTimer);
}
}, 1000);
}
public render() {
const { onClose, transactionBroadcasting } = this.props;
const { timeToRead } = this.state;
const buttonPrefix = timeToRead > 0 ? `(${timeToRead}) ` : '';
const buttons: IButton[] = [
{
text: buttonPrefix + translateRaw('SENDModal_Yes'),
type: 'primary',
disabled: timeToRead > 0,
onClick: this.confirm
},
{
text: translateRaw('SENDModal_No'),
type: 'default',
onClick: onClose
}
];
return (
<div className="ConfModalWrap">
<Modal
title="Confirm Your Transaction"
buttons={buttons}
handleClose={onClose}
disableButtons={transactionBroadcasting}
isOpen={true}
>
<div className="ConfModal">
{transactionBroadcasting ? (
<div className="ConfModal-loading">
<Spinner size="x5" />
</div>
) : (
<div>
<Summary />
<Details />
<div className="ConfModal-confirm">{translate('SENDModal_Content_3')}</div>
</div>
)}
</div>
</Modal>
</div>
);
}
public componentWillUnmount() {
window.clearInterval(this.readTimer);
}
private confirm = () => {
if (this.state.timeToRead < 1) {
this.props.walletTypes.isWeb3Wallet
? this.props.broadcastWeb3TransactionRequested()
: this.props.broadcastLocalTransactionRequested();
this.setState({ retryingFailedBroadcast: false });
}
};
}
export const ConfirmationModal = connect(
(state: AppState) => ({
transactionBroadcasting: currentTransactionBroadcasting(state),
transactionBroadcasted: currentTransactionBroadcasted(state),
transactionFailed: currentTransactionFailed(state),
lang: getLanguageSelection(state),
walletTypes: getWalletType(state)
}),
{ broadcastLocalTransactionRequested, broadcastWeb3TransactionRequested }
)(ConfirmationModalClass);

View File

@ -0,0 +1,43 @@
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import { UnitDisplay } from 'components/ui';
import { Wei, TokenValue } from 'libs/units';
import ERC20 from 'libs/erc20';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getDecimal, getUnit } from 'selectors/transaction';
interface StateProps {
unit: string;
decimal: number;
}
class AmountClass extends Component<StateProps> {
public render() {
return (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { value, data } = getTransactionFields(transactionInstance);
const { decimal, unit } = this.props;
return (
<UnitDisplay
decimal={decimal}
value={
unit === 'ether' ? Wei(value) : TokenValue(ERC20.transfer.decodeInput(data)._value)
}
symbol={unit}
checkOffline={false}
/>
);
}}
/>
);
}
}
export const Amount = connect((state: AppState) => ({
decimal: getDecimal(state),
unit: getUnit(state)
}))(AmountClass);

View File

@ -0,0 +1,45 @@
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import { Aux } from 'components/ui';
import ERC20 from 'libs/erc20';
import { From } from '../From';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getUnit } from 'selectors/transaction';
import { AppState } from 'reducers';
interface StateProps {
unit: string;
}
class AddressesClass extends Component<StateProps> {
public render() {
return (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { to, data } = getTransactionFields(transactionInstance);
return (
<Aux>
<li className="ConfModal-details-detail">
You are sending from <From withFrom={from => <code>{from}</code>} />
</li>
<li className="ConfModal-details-detail">
You are sending to{' '}
<code>
{this.props.unit === 'ether' ? to : ERC20.transfer.decodeInput(data)._to}
</code>
</li>
</Aux>
);
}}
/>
);
}
}
export const Addresses = connect((state: AppState) => ({ unit: getUnit(state) }))(AddressesClass);
//got duplication here

View File

@ -0,0 +1,18 @@
import { GasPrice } from './components';
import { Amount } from '../../Amount';
import React from 'react';
export const AmountAndGasPrice: React.SFC<{}> = () => (
<li className="ConfModal-details-detail">
<p>
You are sending{' '}
<strong>
<Amount />
</strong>{' '}
with a gas price of{' '}
<strong>
<GasPrice />
</strong>
</p>
</li>
);

View File

@ -0,0 +1,18 @@
import React from 'react';
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import { UnitDisplay } from 'components/ui';
import { Wei } from 'libs/units';
export const GasPrice: React.SFC<{}> = () => (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { gasPrice } = getTransactionFields(transactionInstance);
return (
<UnitDisplay unit={'gwei'} value={Wei(gasPrice)} symbol={'gwei'} checkOffline={false} />
);
}}
/>
);

View File

@ -0,0 +1 @@
export * from './GasPrice';

View File

@ -0,0 +1 @@
export * from './AmountAndGasPrice';

View File

@ -0,0 +1,44 @@
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { isEtherTransaction } from 'selectors/transaction';
interface StateProps {
showData: boolean;
}
class ShowDataWhenNoTokenClass extends Component<StateProps> {
public render() {
return this.props.showData ? <Data /> : null;
}
}
const ShowDataWhenNoToken = connect((state: AppState) => ({ showData: isEtherTransaction(state) }))(
ShowDataWhenNoTokenClass
);
const Data: React.SFC<{}> = () => (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { data } = getTransactionFields(transactionInstance);
const dataBox = (
<span>
You are sending the following data:{' '}
<textarea className="form-control" value={data} rows={3} disabled={true} />
</span>
);
return (
<li className="ConfModal-details-detail">
{!emptyData(data) ? dataBox : 'There is no data attached to this transaction'}
</li>
);
}}
/>
);
const emptyData = (data: string) => data === '0x';
export { ShowDataWhenNoToken as Data };

View File

@ -0,0 +1,16 @@
import { Addresses } from './Addresses';
import { Data } from './Data';
import { Node } from './Node';
import { AmountAndGasPrice } from './AmountAndGasPrice';
import { Nonce } from './Nonce';
import React from 'react';
export const Details: React.SFC<{}> = () => (
<ul className="ConfModal-details">
<Addresses />
<Nonce />
<AmountAndGasPrice />
<Node />
<Data />
</ul>
);

View File

@ -0,0 +1,24 @@
import { NodeConfig } from 'config/data';
import React, { Component } from 'react';
import { AppState } from 'reducers';
import { connect } from 'react-redux';
import { getNodeConfig } from 'selectors/config';
interface StateProps {
node: NodeConfig;
}
class NodeClass extends Component<StateProps, {}> {
public render() {
return (
<li className="ConfModal-details-detail">
You are interacting with the <strong>{this.props.node.network}</strong> network provided by{' '}
<strong>{this.props.node.service}</strong>
</li>
);
}
}
export const Node = connect((state: AppState) => ({
node: getNodeConfig(state)
}))(NodeClass);

View File

@ -0,0 +1,19 @@
import React from 'react';
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import { Nonce as makeNonce } from 'libs/units';
export const Nonce: React.SFC<{}> = () => (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { nonce } = getTransactionFields(transactionInstance);
return (
<li className="ConfModal-details-detail">
You are sending with a nonce of <code>{makeNonce(nonce).toString()}</code>
</li>
);
}}
/>
);

View File

@ -0,0 +1 @@
export * from './Details';

View File

@ -0,0 +1,21 @@
import { AppState } from 'reducers';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getFrom } from 'selectors/transaction';
type From = AppState['transaction']['meta']['from'];
interface StateProps {
from: From;
}
interface OwnProps {
withFrom(from: string): React.ReactElement<any> | null;
}
class FromClass extends Component<StateProps & OwnProps, {}> {
public render() {
const { from, withFrom } = this.props;
return from ? withFrom(from) : null;
}
}
export const From = connect((state: AppState) => ({ from: getFrom(state) }))(FromClass);

View File

@ -0,0 +1,10 @@
import { SummaryAmount, SummaryFrom, SummaryTo } from './components';
import React from 'react';
export const Summary: React.SFC<{}> = () => (
<div className="ConfModal-summary">
<SummaryFrom />
<SummaryAmount />
<SummaryTo />
</div>
);

View File

@ -0,0 +1,11 @@
import React from 'react';
import { Amount } from '../../Amount';
export const SummaryAmount: React.SFC<{}> = () => (
<div className="ConfModal-summary-amount">
<div className="ConfModal-summary-amount-arrow" />
<div className="ConfModal-summary-amount-currency">
<Amount />
</div>
</div>
);

View File

@ -0,0 +1,9 @@
import React from 'react';
import { From } from '../../From';
import { Identicon } from 'components/ui';
export const SummaryFrom: React.SFC<{}> = () => (
<div className="ConfModal-summary-icon ConfModal-summary-icon--from">
<From withFrom={from => <Identicon size="100%" address={from} />} />
</div>
);

View File

@ -0,0 +1,37 @@
import { Identicon } from 'components/ui';
import { SerializedTransaction } from 'components/renderCbs';
import { makeTransaction, getTransactionFields } from 'libs/transaction';
import ERC20 from 'libs/erc20';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getUnit } from 'selectors/transaction';
interface StateProps {
unit: string;
}
//got duplication here
class SummaryToClass extends Component<StateProps> {
public render() {
return (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { to, data } = getTransactionFields(transactionInstance);
return (
<div className="ConfModal-summary-icon ConfModal-summary-icon--to">
<Identicon
size="100%"
address={this.props.unit === 'ether' ? to : ERC20.transfer.decodeInput(data)._to}
/>
</div>
);
}}
/>
);
}
}
export const SummaryTo = connect((state: AppState) => ({ unit: getUnit(state) }))(SummaryToClass);

View File

@ -0,0 +1,3 @@
export * from './SummaryTo';
export * from './SummaryFrom';
export * from './SummaryAmount';

View File

@ -0,0 +1 @@
export * from './Summary';

View File

@ -0,0 +1,2 @@
export * from './Details';
export * from './Summary';

View File

@ -0,0 +1 @@
export * from './ConfirmationModal';

View File

@ -0,0 +1,18 @@
import { CustomMessage, messages } from './components';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getCurrentTo, ICurrentTo } from 'selectors/transaction';
interface StateProps {
currentTo: ICurrentTo;
}
class CurrentCustomMessageClass extends Component<StateProps> {
public render() {
return <CustomMessage message={messages.find(m => m.to === this.props.currentTo.raw)} />;
}
}
export const CurrentCustomMessage = connect((state: AppState) => ({
currentTo: getCurrentTo(state)
}))(CurrentCustomMessageClass);

View File

@ -7,22 +7,19 @@ interface Props {
};
}
export default function CustomMessage(props: Props) {
export const CustomMessage = (props: Props) => {
return (
<div className="clearfix form-group">
{!!props.message &&
{!!props.message && (
<div className="alert alert-info col-xs-12 clearfix">
<p>
<small>
A message from {props.message.to}
</small>
<small>A message from {props.message.to}</small>
</p>
<p>
<strong>
{props.message.msg}
</strong>
<strong>{props.message.msg}</strong>
</p>
</div>}
</div>
)}
</div>
);
}
};

View File

@ -0,0 +1,2 @@
export * from './CustomMessage';
export * from './messages';

View File

@ -1,4 +1,4 @@
export default [
export const messages = [
{
// donation address example
to: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8',

View File

@ -0,0 +1 @@
export * from './CurrentCustomMessage';

View File

@ -0,0 +1,32 @@
import { DataFieldFactory } from './DataFieldFactory';
import React from 'react';
import { Expandable, ExpandHandler } from 'components/ui';
import translate from 'translations';
import { donationAddressMap } from 'config/data';
const expander = (expandHandler: ExpandHandler) => (
<a onClick={expandHandler}>
<p className="strong">{translate('TRANS_advanced')}</p>
</a>
);
export const DataField: React.SFC<{}> = () => (
<DataFieldFactory
withProps={({ data: { raw }, dataExists, onChange, readOnly }) => (
<Expandable expandLabel={expander}>
<div className="form-group">
<label>{translate('TRANS_data')}</label>
<input
className={`form-control ${dataExists ? 'is-valid' : 'is-invalid'}`}
type="text"
placeholder={donationAddressMap.ETH}
value={raw}
readOnly={!!readOnly}
onChange={onChange}
/>
</div>
</Expandable>
)}
/>
);

View File

@ -0,0 +1,65 @@
import { DataInput } from './DataInputFactory';
import { Query } from 'components/renderCbs';
import { inputData, TInputData } from 'actions/transaction';
import React from 'react';
import { connect } from 'react-redux';
import { isEtherTransaction } from 'selectors/transaction';
import { AppState } from 'reducers';
export interface CallBackProps {
data: AppState['transaction']['fields']['data'];
dataExists: boolean;
readOnly: boolean;
onChange(ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>);
}
interface DispatchProps {
isEtherTransaction;
inputData: TInputData;
}
interface OwnProps {
data: string | null;
withProps(props: CallBackProps): React.ReactElement<any> | null;
}
interface StateProps {
isEtherTransaction: boolean;
}
type Props = DispatchProps & OwnProps & StateProps;
class DataFieldClass extends React.Component<Props> {
public componentDidMount() {
const { data } = this.props;
if (data) {
this.props.inputData(data);
}
}
public render() {
return this.props.isEtherTransaction ? (
<DataInput onChange={this.setData} withProps={this.props.withProps} />
) : null;
}
private setData = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { value } = ev.currentTarget;
this.props.inputData(value);
};
}
const DataField = connect(
(state: AppState) => ({ isEtherTransaction: isEtherTransaction(state) }),
{ inputData }
)(DataFieldClass);
interface DefaultDataFieldProps {
withProps(props: CallBackProps): React.ReactElement<any> | null;
}
const DefaultDataField: React.SFC<DefaultDataFieldProps> = ({ withProps }) => (
/* TODO: check query param of tokens too */
<Query
params={['data']}
withQuery={({ data }) => <DataField data={data} withProps={withProps} />}
/>
);
export { DefaultDataField as DataFieldFactory };

View File

@ -0,0 +1,36 @@
import React, { Component } from 'react';
import { Query } from 'components/renderCbs';
import { getData, getDataExists } from 'selectors/transaction';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { CallBackProps } from 'components/DataFieldFactory';
interface OwnProps {
withProps(props: CallBackProps): React.ReactElement<any> | null;
onChange(ev: React.FormEvent<HTMLInputElement>);
}
interface StateProps {
data: AppState['transaction']['fields']['data'];
dataExists: boolean;
}
type Props = OwnProps & StateProps;
class DataInputClass extends Component<Props> {
public render() {
const { data, onChange, dataExists } = this.props;
return (
<Query
params={['readOnly']}
withQuery={({ readOnly }) =>
this.props.withProps({ data, onChange, readOnly: !!readOnly, dataExists })
}
/>
);
}
}
export const DataInput = connect((state: AppState) => ({
data: getData(state),
dataExists: getDataExists(state)
}))(DataInputClass);

View File

@ -0,0 +1 @@
export * from './DataFieldFactory';

View File

@ -0,0 +1,20 @@
@import 'common/sass/variables';
@import 'common/sass/mixins';
.ErrorScreen {
@include cover-message;
background: $brand-danger;
code {
display: block;
word-wrap: break-word;
background: rgba(#fff, 0.25);
border: 1px solid rgba(#fff, 0.6);
color: #FFF;
width: 100%;
padding: 10px;
border-radius: 2px;
text-shadow: none;
opacity: 0.8;
}
}

Some files were not shown because too many files have changed in this diff Show More