diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000000..ce3b78b218d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "printWidth": 100, + "singleQuote": true, + "useTabs": false, + "semi": true, + "tabWidth": 2, + "trailingComma": + "none" +} diff --git a/.travis.yml b/.travis.yml index 8da34db3d4d..6155a7b6b84 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 \ No newline at end of file diff --git a/README.md b/README.md index 908415224c5..65671931d5e 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/common/Root.tsx b/common/Root.tsx index a531d9a46a3..17b077751c8 100644 --- a/common/Root.tsx +++ b/common/Root.tsx @@ -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 { +interface State { + error: Error | null; +} + +export default class Root extends Component { + public state = { + error: null + }; + + public componentDidCatch(error) { + this.setState({ error }); + } + public render() { const { store, history } = this.props; + const { error } = this.state; + + if (error) { + return ; + } + // key={Math.random()} = hack for HMR from https://github.com/webpack/webpack-dev-server/issues/395 return (
- + + + + - - +
@@ -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 => { + ); }); diff --git a/common/actions/config/actionCreators.ts b/common/actions/config/actionCreators.ts index dcfea5c4791..4a71626c19a 100644 --- a/common/actions/config/actionCreators.ts +++ b/common/actions/config/actionCreators.ts @@ -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 diff --git a/common/actions/config/actionTypes.ts b/common/actions/config/actionTypes.ts index 7e72703d165..e42d47ebec3 100644 --- a/common/actions/config/actionTypes.ts +++ b/common/actions/config/actionTypes.ts @@ -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 diff --git a/common/actions/config/constants.ts b/common/actions/config/constants.ts index 519f2ba6d45..dff084345b0 100644 --- a/common/actions/config/constants.ts +++ b/common/actions/config/constants.ts @@ -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', diff --git a/common/actions/config/index.ts b/common/actions/config/index.ts index 113dd69a997..51fcd517a16 100644 --- a/common/actions/config/index.ts +++ b/common/actions/config/index.ts @@ -1,2 +1,3 @@ export * from './actionCreators'; export * from './actionTypes'; +export * from './constants'; diff --git a/common/actions/customTokens/actionCreators.ts b/common/actions/customTokens/actionCreators.ts index 300bacdefd3..a18e84359d6 100644 --- a/common/actions/customTokens/actionCreators.ts +++ b/common/actions/customTokens/actionCreators.ts @@ -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 diff --git a/common/actions/customTokens/index.ts b/common/actions/customTokens/index.ts index 113dd69a997..51fcd517a16 100644 --- a/common/actions/customTokens/index.ts +++ b/common/actions/customTokens/index.ts @@ -1,2 +1,3 @@ export * from './actionCreators'; export * from './actionTypes'; +export * from './constants'; diff --git a/common/actions/deterministicWallets/actionCreators.ts b/common/actions/deterministicWallets/actionCreators.ts index fb043d7c6aa..396cd2d7259 100644 --- a/common/actions/deterministicWallets/actionCreators.ts +++ b/common/actions/deterministicWallets/actionCreators.ts @@ -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 diff --git a/common/actions/generateWallet/actionCreators.ts b/common/actions/generateWallet/actionCreators.ts index 2ae69f218ce..88bf4c8edd8 100644 --- a/common/actions/generateWallet/actionCreators.ts +++ b/common/actions/generateWallet/actionCreators.ts @@ -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(), diff --git a/common/actions/notifications/actionCreators.ts b/common/actions/notifications/actionCreators.ts index bfda0fa30ec..99949933bca 100644 --- a/common/actions/notifications/actionCreators.ts +++ b/common/actions/notifications/actionCreators.ts @@ -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 diff --git a/common/actions/notifications/actionTypes.ts b/common/actions/notifications/actionTypes.ts index f6b70117ee9..3a7377f18d4 100644 --- a/common/actions/notifications/actionTypes.ts +++ b/common/actions/notifications/actionTypes.ts @@ -23,6 +23,4 @@ export interface ShowNotificationAction { } /*** Union Type ***/ -export type NotificationsAction = - | ShowNotificationAction - | CloseNotificationAction; +export type NotificationsAction = ShowNotificationAction | CloseNotificationAction; diff --git a/common/actions/rates/actionCreators.ts b/common/actions/rates/actionCreators.ts index ef5705c15b5..98f1e2e7f7f 100644 --- a/common/actions/rates/actionCreators.ts +++ b/common/actions/rates/actionCreators.ts @@ -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 diff --git a/common/actions/rates/actionPayloads.ts b/common/actions/rates/actionPayloads.ts index 7ba9d9cf880..92e8041242a 100644 --- a/common/actions/rates/actionPayloads.ts +++ b/common/actions/rates/actionPayloads.ts @@ -36,10 +36,12 @@ export const fetchRates = (symbols: string[] = []): Promise => // 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; }, { diff --git a/common/actions/rates/actionTypes.ts b/common/actions/rates/actionTypes.ts index 0609c318ecb..f0d3ca4a2b3 100644 --- a/common/actions/rates/actionTypes.ts +++ b/common/actions/rates/actionTypes.ts @@ -17,7 +17,4 @@ export interface FetchCCRatesFailed { } /*** Union Type ***/ -export type RatesAction = - | FetchCCRates - | FetchCCRatesSucceeded - | FetchCCRatesFailed; +export type RatesAction = FetchCCRates | FetchCCRatesSucceeded | FetchCCRatesFailed; diff --git a/common/actions/swap/actionCreators.ts b/common/actions/swap/actionCreators.ts index 71557b7538f..06010438a20 100644 --- a/common/actions/swap/actionCreators.ts +++ b/common/actions/swap/actionCreators.ts @@ -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 diff --git a/common/actions/swap/actionTypes.ts b/common/actions/swap/actionTypes.ts index 6f81de04c0a..ca0609ca243 100644 --- a/common/actions/swap/actionTypes.ts +++ b/common/actions/swap/actionTypes.ts @@ -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 SwapInputs { + origin: SwapInput; + destination: SwapInput; } -export interface DestinationKindSwapAction { - type: TypeKeys.SWAP_DESTINATION_KIND; - payload: string; +export interface InitSwap { + type: TypeKeys.SWAP_INIT; + payload: SwapInputs; } -export interface OriginAmountSwapAction { - type: TypeKeys.SWAP_ORIGIN_AMOUNT; - payload?: number | null; +export interface Option { + id: string; +} + +export interface ApiResponseObj { + id: string; + options: Option[]; + rate: number; } -export interface DestinationAmountSwapAction { - type: TypeKeys.SWAP_DESTINATION_AMOUNT; - payload?: number | null; +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 diff --git a/common/actions/swap/constants.ts b/common/actions/swap/constants.ts index e343436fffd..6192ec532bb 100644 --- a/common/actions/swap/constants.ts +++ b/common/actions/swap/constants.ts @@ -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', diff --git a/common/actions/transaction/actionCreators/broadcast.ts b/common/actions/transaction/actionCreators/broadcast.ts new file mode 100644 index 00000000000..1b50d676203 --- /dev/null +++ b/common/actions/transaction/actionCreators/broadcast.ts @@ -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 +}; diff --git a/common/actions/transaction/actionCreators/current.ts b/common/actions/transaction/actionCreators/current.ts new file mode 100644 index 00000000000..c9c1f29b594 --- /dev/null +++ b/common/actions/transaction/actionCreators/current.ts @@ -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 }; diff --git a/common/actions/transaction/actionCreators/fields.ts b/common/actions/transaction/actionCreators/fields.ts new file mode 100644 index 00000000000..1e0827c36a0 --- /dev/null +++ b/common/actions/transaction/actionCreators/fields.ts @@ -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 +}; diff --git a/common/actions/transaction/actionCreators/index.ts b/common/actions/transaction/actionCreators/index.ts new file mode 100644 index 00000000000..32cc4be787c --- /dev/null +++ b/common/actions/transaction/actionCreators/index.ts @@ -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'; diff --git a/common/actions/transaction/actionCreators/meta.ts b/common/actions/transaction/actionCreators/meta.ts new file mode 100644 index 00000000000..d27004cb5ac --- /dev/null +++ b/common/actions/transaction/actionCreators/meta.ts @@ -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 }; diff --git a/common/actions/transaction/actionCreators/network.ts b/common/actions/transaction/actionCreators/network.ts new file mode 100644 index 00000000000..f6c5fd28acc --- /dev/null +++ b/common/actions/transaction/actionCreators/network.ts @@ -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 +}; diff --git a/common/actions/transaction/actionCreators/sendEverything.ts b/common/actions/transaction/actionCreators/sendEverything.ts new file mode 100644 index 00000000000..c9a86471dce --- /dev/null +++ b/common/actions/transaction/actionCreators/sendEverything.ts @@ -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 +}; diff --git a/common/actions/transaction/actionCreators/sign.ts b/common/actions/transaction/actionCreators/sign.ts new file mode 100644 index 00000000000..4de60e82425 --- /dev/null +++ b/common/actions/transaction/actionCreators/sign.ts @@ -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 +}; diff --git a/common/actions/transaction/actionCreators/swap.ts b/common/actions/transaction/actionCreators/swap.ts new file mode 100644 index 00000000000..be92e192740 --- /dev/null +++ b/common/actions/transaction/actionCreators/swap.ts @@ -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 +}; diff --git a/common/actions/transaction/actionTypes/actionTypes.ts b/common/actions/transaction/actionTypes/actionTypes.ts new file mode 100644 index 00000000000..2bc7200b1f1 --- /dev/null +++ b/common/actions/transaction/actionTypes/actionTypes.ts @@ -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; diff --git a/common/actions/transaction/actionTypes/broadcast.ts b/common/actions/transaction/actionTypes/broadcast.ts new file mode 100644 index 00000000000..7cefd93dfe8 --- /dev/null +++ b/common/actions/transaction/actionTypes/broadcast.ts @@ -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 +}; diff --git a/common/actions/transaction/actionTypes/current.ts b/common/actions/transaction/actionTypes/current.ts new file mode 100644 index 00000000000..643022c6348 --- /dev/null +++ b/common/actions/transaction/actionTypes/current.ts @@ -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 }; diff --git a/common/actions/transaction/actionTypes/fields.ts b/common/actions/transaction/actionTypes/fields.ts new file mode 100644 index 00000000000..0b151b05bed --- /dev/null +++ b/common/actions/transaction/actionTypes/fields.ts @@ -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 +}; diff --git a/common/actions/transaction/actionTypes/index.ts b/common/actions/transaction/actionTypes/index.ts new file mode 100644 index 00000000000..3eb256deba3 --- /dev/null +++ b/common/actions/transaction/actionTypes/index.ts @@ -0,0 +1 @@ +export * from './actionTypes'; diff --git a/common/actions/transaction/actionTypes/meta.ts b/common/actions/transaction/actionTypes/meta.ts new file mode 100644 index 00000000000..3550d79fb06 --- /dev/null +++ b/common/actions/transaction/actionTypes/meta.ts @@ -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 }; diff --git a/common/actions/transaction/actionTypes/network.ts b/common/actions/transaction/actionTypes/network.ts new file mode 100644 index 00000000000..eee14f59bb6 --- /dev/null +++ b/common/actions/transaction/actionTypes/network.ts @@ -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; +} +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 +}; diff --git a/common/actions/transaction/actionTypes/sendEverything.ts b/common/actions/transaction/actionTypes/sendEverything.ts new file mode 100644 index 00000000000..ff4c6bfbb19 --- /dev/null +++ b/common/actions/transaction/actionTypes/sendEverything.ts @@ -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 +}; diff --git a/common/actions/transaction/actionTypes/sign.ts b/common/actions/transaction/actionTypes/sign.ts new file mode 100644 index 00000000000..d9c46c3ce24 --- /dev/null +++ b/common/actions/transaction/actionTypes/sign.ts @@ -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 +}; diff --git a/common/actions/transaction/actionTypes/swap.ts b/common/actions/transaction/actionTypes/swap.ts new file mode 100644 index 00000000000..6e7f338a2c7 --- /dev/null +++ b/common/actions/transaction/actionTypes/swap.ts @@ -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 }; diff --git a/common/actions/transaction/constants.ts b/common/actions/transaction/constants.ts new file mode 100644 index 00000000000..6da043283a6 --- /dev/null +++ b/common/actions/transaction/constants.ts @@ -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' +} diff --git a/common/actions/transaction/index.ts b/common/actions/transaction/index.ts new file mode 100644 index 00000000000..d5122d97a45 --- /dev/null +++ b/common/actions/transaction/index.ts @@ -0,0 +1,3 @@ +export * from './actionCreators'; +export * from './constants'; +export * from './actionTypes'; diff --git a/common/actions/wallet/actionCreators.ts b/common/actions/wallet/actionCreators.ts index f69305f31cd..7435c972f8e 100644 --- a/common/actions/wallet/actionCreators.ts +++ b/common/actions/wallet/actionCreators.ts @@ -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 + }; +} diff --git a/common/actions/wallet/actionTypes.ts b/common/actions/wallet/actionTypes.ts index e11aab99af2..a0a2d77389d 100644 --- a/common/actions/wallet/actionTypes.ts +++ b/common/actions/wallet/actionTypes.ts @@ -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; diff --git a/common/actions/wallet/constants.ts b/common/actions/wallet/constants.ts index 8ccb8e5d4a6..cf213d28a8e 100644 --- a/common/actions/wallet/constants.ts +++ b/common/actions/wallet/constants.ts @@ -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' } diff --git a/common/api/bity.ts b/common/api/bity.ts index 21989135e08..e10a8e74a81 100644 --- a/common/api/bity.ts +++ b/common/api/bity.ts @@ -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({ diff --git a/common/api/utils.ts b/common/api/utils.ts index 07357ab8659..8996e4c322a 100644 --- a/common/api/utils.ts +++ b/common/api/utils.ts @@ -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; diff --git a/common/components/AddressField/AddressField.tsx b/common/components/AddressField/AddressField.tsx new file mode 100644 index 00000000000..87e098a4c45 --- /dev/null +++ b/common/components/AddressField/AddressField.tsx @@ -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 { + 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 ; + } + + private setAddress = (ev: React.FormEvent) => { + const { value } = ev.currentTarget; + this.props.setCurrentTo(value); + }; +} + +const AddressField = connect(null, { setCurrentTo })(AddressFieldClass); + +const DefaultAddressField: React.SFC<{}> = () => ( + } /> +); + +export { DefaultAddressField as AddressField }; diff --git a/common/components/AddressField/AddressInput.tsx b/common/components/AddressField/AddressInput.tsx new file mode 100644 index 00000000000..9d91c1aacd7 --- /dev/null +++ b/common/components/AddressField/AddressInput.tsx @@ -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): void; +} + +type Props = OwnProps & StateProps; + +//TODO: ENS handling +class AddressInputClass extends Component { + public render() { + const { currentTo, onChange, isValid } = this.props; + const { raw } = currentTo; + return ( +
+
+ + ( + + )} + /> + {/**/} +
+
+ +
+
+ ); + } +} + +export const AddressInput = connect((state: AppState) => ({ + currentTo: getCurrentTo(state), + isValid: isValidCurrentTo(state) +}))(AddressInputClass); diff --git a/common/components/AddressField/components/EnsAddress.tsx b/common/components/AddressField/components/EnsAddress.tsx new file mode 100644 index 00000000000..b26084e41e1 --- /dev/null +++ b/common/components/AddressField/components/EnsAddress.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +/* + + public onChange = (e: React.SyntheticEvent) => { + 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 = ({ ensAddress }) => + (!!ensAddress && ( +

+ ↳ + {ensAddress} +

+ )) || + null; diff --git a/common/components/AddressField/components/index.ts b/common/components/AddressField/components/index.ts new file mode 100644 index 00000000000..ee054d1308b --- /dev/null +++ b/common/components/AddressField/components/index.ts @@ -0,0 +1 @@ +export * from './EnsAddress'; diff --git a/common/components/AddressField/index.ts b/common/components/AddressField/index.ts new file mode 100644 index 00000000000..3164294bd8f --- /dev/null +++ b/common/components/AddressField/index.ts @@ -0,0 +1 @@ +export * from './AddressField'; diff --git a/common/components/AlphaAgreement/index.tsx b/common/components/AlphaAgreement/index.tsx index 0be1ea3e486..cce81bbc202 100644 --- a/common/components/AlphaAgreement/index.tsx +++ b/common/components/AlphaAgreement/index.tsx @@ -25,23 +25,19 @@ export default class AlphaAgreement extends React.Component<{}, State> {

This is an Unstable Version of MEW

- 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.

- 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.

Are you sure you would like to continue?

- diff --git a/common/components/BalanceSidebar/Promos.tsx b/common/components/BalanceSidebar/Promos.tsx index eea71c78e34..06693d12094 100644 --- a/common/components/BalanceSidebar/Promos.tsx +++ b/common/components/BalanceSidebar/Promos.tsx @@ -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: [
Learn more about protecting your funds.
], - 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¤cy=USD', - texts: [ -

It’s now easier to get more ETH

, -
Buy ETH with USD
- ], + texts: [

It’s now easier to get more ETH

,
Buy ETH with USD
], images: [require('assets/images/logo-coinbase.svg')] }, { @@ -87,9 +81,7 @@ export default class Promos extends React.Component<{}, State> { {promos.map((_, index) => { return ( +
+ + {translate('Need help? Learn how to add custom tokens.')} + + + +
); } 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) => { @@ -115,4 +146,11 @@ export default class AddCustomTokenForm extends React.Component { 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; + }, {}); + } } diff --git a/common/components/BalanceSidebar/TokenBalances/Balances.tsx b/common/components/BalanceSidebar/TokenBalances/Balances.tsx new file mode 100644 index 00000000000..4c61c2c97e1 --- /dev/null +++ b/common/components/BalanceSidebar/TokenBalances/Balances.tsx @@ -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 { + 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 = ( +
+ +

+ {translate('Missing tokens? You can add custom tokens next.')} +

+
+ ); + } else if (showCustomTokenForm) { + bottom = ( +
+ +
+ ); + } else { + bottom = ( +
+ {' '} + +
+ ); + } + + return ( +
+ {!hasSavedWalletTokens && ( +

Select which tokens you would like to keep track of

+ )} + + + {tokenBalances.map( + token => + token ? ( + + ) : null + )} + +
+ {bottom} +
+ ); + } + + 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); + }; +} diff --git a/common/components/BalanceSidebar/TokenBalances/TokenRow.scss b/common/components/BalanceSidebar/TokenBalances/TokenRow.scss index 12f7031ef03..5bc171724e2 100644 --- a/common/components/BalanceSidebar/TokenBalances/TokenRow.scss +++ b/common/components/BalanceSidebar/TokenBalances/TokenRow.scss @@ -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; - } } diff --git a/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx b/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx index d7740b6209d..011dd9e4605 100644 --- a/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx +++ b/common/components/BalanceSidebar/TokenBalances/TokenRow.tsx @@ -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 { }; public render() { - const { balance, symbol, custom, decimal } = this.props; + const { balance, symbol, custom, decimal, tracked } = this.props; const { showLongBalance } = this.state; return ( - + + {this.props.toggleTracked && ( + + + + )} + + + + + + {symbol} {!!custom && ( { tabIndex={0} /> )} - - - - {symbol} ); } - public toggleShowLongBalance = ( - // TODO: don't use any - e: any - ) => { + public toggleShowLongBalance = (e: React.SyntheticEvent) => { e.preventDefault(); this.setState(state => { return { @@ -65,7 +74,13 @@ export default class TokenRow extends React.Component { }); }; - public onRemove = () => { + private onRemove = () => { this.props.onRemove(this.props.symbol); }; + + private handleToggleTracked = () => { + if (this.props.toggleTracked) { + this.props.toggleTracked(this.props.symbol); + } + }; } diff --git a/common/components/BalanceSidebar/TokenBalances/index.scss b/common/components/BalanceSidebar/TokenBalances/index.scss index e75cb29b711..dfd4d9e89fe 100644 --- a/common/components/BalanceSidebar/TokenBalances/index.scss +++ b/common/components/BalanceSidebar/TokenBalances/index.scss @@ -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; + } } } diff --git a/common/components/BalanceSidebar/TokenBalances/index.tsx b/common/components/BalanceSidebar/TokenBalances/index.tsx index a8920dbddae..4eaa3ceddcc 100644 --- a/common/components/BalanceSidebar/TokenBalances/index.tsx +++ b/common/components/BalanceSidebar/TokenBalances/index.tsx @@ -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 { - public state = { - showAllTokens: false, - showCustomTokenForm: false - }; +type Props = StateProps & ActionProps; +class TokenBalances extends React.Component { 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; - return ( -
-
{translate('sidebar_TokenBal')}
- - - {shownTokens.map(token => ( - - ))} - -
+ const walletTokens = walletConfig ? walletConfig.tokens : []; -
- {' '} - + let content; + if (tokensError) { + content =
{tokensError}
; + } else if (isTokensLoading) { + content = ( +
+
+ ); + } else if (!walletTokens) { + content = ( + + ); + } else { + const shownBalances = tokenBalances.filter(t => walletTokens.includes(t.symbol)); - {this.state.showCustomTokenForm && ( -
- -
- )} + content = ( + + ); + } + + return ( +
+
{translate('sidebar_TokenBal')}
+ {content}
); } - public toggleShowAllTokens = () => { - this.setState(state => { - return { - showAllTokens: !state.showAllTokens - }; - }); - }; - - public toggleShowCustomTokenForm = () => { - this.setState(state => { - return { - showCustomTokenForm: !state.showCustomTokenForm - }; - }); + private scanWalletForTokens = () => { + if (this.props.wallet) { + this.props.scanWalletForTokens(this.props.wallet); + } }; +} - public addCustomToken = (token: Token) => { - this.props.onAddCustomToken(token); - this.setState({ showCustomTokenForm: false }); +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); diff --git a/common/components/BalanceSidebar/index.tsx b/common/components/BalanceSidebar/index.tsx index 7fab773dc8e..3221afb3852 100644 --- a/common/components/BalanceSidebar/index.tsx +++ b/common/components/BalanceSidebar/index.tsx @@ -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 { 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 { }, { name: 'Account Info', - content: ( - - ) + content: }, { name: 'Promos', @@ -75,13 +52,7 @@ export class BalanceSidebar extends React.Component { }, { name: 'Token Balances', - content: ( - - ) + content: }, { name: 'Equivalent Values', @@ -91,7 +62,7 @@ export class BalanceSidebar extends React.Component { tokenBalances={tokenBalances} rates={rates} ratesError={ratesError} - fetchCCRates={fetchCCRates} + fetchCCRates={this.props.fetchCCRates} /> ) } @@ -100,10 +71,7 @@ export class BalanceSidebar extends React.Component { return (