diff --git a/common/actions/wallet/actionCreators.ts b/common/actions/wallet/actionCreators.ts index e104d779..e078772e 100644 --- a/common/actions/wallet/actionCreators.ts +++ b/common/actions/wallet/actionCreators.ts @@ -47,14 +47,28 @@ export function setWallet(value: IWallet): types.SetWalletAction { }; } -export type TSetBalance = typeof setBalance; -export function setBalance(value: Wei): types.SetBalanceAction { +export function setBalancePending(): types.SetBalancePendingAction { return { - type: TypeKeys.WALLET_SET_BALANCE, + type: TypeKeys.WALLET_SET_BALANCE_PENDING + }; +} + +export type TSetBalance = typeof setBalanceFullfilled; +export function setBalanceFullfilled( + value: Wei +): types.SetBalanceFullfilledAction { + return { + type: TypeKeys.WALLET_SET_BALANCE_FULFILLED, payload: value }; } +export function setBalanceRejected(): types.SetBalanceRejectedAction { + return { + type: TypeKeys.WALLET_SET_BALANCE_REJECTED + }; +} + export type TSetTokenBalances = typeof setTokenBalances; export function setTokenBalances(payload: { [key: string]: TokenValue; diff --git a/common/actions/wallet/actionTypes.ts b/common/actions/wallet/actionTypes.ts index f44c280e..1eb64b50 100644 --- a/common/actions/wallet/actionTypes.ts +++ b/common/actions/wallet/actionTypes.ts @@ -33,10 +33,16 @@ export interface ResetWalletAction { } /*** Set Balance ***/ -export interface SetBalanceAction { - type: TypeKeys.WALLET_SET_BALANCE; +export interface SetBalancePendingAction { + type: TypeKeys.WALLET_SET_BALANCE_PENDING; +} +export interface SetBalanceFullfilledAction { + type: TypeKeys.WALLET_SET_BALANCE_FULFILLED; payload: Wei; } +export interface SetBalanceRejectedAction { + type: TypeKeys.WALLET_SET_BALANCE_REJECTED; +} /*** Set Token Balance ***/ export interface SetTokenBalancesAction { @@ -94,7 +100,9 @@ export type WalletAction = | UnlockPrivateKeyAction | SetWalletAction | ResetWalletAction - | SetBalanceAction + | SetBalancePendingAction + | SetBalanceFullfilledAction + | SetBalanceRejectedAction | SetTokenBalancesAction | BroadcastTxRequestedAction | BroadcastTxFailedAction diff --git a/common/actions/wallet/constants.ts b/common/actions/wallet/constants.ts index 0d5e914a..8ccb8e5d 100644 --- a/common/actions/wallet/constants.ts +++ b/common/actions/wallet/constants.ts @@ -4,7 +4,9 @@ export enum TypeKeys { WALLET_UNLOCK_MNEMONIC = 'WALLET_UNLOCK_MNEMONIC', WALLET_UNLOCK_WEB3 = 'WALLET_UNLOCK_WEB3', WALLET_SET = 'WALLET_SET', - WALLET_SET_BALANCE = 'WALLET_SET_BALANCE', + 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', diff --git a/common/components/BalanceSidebar/AccountInfo.tsx b/common/components/BalanceSidebar/AccountInfo.tsx index 757474bb..5f94d5e1 100644 --- a/common/components/BalanceSidebar/AccountInfo.tsx +++ b/common/components/BalanceSidebar/AccountInfo.tsx @@ -1,14 +1,14 @@ import { TFetchCCRates } from 'actions/rates'; import { Identicon, UnitDisplay } from 'components/ui'; import { NetworkConfig } from 'config/data'; -import { IWallet } from 'libs/wallet'; -import { Wei } from 'libs/units'; +import { IWallet, Balance } from 'libs/wallet'; import React from 'react'; import translate from 'translations'; import './AccountInfo.scss'; +import Spinner from 'components/ui/Spinner'; interface Props { - balance: Wei; + balance: Balance; wallet: IWallet; network: NetworkConfig; fetchCCRates: TFetchCCRates; @@ -79,13 +79,17 @@ export default class AccountInfo extends React.Component { className="AccountInfo-list-item-clickable mono wrap" onClick={this.toggleShowLongBalance} > - + {balance.isPending ? ( + + ) : ( + + )} - {` ${network.name}`} + {balance ? `${network.name}` : null} diff --git a/common/components/BalanceSidebar/EquivalentValues.scss b/common/components/BalanceSidebar/EquivalentValues.scss index 6b54c226..9b47e328 100644 --- a/common/components/BalanceSidebar/EquivalentValues.scss +++ b/common/components/BalanceSidebar/EquivalentValues.scss @@ -1,5 +1,5 @@ -@import "common/sass/variables"; -@import "common/sass/mixins"; +@import 'common/sass/variables'; +@import 'common/sass/mixins'; .EquivalentValues { &-title { @@ -25,6 +25,7 @@ } &-label { + white-space: pre-wrap; display: inline-block; min-width: 36px; } diff --git a/common/components/BalanceSidebar/EquivalentValues.tsx b/common/components/BalanceSidebar/EquivalentValues.tsx index 865a238b..c8053a51 100644 --- a/common/components/BalanceSidebar/EquivalentValues.tsx +++ b/common/components/BalanceSidebar/EquivalentValues.tsx @@ -1,13 +1,14 @@ -import { Wei } from 'libs/units'; import React from 'react'; import translate from 'translations'; import './EquivalentValues.scss'; import { State } from 'reducers/rates'; import { symbols } from 'actions/rates'; import { UnitDisplay } from 'components/ui'; +import { Balance } from 'libs/wallet'; +import Spinner from 'components/ui/Spinner'; interface Props { - balance?: Wei; + balance: Balance; rates?: State['rates']; ratesError?: State['ratesError']; } @@ -29,18 +30,19 @@ export default class EquivalentValues extends React.Component { return (
  • - {key}: + {key + ': '} - {' '} - {balance ? ( + {balance.isPending ? ( + + ) : ( - ) : ( - '???' )}
  • diff --git a/common/components/BalanceSidebar/index.tsx b/common/components/BalanceSidebar/index.tsx index 5098fedf..06c8054b 100644 --- a/common/components/BalanceSidebar/index.tsx +++ b/common/components/BalanceSidebar/index.tsx @@ -7,8 +7,7 @@ import { import { showNotification, TShowNotification } from 'actions/notifications'; import { fetchCCRates as dFetchCCRates, TFetchCCRates } from 'actions/rates'; import { NetworkConfig } from 'config/data'; -import { Wei } from 'libs/units'; -import { IWallet } from 'libs/wallet/IWallet'; +import { IWallet, Balance } from 'libs/wallet'; import React from 'react'; import { connect } from 'react-redux'; import { AppState } from 'reducers'; @@ -27,7 +26,7 @@ import OfflineToggle from './OfflineToggle'; interface Props { wallet: IWallet; - balance: Wei; + balance: Balance; network: NetworkConfig; tokenBalances: TokenBalance[]; rates: State['rates']; diff --git a/common/components/ui/Spinner.scss b/common/components/ui/Spinner.scss new file mode 100644 index 00000000..9c5aa284 --- /dev/null +++ b/common/components/ui/Spinner.scss @@ -0,0 +1,66 @@ +.Spinner { + animation: rotate 2s linear infinite; + + &-x1 { + height: 1em; + width: 1em; + } + + &-x2 { + height: 2em; + width: 2em; + } + + &-x3 { + height: 3em; + width: 3em; + } + + &-x4 { + height: 4em; + width: 4em; + } + + &-x5 { + height: 5em; + width: 5em; + } + + & .path { + stroke-linecap: round; + animation: dash 1.5s ease-in-out infinite; + } + + &-light { + & .path { + stroke: white; + } + } + + &-dark { + & .path { + stroke: #163151; + } + } +} + +@keyframes rotate { + 100% { + transform: rotate(360deg); + } +} + +@keyframes dash { + 0% { + stroke-dasharray: 1, 150; + stroke-dashoffset: 0; + } + 50% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -35; + } + 100% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -124; + } +} diff --git a/common/components/ui/Spinner.tsx b/common/components/ui/Spinner.tsx index 82d3adfe..d1ef0a78 100644 --- a/common/components/ui/Spinner.tsx +++ b/common/components/ui/Spinner.tsx @@ -1,13 +1,27 @@ import React from 'react'; +import './Spinner.scss'; -type Size = 'lg' | '2x' | '3x' | '4x' | '5x'; +type Size = 'x1' | 'x2' | 'x3' | 'x4' | 'x5'; interface SpinnerProps { size?: Size; + light?: boolean; } -const Spinner = ({ size = 'fa-' }: SpinnerProps) => { - return ; +const Spinner = ({ size = 'x1', light = false }: SpinnerProps) => { + const color = light ? 'Spinner-light' : 'Spinner-dark'; + return ( + + + + ); }; export default Spinner; diff --git a/common/components/ui/UnitDisplay.tsx b/common/components/ui/UnitDisplay.tsx index f4f56f48..1bce8bb0 100644 --- a/common/components/ui/UnitDisplay.tsx +++ b/common/components/ui/UnitDisplay.tsx @@ -14,7 +14,7 @@ interface Props { * @type {TokenValue | Wei} * @memberof Props */ - value?: TokenValue | Wei; + value?: TokenValue | Wei | null; /** * @description Symbol to display to the right of the value, such as 'ETH' * @type {string} @@ -43,7 +43,7 @@ const UnitDisplay: React.SFC = params => { const { value, symbol, displayShortBalance } = params; if (!value) { - return ???; + return Balance isn't available offline; } const convertedValue = isEthereumUnit(params) diff --git a/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/types.ts b/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/types.ts index d0ca0bcc..218c111b 100644 --- a/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/types.ts +++ b/common/containers/Tabs/Contracts/components/Deploy/components/DeployHoc/types.ts @@ -1,5 +1,5 @@ import { Wei } from 'libs/units'; -import { IWallet } from 'libs/wallet/IWallet'; +import { IWallet, Balance } from 'libs/wallet'; import { RPCNode } from 'libs/nodes'; import { NodeConfig, NetworkConfig } from 'config/data'; import { TBroadcastTx } from 'actions/wallet'; @@ -7,7 +7,7 @@ import { TShowNotification } from 'actions/notifications'; export interface Props { wallet: IWallet; - balance: Wei; + balance: Balance; node: NodeConfig; nodeLib: RPCNode; chainId: NetworkConfig['chainId']; diff --git a/common/containers/Tabs/Contracts/components/withTx.tsx b/common/containers/Tabs/Contracts/components/withTx.tsx index fc097d3c..b3718805 100644 --- a/common/containers/Tabs/Contracts/components/withTx.tsx +++ b/common/containers/Tabs/Contracts/components/withTx.tsx @@ -4,13 +4,13 @@ import { toWei, Wei, getDecimal } from 'libs/units'; import { connect } from 'react-redux'; import { showNotification, TShowNotification } from 'actions/notifications'; import { broadcastTx, TBroadcastTx } from 'actions/wallet'; -import { IWallet } from 'libs/wallet/IWallet'; +import { IWallet, Balance } from 'libs/wallet'; import { RPCNode } from 'libs/nodes'; import { NodeConfig, NetworkConfig } from 'config/data'; export interface IWithTx { wallet: IWallet; - balance: Wei; + balance: Balance; node: NodeConfig; nodeLib: RPCNode; chainId: NetworkConfig['chainId']; diff --git a/common/containers/Tabs/SendTransaction/components/AmountField.tsx b/common/containers/Tabs/SendTransaction/components/AmountField.tsx index cbefaab4..759cee86 100644 --- a/common/containers/Tabs/SendTransaction/components/AmountField.tsx +++ b/common/containers/Tabs/SendTransaction/components/AmountField.tsx @@ -1,13 +1,13 @@ import React from 'react'; import translate, { translateRaw } from 'translations'; import UnitDropdown from './UnitDropdown'; -import { Wei } from 'libs/units'; +import { Balance } from 'libs/wallet'; import { UnitConverter } from 'components/renderCbs'; interface Props { decimal: number; unit: string; tokens: string[]; - balance: number | null | Wei; + balance: number | null | Balance; isReadOnly: boolean; onAmountChange(value: string, unit: string): void; onUnitChange(unit: string): void; @@ -30,10 +30,11 @@ export default class AmountField extends React.Component { {({ onUserInput, convertedUnit }) => ( 0 - ? 'is-valid' - : 'is-invalid'}`} + className={`form-control ${ + isFinite(Number(convertedUnit)) && Number(convertedUnit) > 0 + ? 'is-valid' + : 'is-invalid' + }`} type="text" placeholder={translateRaw('SEND_amount_short')} value={convertedUnit} diff --git a/common/containers/Tabs/SendTransaction/components/ConfirmationModal.tsx b/common/containers/Tabs/SendTransaction/components/ConfirmationModal.tsx index 2c7b1cb0..be33f7d7 100644 --- a/common/containers/Tabs/SendTransaction/components/ConfirmationModal.tsx +++ b/common/containers/Tabs/SendTransaction/components/ConfirmationModal.tsx @@ -115,7 +115,7 @@ class ConfirmationModal extends React.Component {
    {isBroadcasting ? (
    - +
    ) : (
    diff --git a/common/containers/Tabs/SendTransaction/index.tsx b/common/containers/Tabs/SendTransaction/index.tsx index 9212bc44..945ae57e 100644 --- a/common/containers/Tabs/SendTransaction/index.tsx +++ b/common/containers/Tabs/SendTransaction/index.tsx @@ -32,7 +32,7 @@ import { import { UnitKey, Wei, getDecimal, toWei } from 'libs/units'; import { isValidETHAddress } from 'libs/validators'; // LIBS -import { IWallet, Web3Wallet } from 'libs/wallet'; +import { IWallet, Balance, Web3Wallet } from 'libs/wallet'; import pickBy from 'lodash/pickBy'; import React from 'react'; // REDUX @@ -92,7 +92,7 @@ interface State { interface Props { wallet: IWallet; - balance: Wei; + balance: Balance; nodeLib: RPCNode; network: NetworkConfig; tokens: MergedToken[]; @@ -353,7 +353,7 @@ export class SendTransaction extends React.Component { {generateTxProcessing && (
    - +
    )} @@ -574,7 +574,7 @@ export class SendTransaction extends React.Component { value = getBalanceMinusGasCosts( bigGasLimit, gasPrice, - balance + balance.wei ).toString(); } else { const tokenBalance = this.props.tokenBalances.find( diff --git a/common/containers/Tabs/Swap/components/CurrentRates.tsx b/common/containers/Tabs/Swap/components/CurrentRates.tsx index 8ac43643..b4a9bfdb 100644 --- a/common/containers/Tabs/Swap/components/CurrentRates.tsx +++ b/common/containers/Tabs/Swap/components/CurrentRates.tsx @@ -44,14 +44,13 @@ export default class CurrentRates extends Component { name={pair + 'Amount'} /> - {` ${origin} = ${toFixedIfLarger( - statePair * propsPair, - 6 - )} ${destination}`} + {` ${origin} = ${toFixedIfLarger(statePair * propsPair, 6)} ${ + destination + }`}
    ) : ( - + )}
    ); diff --git a/common/libs/contracts/ABIFunction.ts b/common/libs/contracts/ABIFunction.ts index 913a4e36..05b0a229 100644 --- a/common/libs/contracts/ABIFunction.ts +++ b/common/libs/contracts/ABIFunction.ts @@ -183,11 +183,9 @@ EncodedCall:${data}`); //TODO: parse args based on type if (!suppliedArgs[name]) { throw Error( - `Expected argument "${name}" of type "${type}" missing, suppliedArgs: ${JSON.stringify( - suppliedArgs, - null, - 2 - )}` + `Expected argument "${name}" of type "${ + type + }" missing, suppliedArgs: ${JSON.stringify(suppliedArgs, null, 2)}` ); } const value = suppliedArgs[name]; diff --git a/common/libs/wallet/balance.ts b/common/libs/wallet/balance.ts new file mode 100644 index 00000000..e5ec2c7c --- /dev/null +++ b/common/libs/wallet/balance.ts @@ -0,0 +1,6 @@ +import { Wei } from 'libs/units'; + +export interface Balance { + wei: Wei; + isPending: boolean; +} diff --git a/common/libs/wallet/index.ts b/common/libs/wallet/index.ts index 2e4ca953..52e71ba3 100644 --- a/common/libs/wallet/index.ts +++ b/common/libs/wallet/index.ts @@ -1,3 +1,4 @@ export { IWallet } from './IWallet'; +export { Balance } from './balance'; export * from './deterministic'; export * from './non-deterministic'; diff --git a/common/reducers/wallet.ts b/common/reducers/wallet.ts index 6e37ac35..83a021c5 100644 --- a/common/reducers/wallet.ts +++ b/common/reducers/wallet.ts @@ -1,19 +1,19 @@ +import { SetBalanceFullfilledAction } from 'actions/wallet/actionTypes'; import { - SetBalanceAction, SetTokenBalancesAction, SetWalletAction, WalletAction, TypeKeys } from 'actions/wallet'; -import { Wei, TokenValue } from 'libs/units'; +import { TokenValue } from 'libs/units'; import { BroadcastTransactionStatus } from 'libs/transaction'; -import { IWallet } from 'libs/wallet'; +import { IWallet, Balance } from 'libs/wallet'; import { getTxFromBroadcastTransactionStatus } from 'selectors/wallet'; export interface State { inst?: IWallet | null; // in ETH - balance?: Wei | null; + balance: Balance | { wei: null }; tokens: { [key: string]: TokenValue; }; @@ -22,18 +22,36 @@ export interface State { export const INITIAL_STATE: State = { inst: null, - balance: null, + balance: { isPending: false, wei: null }, tokens: {}, transactions: [] }; function setWallet(state: State, action: SetWalletAction): State { - return { ...state, inst: action.payload, balance: null, tokens: {} }; + return { + ...state, + inst: action.payload, + balance: INITIAL_STATE.balance, + tokens: INITIAL_STATE.tokens + }; } -function setBalance(state: State, action: SetBalanceAction): State { - const weiBalance = action.payload; - return { ...state, balance: weiBalance }; +function setBalancePending(state: State): State { + return { ...state, balance: { ...state.balance, isPending: true } }; +} + +function setBalanceFullfilled( + state: State, + action: SetBalanceFullfilledAction +): State { + return { + ...state, + balance: { wei: action.payload, isPending: false } + }; +} + +function setBalanceRejected(state: State): State { + return { ...state, balance: { ...state.balance, isPending: false } }; } function setTokenBalances(state: State, action: SetTokenBalancesAction): State { @@ -111,8 +129,12 @@ export function wallet( return setWallet(state, action); case TypeKeys.WALLET_RESET: return INITIAL_STATE; - case TypeKeys.WALLET_SET_BALANCE: - return setBalance(state, action); + case TypeKeys.WALLET_SET_BALANCE_PENDING: + return setBalancePending(state); + case TypeKeys.WALLET_SET_BALANCE_FULFILLED: + return setBalanceFullfilled(state, action); + case TypeKeys.WALLET_SET_BALANCE_REJECTED: + return setBalanceRejected(state); case TypeKeys.WALLET_SET_TOKEN_BALANCES: return setTokenBalances(state, action); case TypeKeys.WALLET_BROADCAST_TX_REQUESTED: diff --git a/common/sagas/wallet.tsx b/common/sagas/wallet.tsx index 6bd43576..f20b3e56 100644 --- a/common/sagas/wallet.tsx +++ b/common/sagas/wallet.tsx @@ -3,7 +3,9 @@ import { broadCastTxFailed, BroadcastTxRequestedAction, broadcastTxSucceded, - setBalance, + setBalanceFullfilled, + setBalancePending, + setBalanceRejected, setTokenBalances, setWallet, UnlockKeystoreAction, @@ -39,6 +41,7 @@ import translate from 'translations'; function* updateAccountBalance(): SagaIterator { try { + yield put(setBalancePending()); const wallet: null | IWallet = yield select(getWalletInst); if (!wallet) { return; @@ -47,9 +50,9 @@ function* updateAccountBalance(): SagaIterator { const address = yield apply(wallet, wallet.getAddressString); // network request const balance: Wei = yield apply(node, node.getBalance, [address]); - yield put(setBalance(balance)); + yield put(setBalanceFullfilled(balance)); } catch (error) { - yield put({ type: 'updateAccountBalance_error', error }); + yield put(setBalanceRejected()); } } @@ -191,8 +194,6 @@ function* broadcastTx(action: BroadcastTxRequestedAction): SagaIterator { } export default function* walletSaga(): SagaIterator { - // useful for development - yield call(updateBalances); yield [ takeEvery('WALLET_UNLOCK_PRIVATE_KEY', unlockPrivateKey), takeEvery('WALLET_UNLOCK_KEYSTORE', unlockKeystore), diff --git a/common/typescript/bn.d.ts b/common/typescript/bn.d.ts index a8ee5700..d4e4844f 100644 --- a/common/typescript/bn.d.ts +++ b/common/typescript/bn.d.ts @@ -369,7 +369,7 @@ declare module 'bn.js' { * @description reduct */ - modn(b: number): number; //API consistency https://github.com/indutny/bn.js/pull/130 + modn(b: number): BN; /** * @description rounded division diff --git a/spec/reducers/wallet.spec.ts b/spec/reducers/wallet.spec.ts index 8d009099..4b395bf5 100644 --- a/spec/reducers/wallet.spec.ts +++ b/spec/reducers/wallet.spec.ts @@ -16,9 +16,7 @@ describe('wallet reducer', () => { expect(wallet(undefined, walletActions.setWallet(walletInstance))).toEqual({ ...INITIAL_STATE, - inst: walletInstance, - balance: null, - tokens: {} + inst: walletInstance }); });