Productionize Transaction Stack (#456)
* export conditional input and hoc * Move typings and fields out of send transaction * Move fields into their own component for decoupled handling, use conditional inputs to simplify disabled components * Handle hex and non hex strings automatically in BN conversion * Fix handling of strings and numbers for BN * add web3 fixes & comments * Display short balances on deterministic modals * add more tests, fix rounding * Add spacer to balance sidebar network name * Fix tsc error * Add offline render CB * Make more render callbacks * Transform NonceField into its self contained component * Remove styling from nonce field * Better network handling in nonce cb * Move network nonce initialization to componentDidMount * Remove unessesary conditional input * Make nonce component return a BN * Simplify Query render cb * Add gas query and token query render cbs * Re-write address field component, strip out ENS name functionality for now * Add address and data as unit types * Cleanup Address Field component * Export gas query * Re-write gas field component * Cleanup gas estimation check * Re-write Data field * Transaction field skeleton * Export transaction field actions * Rename fields to map to ethtx fields * Make reducers for fields * Fix reducer module exports * Export reducer * Formatting fix * Type return of GasQuery * Add transaction field getter / setter component * Make transaction fields more flexible * Formatting fix * Split transaction fields component into two * Remove erroneous prop * Fix field naming to follow ethereum transaction fields * Merge valid prop into componeent * Change address field to be redux based * Convert nonce field to redux based * Make component for passing in current transaction * Re-write Gas component to use redux state * Reduxify data field component * seperate transaction fields redux state into field data and meta data * Rename SetTransactionFields to be singular * Make render callback components for getting/setting meta fields * Add non-zero option prop for token balance render cb * re-write unit dropdown component to be redux based * Make ether the first option * Fix tsc error on tokenquery * Handle query string default values in unit drop down * Add thunks to package * Add helper function for encoding transfer data * Handle co-dependencies between fields via thunks, seperate value fields into ether and token based * Fix wrong typing * Add token metafield as export * Start scaffolding out amount field component * Make render cbs for conditional selection of value and balance * Make render callbacks nullable * Progress commit -- get dynaming swapping between tokens and ether working * Get gas estimation working between ether and tokens * Remove nonce from breaking gas estimation * Add better validation for amount field * Add 500ms debounce to gas saga * Self contain custom message component * Add web3 awareness to wallet render cb * Add render cb for checking if wallet is unlocked * Cleanup inline typing * export available params * Add render cb to render component when a query string exists * Add boolean callback param that check that the transaction is filled by user * Remove uneeded typings from send transaction * Fix misnomer * Self contain generate transaction button * Compartmentalize more send transaction components * Add query string warning, custom message and generate tx button to fields * Cleanup send tx component with new components * export render callbacks * saga transaction scaffolding * make gas saga fully declarative * transaction lib renaming * Seperate gasprice into its own generator * Make action creators for tx sign actions * Clean up signing saga, introduce reducer for signing, make HW wallet libs compatible with new tx format, fix some typing with ethereumjs-tx * Add TransactionComparison component * Add pushTx * Progress commit -- Streamline web3 and local signing / broadcasting flows. Need to still implement reducers for broadcasting and notifications * Get local transaction broadcasting working * re-write confirmation modal to be redux based * Fix spacing and import * Move confirmation modal to be attached to send button, create send button * Properly handle broadcasting for conf modal * Handle gas cost > balance for send everything * Add signing status as its own component (#454) * Fix ledger errors not showing on notifs * Make dedicated actions for swapping from tokens to ether and ether to tokens * Split actionTypes file * Cleanup comments * Cleanup comments * Fix various tsc errors * Lay down infrastructure for saving configurations per-wallet. * Add pending and rejected states properly to token values. * Add custom token form improvements. * Fix metamask transaction errors * Fix send entire balance estimation * Fix add token form from never being enabled. * Initial pass at account tab with send and view wallet tabs. * Fix inactive tab. * Hide private key, toggling * Progress commit -- Replacing render callbacks with selectors, put validation logic in sagas * Moved the restore keystore functionality to view wallet info, and put it in a modal / util file. * Fix navigation link active * Force read only wallets to info tab. * Remove commented code * Saga-ify send everything * Scan for new tokens, track saved tokens, only request tracked tokens on initial load. * Add custom token to current wallets tracked tokens. * Rework remove token icon. * Adjust button margin * Remove the rest of the needless render callbacks for selectors, sagaify nonce * Bug fix send transaction * remove unused redux-thunk * Move fields to general components * Clean up saga structure * Refactor broadcast tx * Implement better validation logic, get contract deploy working * PR feedback. * Convert tokenbalances component to connected redux component. * Addressed feedback from Henry. * Progress commit -- Implement Interact logic, needs manual testing * Get rid of commented code * move exports after declarations * add tests, rough draft * Get contract method calls working * Bugfix contracts * Cleanup hex prefixing * Reset transaction state on wallet change * Get rid of old send transaction component * Disable sign transaction button when network request is underway * Flatten send button tree, make nonce human readable in confirmation modal * Add ghetto cost breakdown component, fix token field validation * Create Generic SubTab and use in Send * MVP of mnemonics with sub-tabs in Create Wallet view. * Do dynamic revalidation * move exports after declarations * add forgotten signing tests * update token spec * update currentValue spec * update validationHelpers spec * Address TODO - use injected history to push navigation state instead of hardcoding window.location * Use SubTabs in Contracts * Fix revertPath prop for AcceptOrRedirectModal * Use subtabs in SignAndVerifyMessage * Routing for subtabs * Fix routes, adjust sizing. * Remove unused import * Request nonce in base 10 * Add offline override to unit display * Make cost breakdown less buggy * Add non standard transaction warning * Fix amount validity * Cleanup datafield validity * Display notif on gas estimation failure * Add post-signing verification against fields, clean up gas price * Fix tsc errors * Code cleanup * add exports to functions * add specs for sendEverything and reset sagas * delete duplicate files * make tslint happy * Merge develop * Fix develop regressions * Delegate nonce pulling to wallet being set * Clarify non standard transaction * Make address a buffer to avoid leading 0's bug * Clarify validation helper comment * Increase debounce time, add console error * Better validation for non-standard transactions * Add verification skipping for broadcasting txs * Fix state and wallet resetting for contract tabs * Fix some spec files, remove contract.spec * Remove broadcasting specs from wallet * Close DeterministicWalletModal on confirm * Revert "Close DeterministicWalletModal on confirm" This reverts commit 16c860e854ca29e9de754164d8be5e24f722cbad. * Reset hardware wallet state on unlocking. Dont render walletdecrypt content when its hidden. * Fix client side broadcast checking * Add more state resetters in error scenarios * Fix gas estimation * Add validation for value transactions to contract creation * Add transaction comparaision differentiation depending on wallet type * Fix token row display balance showing twice * Properly handle failed transactions * Handle bad error messages * fix broken tests * fix broken test * Progress commit -- Implement generic subtab types * Remove react router v3 * Remove unused routes * Clean up Tabbing code, add onTabChange handler * Fix tests * Add nav fix * revert opinionated sub-tab implementation * additional reverts * Add decimal validation * Make gas price single source of truth, dont save any transaction state other than gas price * Get rid of old wallet.spec reducer tests * Add decimal validation when re-validating gasCost * remove utilities view * Remove cost breakdown * Remove local gas estimation warning * Create getShownTokenBalances selector; use in UnitDropDown and Equivalent Values * Convert reducers to switch case * Clean tsc errors * Fix failing test * fix tscheck error * Add number validation to gas field * Fix misaligned input dropdown * Revert "Fix misaligned input dropdown" This reverts commit a40a4c0e8d52471dea01e6727f741a737b798695. * Set window timeout long enough for node switch to be persisted to state * Transaction Refactor Style Fixes (#615) * Fix unit dropdown alignment by rendering it in AmountField, and fixing a missed bootstrap case. * Fix modal amount and gas text. * Fix misaligned dropdown * Update conditions for NavLink is-active class
This commit is contained in:
parent
d98389b201
commit
08d4ccbdae
|
@ -8,10 +8,8 @@ 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
|
||||
|
@ -47,13 +45,15 @@ export default class Root extends Component<Props, State> {
|
|||
<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="/pushTx" component={BroadcastTx} />
|
||||
<LegacyRoutes />
|
||||
|
@ -87,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');
|
||||
|
@ -99,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>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './actionCreators';
|
||||
export * from './actionTypes';
|
||||
export * from './constants';
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './actionCreators';
|
||||
export * from './actionTypes';
|
||||
export * from './constants';
|
||||
|
|
|
@ -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
|
||||
};
|
|
@ -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 };
|
|
@ -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
|
||||
};
|
|
@ -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';
|
|
@ -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 };
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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;
|
|
@ -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
|
||||
};
|
|
@ -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 };
|
|
@ -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
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from './actionTypes';
|
|
@ -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 };
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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 };
|
|
@ -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'
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './actionCreators';
|
||||
export * from './constants';
|
||||
export * from './actionTypes';
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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 };
|
|
@ -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);
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export * from './EnsAddress';
|
|
@ -0,0 +1 @@
|
|||
export * from './AddressField';
|
|
@ -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>
|
||||
)}
|
||||
/>
|
||||
);
|
|
@ -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 };
|
|
@ -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);
|
|
@ -0,0 +1 @@
|
|||
export * from './AmountFieldFactory';
|
|
@ -37,8 +37,7 @@ export default class AccountInfo extends React.Component<Props, State> {
|
|||
this.setAddressFromWallet();
|
||||
}
|
||||
|
||||
// TODO: don't use any;
|
||||
public toggleShowLongBalance = (e: React.SyntheticEvent<HTMLSpanElement>) => {
|
||||
public toggleShowLongBalance = (e: React.FormEvent<HTMLSpanElement>) => {
|
||||
e.preventDefault();
|
||||
this.setState(state => {
|
||||
return {
|
||||
|
@ -79,6 +78,7 @@ export default class AccountInfo extends React.Component<Props, State> {
|
|||
value={balance.wei}
|
||||
unit={'ether'}
|
||||
displayShortBalance={!showLongBalance}
|
||||
checkOffline={true}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
@ -70,7 +70,12 @@ export default class EquivalentValues extends React.Component<Props, CmpState> {
|
|||
<li className="EquivalentValues-values-currency" key={key}>
|
||||
<span className="EquivalentValues-values-currency-label">{key}:</span>{' '}
|
||||
<span className="EquivalentValues-values-currency-value">
|
||||
<UnitDisplay unit={'ether'} value={values[key]} displayShortBalance={3} />
|
||||
<UnitDisplay
|
||||
unit={'ether'}
|
||||
value={values[key]}
|
||||
displayShortBalance={3}
|
||||
checkOffline={true}
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
<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="btn btn-primary btn-sm btn-block"
|
||||
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;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,11 +60,7 @@ 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>
|
||||
);
|
||||
}
|
||||
|
@ -58,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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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
|
|
@ -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>
|
||||
);
|
|
@ -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} />
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
export * from './GasPrice';
|
|
@ -0,0 +1 @@
|
|||
export * from './AmountAndGasPrice';
|
|
@ -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 };
|
|
@ -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>
|
||||
);
|
|
@ -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);
|
|
@ -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>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
export * from './Details';
|
|
@ -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);
|
|
@ -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>
|
||||
);
|
|
@ -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>
|
||||
);
|
|
@ -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>
|
||||
);
|
|
@ -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);
|
|
@ -0,0 +1,3 @@
|
|||
export * from './SummaryTo';
|
||||
export * from './SummaryFrom';
|
||||
export * from './SummaryAmount';
|
|
@ -0,0 +1 @@
|
|||
export * from './Summary';
|
|
@ -0,0 +1,2 @@
|
|||
export * from './Details';
|
||||
export * from './Summary';
|
|
@ -0,0 +1 @@
|
|||
export * from './ConfirmationModal';
|
|
@ -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);
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export * from './CustomMessage';
|
||||
export * from './messages';
|
|
@ -1,4 +1,4 @@
|
|||
export default [
|
||||
export const messages = [
|
||||
{
|
||||
// donation address example
|
||||
to: '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8',
|
|
@ -0,0 +1 @@
|
|||
export * from './CurrentCustomMessage';
|
|
@ -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>
|
||||
)}
|
||||
/>
|
||||
);
|
|
@ -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 };
|
|
@ -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);
|
|
@ -0,0 +1 @@
|
|||
export * from './DataFieldFactory';
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import { GasFieldFactory } from './GasFieldFactory';
|
||||
import translate from 'translations';
|
||||
import { Aux } from 'components/ui';
|
||||
|
||||
export const GasField: React.SFC<{}> = () => (
|
||||
<Aux>
|
||||
<label>{translate('TRANS_gas')} </label>
|
||||
<GasFieldFactory
|
||||
withProps={({ gasLimit: { raw, value }, onChange, readOnly }) => (
|
||||
<input
|
||||
className={`form-control ${!!value ? 'is-valid' : 'is-invalid'}`}
|
||||
type="text"
|
||||
readOnly={!!readOnly}
|
||||
value={raw}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Aux>
|
||||
);
|
|
@ -0,0 +1,57 @@
|
|||
import React, { Component } from 'react';
|
||||
import { GasQuery } from 'components/renderCbs';
|
||||
import { GasInput } from './GasInputFactory';
|
||||
import { inputGasLimit, TInputGasLimit } from 'actions/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
|
||||
const defaultGasLimit = '21000';
|
||||
|
||||
export interface CallBackProps {
|
||||
readOnly: boolean;
|
||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
||||
onChange(value: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
inputGasLimit: TInputGasLimit;
|
||||
}
|
||||
interface OwnProps {
|
||||
gasLimit: string | null;
|
||||
withProps(props: CallBackProps);
|
||||
}
|
||||
|
||||
type Props = DispatchProps & OwnProps;
|
||||
|
||||
class GasLimitFieldClass extends Component<Props, {}> {
|
||||
public componentDidMount() {
|
||||
const { gasLimit } = this.props;
|
||||
if (gasLimit) {
|
||||
this.props.inputGasLimit(gasLimit);
|
||||
} else {
|
||||
this.props.inputGasLimit(defaultGasLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return <GasInput onChange={this.setGas} withProps={this.props.withProps} />;
|
||||
}
|
||||
|
||||
private setGas = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const { value } = ev.currentTarget;
|
||||
this.props.inputGasLimit(value);
|
||||
};
|
||||
}
|
||||
|
||||
const GasLimitField = connect(null, { inputGasLimit })(GasLimitFieldClass);
|
||||
|
||||
interface DefaultGasFieldProps {
|
||||
withProps(props: CallBackProps);
|
||||
}
|
||||
const DefaultGasField: React.SFC<DefaultGasFieldProps> = ({ withProps }) => (
|
||||
<GasQuery
|
||||
withQuery={({ gasLimit }) => <GasLimitField gasLimit={gasLimit} withProps={withProps} />}
|
||||
/>
|
||||
);
|
||||
|
||||
export { DefaultGasField as GasFieldFactory };
|
|
@ -0,0 +1,34 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { getGasLimit } from 'selectors/transaction';
|
||||
import { CallBackProps } from 'components/GasFieldFactory';
|
||||
|
||||
interface StateProps {
|
||||
gasLimit: AppState['transaction']['fields']['gasLimit'];
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
withProps(props: CallBackProps);
|
||||
onChange(value: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
type Props = StateProps & OwnProps;
|
||||
class GasInputClass extends Component<Props> {
|
||||
public render() {
|
||||
const { gasLimit, onChange } = this.props;
|
||||
return (
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) =>
|
||||
this.props.withProps({ gasLimit, onChange, readOnly: !!readOnly })
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const GasInput = connect((state: AppState) => ({ gasLimit: getGasLimit(state) }))(
|
||||
GasInputClass
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
export * from './GasFieldFactory';
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
isValid: boolean;
|
||||
isVisible: boolean;
|
||||
name: string;
|
||||
value: string;
|
||||
placeholder: string;
|
||||
disabled?: boolean;
|
||||
handleInput(e: React.FormEvent<HTMLInputElement>): void;
|
||||
handleToggle(): void;
|
||||
}
|
||||
|
||||
const KeystoreInput: React.SFC<Props> = ({
|
||||
isValid,
|
||||
isVisible,
|
||||
handleInput,
|
||||
name,
|
||||
value,
|
||||
placeholder,
|
||||
disabled,
|
||||
handleToggle
|
||||
}) => (
|
||||
<div className="input-group">
|
||||
<input
|
||||
className={classnames('form-control', isValid ? 'is-valid' : 'is-invalid')}
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={handleInput}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span onClick={handleToggle} role="button" className="input-group-addon eye" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default KeystoreInput;
|
|
@ -0,0 +1,18 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.GenKeystore {
|
||||
max-width: 40rem;
|
||||
|
||||
&-field {
|
||||
margin-bottom: $space;
|
||||
|
||||
&-label {
|
||||
margin: 0 0 $space * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&-button {
|
||||
max-width: 16rem;
|
||||
margin: $space auto 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
import React from 'react';
|
||||
import { generateKeystoreFileInfo, KeystoreFile } from 'utils/keystore';
|
||||
import Modal from 'components/ui/Modal';
|
||||
import Input from './Input';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { MINIMUM_PASSWORD_LENGTH } from 'config/data';
|
||||
import { isValidPrivKey } from 'libs/validators';
|
||||
import './index.scss';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
privateKey?: string;
|
||||
handleClose(): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
privateKey: string;
|
||||
password: string;
|
||||
isPrivateKeyVisible: boolean;
|
||||
isPasswordVisible: boolean;
|
||||
keystoreFile: KeystoreFile | null;
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
privateKey: '',
|
||||
password: '',
|
||||
isPrivateKeyVisible: false,
|
||||
isPasswordVisible: false,
|
||||
keystoreFile: null,
|
||||
hasError: false
|
||||
};
|
||||
|
||||
export default class GenerateKeystoreModal extends React.Component<Props, State> {
|
||||
public state: State = initialState;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
if (props.privateKey) {
|
||||
this.state = {
|
||||
...this.state,
|
||||
privateKey: props.privateKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.privateKey !== this.props.privateKey) {
|
||||
this.setState({ privateKey: nextProps.privateKey });
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
privateKey,
|
||||
password,
|
||||
isPrivateKeyVisible,
|
||||
isPasswordVisible,
|
||||
keystoreFile,
|
||||
hasError
|
||||
} = this.state;
|
||||
|
||||
const isPrivateKeyValid = isValidPrivKey(privateKey);
|
||||
const isPasswordValid = password.length >= MINIMUM_PASSWORD_LENGTH;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={translate('Generate Keystore File')}
|
||||
isOpen={this.props.isOpen}
|
||||
handleClose={this.handleClose}
|
||||
>
|
||||
<form className="GenKeystore" onSubmit={this.handleSubmit}>
|
||||
<label className="GenKeystore-field">
|
||||
<h4 className="GenKeystore-field-label">Private Key</h4>
|
||||
<Input
|
||||
isValid={isPrivateKeyValid}
|
||||
isVisible={isPrivateKeyVisible}
|
||||
name="privateKey"
|
||||
value={privateKey}
|
||||
handleInput={this.handleInput}
|
||||
placeholder="f1d0e0789c6d40f39..."
|
||||
handleToggle={this.togglePrivateKey}
|
||||
disabled={!!this.props.privateKey}
|
||||
/>
|
||||
</label>
|
||||
<label className="GenKeystore-field">
|
||||
<h4 className="GenKeystore-field-label">Password</h4>
|
||||
<Input
|
||||
isValid={isPasswordValid}
|
||||
isVisible={isPasswordVisible}
|
||||
name="password"
|
||||
value={password}
|
||||
placeholder={translateRaw('Minimum 9 characters')}
|
||||
handleInput={this.handleInput}
|
||||
handleToggle={this.togglePassword}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{!keystoreFile ? (
|
||||
<button
|
||||
className="GenKeystore-button btn btn-primary btn-block"
|
||||
disabled={!isPrivateKeyValid || !isPasswordValid}
|
||||
>
|
||||
{translate('Generate Keystore File')}
|
||||
</button>
|
||||
) : hasError ? (
|
||||
<p className="alert alert-danger">
|
||||
Keystore generation failed or was invalid. In order to prevent loss of funds, we
|
||||
cannot provide you with a keystore file that may be corrupted. Refresh the page or use
|
||||
a different browser, and try again.
|
||||
</p>
|
||||
) : (
|
||||
<a
|
||||
onClick={this.handleClose}
|
||||
href={keystoreFile.blob}
|
||||
className="GenKeystore-button btn btn-success btn-block"
|
||||
aria-label={translateRaw('x_Keystore')}
|
||||
aria-describedby={translateRaw('x_KeystoreDesc')}
|
||||
download={keystoreFile.filename}
|
||||
>
|
||||
{translate('Download Keystore File')}
|
||||
</a>
|
||||
)}
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private togglePrivateKey = () => {
|
||||
this.setState({
|
||||
isPrivateKeyVisible: !this.state.isPrivateKeyVisible
|
||||
});
|
||||
};
|
||||
|
||||
private togglePassword = () => {
|
||||
this.setState({
|
||||
isPasswordVisible: !this.state.isPasswordVisible
|
||||
});
|
||||
};
|
||||
|
||||
private handleInput = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.currentTarget;
|
||||
let { keystoreFile } = this.state;
|
||||
if (name === 'privateKey') {
|
||||
keystoreFile = null;
|
||||
}
|
||||
this.setState({ [name as any]: value, keystoreFile });
|
||||
};
|
||||
|
||||
private handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (!this.state.keystoreFile) {
|
||||
this.generateKeystoreFileInfo();
|
||||
}
|
||||
};
|
||||
|
||||
private generateKeystoreFileInfo = async () => {
|
||||
try {
|
||||
const { privateKey, password } = this.state;
|
||||
const keystoreFile = await generateKeystoreFileInfo(privateKey, password);
|
||||
if (keystoreFile) {
|
||||
this.setState({ keystoreFile });
|
||||
}
|
||||
} catch (err) {
|
||||
this.setState({ hasError: true });
|
||||
}
|
||||
};
|
||||
|
||||
private handleClose = () => {
|
||||
this.setState({
|
||||
...initialState,
|
||||
privateKey: this.props.privateKey || initialState.privateKey
|
||||
});
|
||||
this.props.handleClose();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import {
|
||||
signLocalTransactionRequested,
|
||||
signWeb3TransactionRequested,
|
||||
TSignLocalTransactionRequested,
|
||||
TSignWeb3TransactionRequested,
|
||||
SignLocalTransactionRequestedAction,
|
||||
SignWeb3TransactionRequestedAction
|
||||
} from 'actions/transaction';
|
||||
import React, { Component } from 'react';
|
||||
import { connect, Dispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { AppState } from 'reducers';
|
||||
type Payload =
|
||||
| SignLocalTransactionRequestedAction['payload']
|
||||
| SignWeb3TransactionRequestedAction['payload'];
|
||||
type Signer = (
|
||||
payload: Payload
|
||||
) => () => SignLocalTransactionRequestedAction | SignWeb3TransactionRequestedAction;
|
||||
|
||||
interface DispatchProps {
|
||||
signer: TSignLocalTransactionRequested | TSignWeb3TransactionRequested;
|
||||
}
|
||||
interface Props {
|
||||
isWeb3: boolean;
|
||||
withSigner(signer: Signer): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
class Container extends Component<DispatchProps & Props, {}> {
|
||||
public render() {
|
||||
return this.props.withSigner(this.sign);
|
||||
}
|
||||
|
||||
private sign = (payload: Payload) => () => this.props.signer(payload);
|
||||
}
|
||||
|
||||
export const WithSigner = connect(null, (dispatch: Dispatch<AppState>, ownProps: Props) => {
|
||||
return bindActionCreators(
|
||||
{
|
||||
signer: ownProps.isWeb3 ? signWeb3TransactionRequested : signLocalTransactionRequested
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
})(Container);
|
|
@ -0,0 +1,41 @@
|
|||
import translate from 'translations';
|
||||
import { WithSigner } from './Container';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { getTransaction, isNetworkRequestPending } from 'selectors/transaction';
|
||||
import { getWalletType } from 'selectors/wallet';
|
||||
|
||||
interface StateProps {
|
||||
transaction: EthTx;
|
||||
networkRequestPending: boolean;
|
||||
isFullTransaction: boolean;
|
||||
isWeb3Wallet: boolean;
|
||||
}
|
||||
|
||||
class GenerateTransactionClass extends Component<StateProps> {
|
||||
public render() {
|
||||
const { isFullTransaction, isWeb3Wallet, transaction, networkRequestPending } = this.props;
|
||||
return (
|
||||
<WithSigner
|
||||
isWeb3={isWeb3Wallet}
|
||||
withSigner={signer => (
|
||||
<button
|
||||
disabled={!isFullTransaction || networkRequestPending}
|
||||
className="btn btn-info btn-block"
|
||||
onClick={signer(transaction)}
|
||||
>
|
||||
{isWeb3Wallet ? translate('Send to MetaMask / Mist') : translate('DEP_signtx')}
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const GenerateTransaction = connect((state: AppState) => ({
|
||||
...getTransaction(state),
|
||||
networkRequestPending: isNetworkRequestPending(state),
|
||||
isWeb3Wallet: getWalletType(state).isWeb3Wallet
|
||||
}))(GenerateTransactionClass);
|
|
@ -0,0 +1 @@
|
|||
export * from './GenerateTransaction';
|
|
@ -3,13 +3,15 @@ import throttle from 'lodash/throttle';
|
|||
import React, { Component } from 'react';
|
||||
import DropdownShell from 'components/ui/DropdownShell';
|
||||
import './GasPriceDropdown.scss';
|
||||
import { SetGasLimitFieldAction } from 'actions/transaction';
|
||||
import { gasPricetoBase } from 'libs/units';
|
||||
|
||||
interface Props {
|
||||
value: number;
|
||||
onChange(gasPrice: number): void;
|
||||
value: string;
|
||||
onChange(payload: SetGasLimitFieldAction['payload']): void;
|
||||
}
|
||||
|
||||
export default class GasPriceDropdown extends Component<Props, {}> {
|
||||
export default class GasPriceDropdown extends Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.updateGasPrice = throttle(this.updateGasPrice, 50);
|
||||
|
@ -69,10 +71,10 @@ export default class GasPriceDropdown extends Component<Props, {}> {
|
|||
};
|
||||
|
||||
private updateGasPrice = (value: string) => {
|
||||
this.props.onChange(parseInt(value, 10));
|
||||
this.props.onChange({ raw: value, value: gasPricetoBase(parseInt(value, 10)) });
|
||||
};
|
||||
|
||||
private handleGasPriceChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
this.updateGasPrice((e.target as HTMLInputElement).value);
|
||||
private handleGasPriceChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
this.updateGasPrice(e.currentTarget.value);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,36 +11,28 @@ const tabs = [
|
|||
},
|
||||
|
||||
{
|
||||
name: 'NAV_SendEther',
|
||||
to: 'send-transaction'
|
||||
name: 'Account View & Send',
|
||||
to: '/account'
|
||||
},
|
||||
{
|
||||
name: 'NAV_Swap',
|
||||
to: 'swap'
|
||||
},
|
||||
{
|
||||
name: 'NAV_ViewWallet'
|
||||
// to: 'view-wallet'
|
||||
to: '/swap'
|
||||
},
|
||||
{
|
||||
name: 'NAV_Contracts',
|
||||
to: 'contracts'
|
||||
to: '/contracts'
|
||||
},
|
||||
{
|
||||
name: 'NAV_ENS',
|
||||
to: 'ens'
|
||||
to: '/ens'
|
||||
},
|
||||
{
|
||||
name: 'Sign & Verify Message',
|
||||
to: 'sign-and-verify-message'
|
||||
to: '/sign-and-verify-message'
|
||||
},
|
||||
{
|
||||
name: 'Broadcast Transaction',
|
||||
to: 'pushTx'
|
||||
},
|
||||
{
|
||||
name: 'NAV_Utilities',
|
||||
to: 'utilities'
|
||||
to: '/pushTx'
|
||||
},
|
||||
{
|
||||
name: 'NAV_Help',
|
||||
|
|
|
@ -26,28 +26,17 @@ class NavigationLink extends React.Component<Props, {}> {
|
|||
const linkClasses = classnames({
|
||||
'NavigationLink-link': true,
|
||||
'is-disabled': !link.to,
|
||||
'is-active':
|
||||
location.pathname === link.to ||
|
||||
location.pathname.substring(1) === link.to
|
||||
'is-active': location.pathname === link.to
|
||||
});
|
||||
const linkLabel = `nav item: ${translateRaw(link.name)}`;
|
||||
|
||||
const linkEl =
|
||||
link.external || !link.to ? (
|
||||
<a
|
||||
className={linkClasses}
|
||||
href={link.to}
|
||||
aria-label={linkLabel}
|
||||
target="_blank"
|
||||
>
|
||||
<a className={linkClasses} href={link.to} aria-label={linkLabel} target="_blank">
|
||||
{translate(link.name)}
|
||||
</a>
|
||||
) : (
|
||||
<Link
|
||||
className={linkClasses}
|
||||
to={(link as any).to}
|
||||
aria-label={linkLabel}
|
||||
>
|
||||
<Link className={linkClasses} to={(link as any).to} aria-label={linkLabel}>
|
||||
{translate(link.name)}
|
||||
</Link>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
TChangeGasPrice,
|
||||
TChangeLanguage,
|
||||
TChangeNodeIntent,
|
||||
TAddCustomNode,
|
||||
|
@ -11,6 +10,7 @@ import { Dropdown, ColorDropdown } from 'components/ui';
|
|||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { TSetGasPriceField } from 'actions/transaction';
|
||||
import {
|
||||
ANNOUNCEMENT_MESSAGE,
|
||||
ANNOUNCEMENT_TYPE,
|
||||
|
@ -28,19 +28,19 @@ import { getKeyByValue } from 'utils/helpers';
|
|||
import { makeCustomNodeId } from 'utils/node';
|
||||
import { getNetworkConfigFromId } from 'utils/network';
|
||||
import './index.scss';
|
||||
import { AppState } from 'reducers';
|
||||
|
||||
interface Props {
|
||||
languageSelection: string;
|
||||
node: NodeConfig;
|
||||
nodeSelection: string;
|
||||
isChangingNode: boolean;
|
||||
gasPriceGwei: number;
|
||||
gasPrice: AppState['transaction']['fields']['gasPrice'];
|
||||
customNodes: CustomNodeConfig[];
|
||||
customNetworks: CustomNetworkConfig[];
|
||||
|
||||
changeLanguage: TChangeLanguage;
|
||||
changeNodeIntent: TChangeNodeIntent;
|
||||
changeGasPrice: TChangeGasPrice;
|
||||
setGasPriceField: TSetGasPriceField;
|
||||
addCustomNode: TAddCustomNode;
|
||||
removeCustomNode: TRemoveCustomNode;
|
||||
addCustomNetwork: TAddCustomNetwork;
|
||||
|
@ -129,8 +129,8 @@ export default class Header extends Component<Props, State> {
|
|||
|
||||
<div className="Header-branding-right-dropdown">
|
||||
<GasPriceDropdown
|
||||
value={this.props.gasPriceGwei}
|
||||
onChange={this.props.changeGasPrice}
|
||||
value={this.props.gasPrice.raw}
|
||||
onChange={this.props.setGasPriceField}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { NonceInput } from './NonceInput';
|
||||
import { inputNonce, TInputNonce } from 'actions/transaction';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface DispatchProps {
|
||||
inputNonce: TInputNonce;
|
||||
}
|
||||
|
||||
class NonceFieldClass extends Component<DispatchProps> {
|
||||
public render() {
|
||||
return <NonceInput onChange={this.setNonce} />;
|
||||
}
|
||||
|
||||
private setNonce = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
const { value } = ev.currentTarget;
|
||||
this.props.inputNonce(value);
|
||||
};
|
||||
}
|
||||
|
||||
export const NonceField = connect(null, {
|
||||
inputNonce
|
||||
})(NonceFieldClass);
|
|
@ -0,0 +1,55 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Aux } from 'components/ui';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import Help from 'components/ui/Help';
|
||||
import { getNonce, nonceRequestFailed } from 'selectors/transaction';
|
||||
import { isAnyOffline } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { connect } from 'react-redux';
|
||||
const nonceHelp = (
|
||||
<Help
|
||||
size={'x1'}
|
||||
link={'https://myetherwallet.github.io/knowledge-base/transactions/what-is-nonce.html'}
|
||||
/>
|
||||
);
|
||||
|
||||
interface OwnProps {
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
interface StateProps {
|
||||
shouldDisplay: boolean;
|
||||
nonce: AppState['transaction']['fields']['nonce'];
|
||||
}
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
class NonceInputClass extends Component<Props> {
|
||||
public render() {
|
||||
const { nonce: { raw, value }, onChange, shouldDisplay } = this.props;
|
||||
const content = (
|
||||
<Aux>
|
||||
{nonceHelp}
|
||||
<label>Nonce</label>
|
||||
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) => (
|
||||
<input
|
||||
className={`form-control ${!!value ? 'is-valid' : 'is-invalid'}`}
|
||||
type="text"
|
||||
value={raw}
|
||||
readOnly={!!readOnly}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Aux>
|
||||
);
|
||||
|
||||
return shouldDisplay ? content : null;
|
||||
}
|
||||
}
|
||||
|
||||
export const NonceInput = connect((state: AppState) => ({
|
||||
shouldDisplay: isAnyOffline(state) || nonceRequestFailed(state),
|
||||
nonce: getNonce(state)
|
||||
}))(NonceInputClass);
|
|
@ -0,0 +1 @@
|
|||
export * from './NonceField';
|
|
@ -0,0 +1,36 @@
|
|||
import { UnlockHeader } from 'components/ui';
|
||||
import React, { Component } from 'react';
|
||||
import translate from 'translations';
|
||||
import { isAnyOffline } from 'selectors/config';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
|
||||
interface Props {
|
||||
allowReadOnly: boolean;
|
||||
}
|
||||
export const OfflineAwareUnlockHeader: React.SFC<Props> = ({ allowReadOnly }) => (
|
||||
<UnlockHeader title={<Title />} allowReadOnly={allowReadOnly} />
|
||||
);
|
||||
|
||||
interface StateProps {
|
||||
shouldDisplayOffline: boolean;
|
||||
}
|
||||
|
||||
class TitleClass extends Component<StateProps> {
|
||||
public render() {
|
||||
const { shouldDisplayOffline } = this.props;
|
||||
const offlineTitle = shouldDisplayOffline ? (
|
||||
<span style={{ color: 'red' }}> (Offline)</span>
|
||||
) : null;
|
||||
return (
|
||||
<div>
|
||||
{translate('Account')}
|
||||
{offlineTitle}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Title = connect((state: AppState) => ({
|
||||
shouldDisplayOffline: isAnyOffline(state)
|
||||
}))(TitleClass);
|
|
@ -5,7 +5,7 @@ import translate from 'translations';
|
|||
import printElement from 'utils/printElement';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
|
||||
const print = (address: string, privateKey: string) => () =>
|
||||
export const print = (address: string, privateKey: string) => () =>
|
||||
address &&
|
||||
privateKey &&
|
||||
printElement(<PaperWallet address={address} privateKey={privateKey} />, {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
import { SendButtonFactory } from './SendButtonFactory';
|
||||
import translate from 'translations';
|
||||
|
||||
export const SendButton: React.SFC<{}> = () => (
|
||||
<SendButtonFactory
|
||||
withProps={({ onClick }) => (
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-12">
|
||||
<button className="btn btn-primary btn-block" onClick={onClick}>
|
||||
{translate('SEND_trans')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue