diff --git a/__tests__/modules/generateWallet.test.js b/__tests__/modules/generateWallet.test.js index 54d23a287..5ce3c75b1 100644 --- a/__tests__/modules/generateWallet.test.js +++ b/__tests__/modules/generateWallet.test.js @@ -1,4 +1,9 @@ -import generateWalletReducer, { newWalletKeys, newWallet, generating, resetKey, NEW_WALLET_KEYS, NEW_WALLET, SET_GENERATING, RESET_KEY } from '../../app/modules/generateWallet' +import generateWalletReducer, { + newWallet, + resetKey, + NEW_WALLET, + RESET_KEY +} from '../../app/modules/generateWallet' describe('generateWallet module tests', () => { // TODO when looking into pulling axios mock adapter into helper file to stay DRY @@ -12,8 +17,7 @@ describe('generateWallet module tests', () => { wif: null, address: null, passphrase: null, - encryptedWif: null, - generating: false + encryptedWif: null } const account = { @@ -41,42 +45,6 @@ describe('generateWallet module tests', () => { }) }) - describe('newWalletKeys tests', () => { - const expectedAction = { - type: NEW_WALLET_KEYS, - payload: { - passphrase - } - } - - test('newWalletKeys action works', () => { - expect(newWalletKeys(passphrase)).toEqual(expectedAction) - }) - - test('generateWallet reducer should handle NEW_WALLET_KEYS', () => { - const expectedState = Object.assign({}, initialState, account) - expect(generateWalletReducer(undefined, expectedAction)).toEqual(expectedState) - }) - }) - - describe('generating tests', () => { - const expectedAction = { - type: SET_GENERATING, - payload: { - generating: true - } - } - - test('generating action works', () => { - expect(generating(true)).toEqual(expectedAction) - }) - - test('generateWallet reducer should handle SET_GENERATING', () => { - const expectedState = Object.assign({}, initialState, { generating: true }) - expect(generateWalletReducer(undefined, expectedAction)).toEqual(expectedState) - }) - }) - describe('resetKey tests', () => { const expectedAction = { type: RESET_KEY } diff --git a/app/components/DisplayWalletKeys/DisplayWalletKeys.jsx b/app/components/DisplayWalletKeys/DisplayWalletKeys.jsx index 6ccfe78ca..f6adf4ba5 100644 --- a/app/components/DisplayWalletKeys/DisplayWalletKeys.jsx +++ b/app/components/DisplayWalletKeys/DisplayWalletKeys.jsx @@ -64,17 +64,17 @@ class DisplayWalletKeys extends Component {
Public Address: {address} - +
Encrypted key: {passphraseKey} - +
Private Key: {wif} - +
diff --git a/app/components/Modals/ReceiveModal/ReceiveModal.jsx b/app/components/Modals/ReceiveModal/ReceiveModal.jsx index fdea062ad..fc2f50ea4 100644 --- a/app/components/Modals/ReceiveModal/ReceiveModal.jsx +++ b/app/components/Modals/ReceiveModal/ReceiveModal.jsx @@ -22,12 +22,12 @@ class ReceiveModal extends Component { if (err) console.log(err) }) }} - title='NEO/GAS Wallet Address' + title='NEO Wallet Address' hideModal={hideModal} style={{ content: { width: '420px', - height: '390px' + height: '420px' } }} > @@ -38,8 +38,8 @@ class ReceiveModal extends Component {
{ this.canvas = node }} />
-
Only send NEO/GAS to this address
-
Sending any other digital asset will result in permanent loss.
+
Only send assets, such as NEO and GAS, and tokens, such as RPX, that are compatible with the NEO Blockchain.
+
Sending any other digital asset or token will result in permanent loss.
) diff --git a/app/components/Modals/SendModal/SendDisplay.jsx b/app/components/Modals/SendModal/SendDisplay.jsx index 3bd0dd0db..9cd87347c 100644 --- a/app/components/Modals/SendModal/SendDisplay.jsx +++ b/app/components/Modals/SendModal/SendDisplay.jsx @@ -3,6 +3,8 @@ import React from 'react' import classNames from 'classnames' import { ASSETS } from '../../../core/constants' +import { isToken } from '../../../core/wallet' +import { formatBalance, formatGAS, formatNEO, truncateGAS, truncateTokenBalance } from '../../../core/formatters' import styles from './SendModal.scss' @@ -49,11 +51,20 @@ const SendDisplay = ({
{[25, 50, 75, 100].map((percent: number) => { - const value = percent * parseFloat(balance) / 100 - return () + let value = percent * parseFloat(balance) / 100 + if (symbol === ASSETS.NEO) { + value = Math.ceil(value) + } else if (symbol === ASSETS.GAS) { + value = truncateGAS(value) + } else { + value = truncateTokenBalance(value, tokens[symbol].info.decimals) + } + return ( + + ) })}
@@ -71,7 +82,9 @@ const SendDisplay = ({
- {balance} + {symbol === ASSETS.NEO && formatNEO(balance)} + {symbol === ASSETS.GAS && formatGAS(balance)} + {isToken(symbol) && formatBalance(balance, tokens[symbol].info.decimals)}
diff --git a/app/components/Modals/SendModal/SendModal.jsx b/app/components/Modals/SendModal/SendModal.jsx index 33b74ffa7..4c84ce82a 100644 --- a/app/components/Modals/SendModal/SendModal.jsx +++ b/app/components/Modals/SendModal/SendModal.jsx @@ -110,7 +110,7 @@ class SendModal extends Component { hideModal={hideModal} style={{ content: { - width: '460px', + width: '480px', height: '410px' } }} diff --git a/app/components/Modals/TokenInfoModal/TokenInfoModal.jsx b/app/components/Modals/TokenInfoModal/TokenInfoModal.jsx index 1fcbc9d71..09c61694b 100644 --- a/app/components/Modals/TokenInfoModal/TokenInfoModal.jsx +++ b/app/components/Modals/TokenInfoModal/TokenInfoModal.jsx @@ -1,89 +1,56 @@ // @flow -import React, { Component } from 'react' -import numeral from 'numeral' +import React from 'react' import BaseModal from '../BaseModal' import Table from '../../Table' -import Loader from '../../Loader' + +import { formatBalance } from '../../../core/formatters' import styles from './TokenInfoModal.scss' type Props = { hideModal: Function, token: TokenWithInfoType, - retrieveTokenInfo: Function -} - -type State = { - token: TokenWithInfoType } -class TokenInfoModal extends Component { - state = { - token: this.props.token - } - - onAfterOpen = () => { - const { retrieveTokenInfo, token } = this.props - if (!token.info) { - retrieveTokenInfo(token.symbol).then((info) => { - this.setState({ - token: { - ...token, - info - } - }) - }) - } - } - - render () { - const { hideModal } = this.props - const { token } = this.state - - return ( - -
- {!token.info ? : ( - - - - - - - - - - - - - - - - - - - - - - - -
Symbol:{token.symbol}
Name:{token.info.name}
Total Supply:{numeral(token.info.totalSupply).format('0,0')}
Decimals{token.info.decimals}
Balance{token.balance}
- )} -
-
- ) - } -} +const TokenInfoModal = ({ hideModal, token }: Props) => ( + +
+ + + + + + + + + + + + + + + + + + + + + + + +
Symbol:{token.symbol}
Name:{token.info.name}
Total Supply:{formatBalance(token.info.totalSupply)}
Decimals{token.info.decimals}
Balance{formatBalance(token.balance, token.info.decimals)}
+
+
+) export default TokenInfoModal diff --git a/app/containers/CreateWallet/index.js b/app/containers/CreateWallet/index.js index 9a0b3cde4..8fb95ab03 100644 --- a/app/containers/CreateWallet/index.js +++ b/app/containers/CreateWallet/index.js @@ -9,8 +9,7 @@ import { getWif, getAddress, getEncryptedWif, - getPassphrase, - getGenerating + getPassphrase } from '../../modules/generateWallet' import CreateWallet from './CreateWallet' @@ -19,8 +18,7 @@ const mapStateToProps = (state: Object) => ({ wif: getWif(state), address: getAddress(state), encryptedWif: getEncryptedWif(state), - passphrase: getPassphrase(state), - generating: getGenerating(state) + passphrase: getPassphrase(state) }) const actionCreators = { diff --git a/app/containers/EncryptKey/index.js b/app/containers/EncryptKey/index.js index 90f32041d..a6eb16b86 100644 --- a/app/containers/EncryptKey/index.js +++ b/app/containers/EncryptKey/index.js @@ -9,8 +9,7 @@ import { getWif, getAddress, getEncryptedWif, - getPassphrase, - getGenerating + getPassphrase } from '../../modules/generateWallet' import EncryptKey from './EncryptKey' @@ -19,8 +18,7 @@ const mapStateToProps = (state: Object) => ({ wif: getWif(state), address: getAddress(state), encryptedWif: getEncryptedWif(state), - passphrase: getPassphrase(state), - generating: getGenerating(state) + passphrase: getPassphrase(state) }) const actionCreators = { diff --git a/app/containers/WalletInfo/TokensBalance.jsx b/app/containers/WalletInfo/TokensBalance.jsx index 6d95bdb63..c3827d124 100644 --- a/app/containers/WalletInfo/TokensBalance.jsx +++ b/app/containers/WalletInfo/TokensBalance.jsx @@ -4,6 +4,7 @@ import React from 'react' import Table from '../../components/Table' import { MODAL_TYPES } from '../../core/constants' +import { formatBalance } from '../../core/formatters' import InfoOutline from 'react-icons/lib/md/info-outline' @@ -26,7 +27,7 @@ const tokens = ({ tokens, showModal, retrieveTokenInfo }: Props) => ( {Object.keys(tokens).map((symbol) => { const token = tokens[symbol] - const { balance } = token + const { balance, info } = token return ( ( onClick={() => showModal(MODAL_TYPES.TOKEN_INFO, { token, retrieveTokenInfo })}> {symbol} - {balance} + {info ? formatBalance(balance, info.decimals) : balance} ) })} diff --git a/app/core/formatters.js b/app/core/formatters.js index e693f1d63..9b2dfe6a6 100644 --- a/app/core/formatters.js +++ b/app/core/formatters.js @@ -2,15 +2,25 @@ import { truncateNumber } from './math' import numeral from 'numeral' +const DEFAULT_DECIMAL_LENGTH = 8 const GAS_DECIMAL_LENGTH = 8 const GAS_DECIMAL_SHORT_DISPLAY_LENGTH = 4 +export const formatBalance = (value: number | string, precision?: number) => { + return precision + ? numeral(value).format('0,0.' + Array(precision + 1).join('0')) + : numeral(value).format('0,0') +} + +export const truncateTokenBalance = (value: number | string, precision?: number = DEFAULT_DECIMAL_LENGTH) => truncateNumber(parseFloat(value), precision).toFixed(precision) + +export const truncateGAS = (gas: number | string, precision?: number = GAS_DECIMAL_LENGTH) => truncateTokenBalance(gas, precision) + export const formatGAS = (gas: number | string, shortDisplay: boolean = false): string => { - const decimalLength = shortDisplay ? GAS_DECIMAL_SHORT_DISPLAY_LENGTH : GAS_DECIMAL_LENGTH - const customTruncatedNumber = truncateNumber(parseFloat(gas), decimalLength).toFixed(decimalLength) - return numeral(customTruncatedNumber).format('0,0.' + Array(decimalLength + 1).join('0')) + const precision = shortDisplay ? GAS_DECIMAL_SHORT_DISPLAY_LENGTH : GAS_DECIMAL_LENGTH + return formatBalance(truncateGAS(gas, precision), precision) } -export const formatNEO = (neo: number | string): string => numeral(neo).format('0,0') +export const formatNEO = (neo: number | string): string => formatBalance(neo) export const formatFiat = (value: number | string): string => numeral(value).format('0,0.00') diff --git a/app/modules/generateWallet.js b/app/modules/generateWallet.js index 8eba90a8c..040338181 100644 --- a/app/modules/generateWallet.js +++ b/app/modules/generateWallet.js @@ -1,25 +1,16 @@ // @flow import storage from 'electron-json-storage' -import Neon, { wallet } from 'neon-js' +import { wallet } from 'neon-js' import { showErrorNotification, showInfoNotification, hideNotification, showSuccessNotification } from './notifications' import { validatePassphrase, checkMatchingPassphrases } from '../core/wallet' -import asyncWrap from '../core/asyncHelper' // Constants -export const NEW_WALLET_KEYS = 'NEW_WALLET_KEYS' export const NEW_WALLET = 'NEW_WALLET' -export const SET_GENERATING = 'SET_GENERATING' export const RESET_KEY = 'RESET_KEY' // Actions -export function newWalletKeys (passphrase: string) { - return { - type: NEW_WALLET_KEYS, - payload: { passphrase } - } -} export function newWallet (account: Object) { return { @@ -33,13 +24,6 @@ export function newWallet (account: Object) { } } -export function generating (generating: boolean) { - return { - type: SET_GENERATING, - payload: { generating } - } -} - export function resetKey () { return { type: RESET_KEY @@ -66,9 +50,16 @@ export const generateWalletFromWif = (passphrase: string, passphrase2: string, w const infoNotificationId = dispatch(showInfoNotification({ message: 'Generating encoded key...', autoDismiss: 0 })) setTimeout(async () => { try { - const [_err, result] = await asyncWrap(wallet.encryptWifAccount(wif, passphrase)) // eslint-disable-line + const account = new wallet.Account(wif) + const { WIF, address } = account + const encryptedWif = wallet.encrypt(WIF, passphrase) dispatch(hideNotification(infoNotificationId)) - return dispatch(newWallet(result)) + return dispatch(newWallet({ + wif: WIF, + address, + passphrase, + encryptedWif + })) } catch (e) { return dispatchError('The private key is not valid') } @@ -87,9 +78,18 @@ export const generateNewWallet = (passphrase: string, passphrase2: string) => as const infoNotificationId = dispatch(showInfoNotification({ message: 'Generating encoded key...', autoDismiss: 0 })) setTimeout(async () => { try { - const [_err, result] = await asyncWrap(wallet.generateEncryptedWif(passphrase)) //eslint-disable-line + const newPrivateKey = wallet.generatePrivateKey() + const account = new wallet.Account(newPrivateKey) + const { WIF, address } = account + const encryptedWif = wallet.encrypt(WIF, passphrase) + dispatch(hideNotification(infoNotificationId)) - return dispatch(newWallet(result)) + return dispatch(newWallet({ + wif: WIF, + address, + passphrase, + encryptedWif + })) } catch (e) { return dispatchError('An error occured while trying to generate a new wallet') } @@ -104,32 +104,16 @@ export const getWif = (state: Object) => state.generateWallet.wif export const getAddress = (state: Object) => state.generateWallet.address export const getPassphrase = (state: Object) => state.generateWallet.passphrase export const getEncryptedWif = (state: Object) => state.generateWallet.encryptedWif -export const getGenerating = (state: Object) => state.generateWallet.generating const initialState = { wif: null, address: null, passphrase: null, - encryptedWif: null, - generating: false + encryptedWif: null } export default (state: Object = initialState, action: ReduxAction) => { switch (action.type) { - case NEW_WALLET_KEYS: { - const { passphrase } = action.payload - const newPrivateKey = wallet.generatePrivateKey() - const newWif = wallet.getWIFFromPrivateKey(newPrivateKey) - const encryptedWif = wallet.encryptWIF(newWif, passphrase) - const loadAccount = Neon.create.account(newWif) - return { - ...state, - wif: newWif, - address: loadAccount.address, - passphrase, - encryptedWif - } - } case NEW_WALLET: { const { passphrase, wif, address, encryptedWif } = action.payload return { @@ -137,15 +121,7 @@ export default (state: Object = initialState, action: ReduxAction) => { wif, address, passphrase, - encryptedWif, - generating: false - } - } - case SET_GENERATING: { - const { generating } = action.payload - return { - ...state, - generating + encryptedWif } } case RESET_KEY: { diff --git a/app/modules/sale.js b/app/modules/sale.js index 5b807e0ff..725cc24b4 100644 --- a/app/modules/sale.js +++ b/app/modules/sale.js @@ -33,7 +33,7 @@ export const participateInSale = (neoToSend: number, scriptHash: string) => asyn dispatch(showInfoNotification({ message: 'Sending transaction', autoDismiss: 0 })) const [error, rpcEndpoint] = await asyncWrap(api.neonDB.getRPCEndpoint(net)) // eslint-disable-line - const [err, balance] = await asyncWrap(api.nep5.getTokenBalance(rpcEndpoint, _scriptHash, account.address)) + const [err, balance] = await asyncWrap(api.nep5.getTokenBalance(rpcEndpoint, _scriptHash, account.address)) // eslint-disable-line const [e, response] = await asyncWrap(api.neonDB.doMintTokens(net, _scriptHash, wif, toMint, 0)) if (error || err || e) { dispatch(showErrorNotification({ message: 'This script hash cannot mint tokens.' })) diff --git a/app/modules/transactions.js b/app/modules/transactions.js index 41be1f57e..a671517a1 100644 --- a/app/modules/transactions.js +++ b/app/modules/transactions.js @@ -1,6 +1,5 @@ // @flow /* eslint-disable camelcase */ -import { capitalize } from 'lodash' import { api } from 'neon-js' import { setTransactionHistory, getNEO, getGAS, getTokens, getScriptHashForNetwork } from './wallet' @@ -8,7 +7,7 @@ import { showErrorNotification, showInfoNotification, showSuccessNotification } import { getWif, getPublicKey, getSigningFunction, getAddress, LOGOUT } from './account' import { getNetwork } from './metadata' -import { validateTransactionBeforeSending, obtainTokenBalance } from '../core/wallet' +import { validateTransactionBeforeSending, obtainTokenBalance, isToken } from '../core/wallet' import { ASSETS } from '../core/constants' import asyncWrap from '../core/asyncHelper' @@ -53,18 +52,17 @@ export const sendTransaction = (sendAddress: string, sendAmount: string, symbol: const publicKey = getPublicKey(state) const rejectTransaction = (message: string) => dispatch(showErrorNotification({ message })) - const tokenBalance = obtainTokenBalance(tokens, symbol) + const tokenBalance = isToken(symbol) && obtainTokenBalance(tokens, symbol) + const parsedSendAmount = parseFloat(sendAmount) - const { error, valid } = validateTransactionBeforeSending(neo, gas, tokenBalance, symbol, sendAddress, sendAmount) + const { error, valid } = validateTransactionBeforeSending(neo, gas, tokenBalance, symbol, sendAddress, parsedSendAmount) if (valid) { const selfAddress = address - // We have to capitalize NEO/GAS because neon-wallet-db is using capitalized asset name - const assetName = capitalize(symbol === ASSETS.NEO ? ASSETS.NEO : ASSETS.GAS) let sendAsset = {} - sendAsset[assetName] = sendAmount + sendAsset[symbol] = parseFloat(parsedSendAmount) dispatch(showInfoNotification({ message: 'Sending Transaction...', autoDismiss: 0 })) - log(net, 'SEND', selfAddress, { to: sendAddress, asset: symbol, amount: sendAmount }) + log(net, 'SEND', selfAddress, { to: sendAddress, asset: symbol, amount: parsedSendAmount }) const isHardwareSend = !!publicKey @@ -77,7 +75,7 @@ export const sendTransaction = (sendAddress: string, sendAmount: string, symbol: sendAssetFn = () => api.neonDB.doSendAsset(net, sendAddress, wif, sendAsset, null) } else { const scriptHash = getScriptHashForNetwork(net, symbol) - sendAssetFn = () => api.nep5.doTransferToken(net, scriptHash, wif, sendAddress, parseFloat(sendAmount)) + sendAssetFn = () => api.nep5.doTransferToken(net, scriptHash, wif, sendAddress, parsedSendAmount) } } diff --git a/app/modules/wallet.js b/app/modules/wallet.js index 6c3cea025..75a0a650e 100644 --- a/app/modules/wallet.js +++ b/app/modules/wallet.js @@ -113,6 +113,7 @@ export const retrieveTokensBalance = () => async (dispatch: DispatchType, getSta const state = getState() const net = getNetwork(state) const address = getAddress(state) + const tokensFromState = getTokens(state) const tokens = {} for (let [symbol] of TOKEN_PAIRS) { @@ -121,6 +122,10 @@ export const retrieveTokensBalance = () => async (dispatch: DispatchType, getSta const [_error, rpcEndpoint] = await asyncWrap(api.neonDB.getRPCEndpoint(net)) // eslint-disable-line const [_err, results] = await asyncWrap(api.nep5.getTokenBalance(rpcEndpoint, scriptHash, address)) // eslint-disable-line if (results) { + let info = tokensFromState[symbol].info + if (!info) { + await dispatch(retrieveTokenInfo(symbol)) + } tokens[symbol] = { symbol, balance: results,