Replace bignumber.js with bn.js (#319)
* Add definition file for bn.js * Remove types-bn * make isBN a static property * progress commit -- swap out bignumber.js for bn.js * Swap out bignumber for bn in vendor * Change modn to number return * Start to strip out units lib for a string manipulation based lib * Convert codebase to only base units * Get rid of useless component * Handle only wei in values * Use unit conversion in sidebar * Automatically strip hex prefix, and handle decimal edge case * Handle base 16 wei in transactions * Make a render callback component for dealing with unit conversion * Switch contracts to use bn.js, and get transaction values from signedTx instead of state * Get send transaction working with bn.js * Remove redundant hex stripping, return base value of tokens * Cleanup unit file * Re-implement toFixed for strings * Use formatNumber in codebase * Cleanup code * Undo package test changes * Update snapshot and remove console logs * Use TokenValue / Wei more consistently where applicable * Add typing to deterministicWallets, fix confirmation modal, make UnitDisplay more flexible * Clean up prop handling in UnitDisplay * Change instanceof to typeof check, change boolean of displayBalance * Fix tsc errors * Fix token row displaying wrong decimals * Fix deterministic modal token display * 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
This commit is contained in:
parent
c9c147db52
commit
8fe664c171
|
@ -6,5 +6,5 @@ export enum TypeKeys {
|
|||
CONFIG_TOGGLE_OFFLINE = 'CONFIG_TOGGLE_OFFLINE',
|
||||
CONFIG_FORCE_OFFLINE = 'CONFIG_FORCE_OFFLINE',
|
||||
CONFIG_POLL_OFFLINE_STATUS = 'CONFIG_POLL_OFFLINE_STATUS',
|
||||
CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET',
|
||||
CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET'
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import { BigNumber } from 'bignumber.js';
|
||||
import { TokenValue, Wei } from 'libs/units';
|
||||
|
||||
export interface TokenValues {
|
||||
[key: string]: BigNumber;
|
||||
export interface ITokenData {
|
||||
value: TokenValue;
|
||||
decimal: number;
|
||||
}
|
||||
|
||||
export interface ITokenValues {
|
||||
[key: string]: ITokenData | null;
|
||||
}
|
||||
|
||||
export interface DeterministicWalletData {
|
||||
index: number;
|
||||
address: string;
|
||||
value?: BigNumber;
|
||||
tokenValues: TokenValues;
|
||||
value?: TokenValue;
|
||||
tokenValues: ITokenValues;
|
||||
}
|
||||
|
||||
/*** Get determinstic wallets ***/
|
||||
|
@ -39,8 +44,8 @@ export interface SetDesiredTokenAction {
|
|||
/*** Set wallet values ***/
|
||||
export interface UpdateDeterministicWalletArgs {
|
||||
address: string;
|
||||
value?: BigNumber;
|
||||
tokenValues?: TokenValues;
|
||||
value?: Wei;
|
||||
tokenValues?: ITokenValues;
|
||||
index?: any;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { BigNumber } from 'bignumber.js';
|
||||
import { Wei } from 'libs/units';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
import { IWallet } from 'libs/wallet/IWallet';
|
||||
import * as types from './actionTypes';
|
||||
import { TypeKeys } from './constants';
|
||||
|
@ -58,7 +57,7 @@ export function setBalance(value: Wei): types.SetBalanceAction {
|
|||
|
||||
export type TSetTokenBalances = typeof setTokenBalances;
|
||||
export function setTokenBalances(payload: {
|
||||
[key: string]: BigNumber;
|
||||
[key: string]: TokenValue;
|
||||
}): types.SetTokenBalancesAction {
|
||||
return {
|
||||
type: TypeKeys.WALLET_SET_TOKEN_BALANCES,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { BigNumber } from 'bignumber.js';
|
||||
import { Wei } from 'libs/units';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
import { IWallet } from 'libs/wallet/IWallet';
|
||||
import { TypeKeys } from './constants';
|
||||
|
||||
|
@ -43,7 +42,7 @@ export interface SetBalanceAction {
|
|||
export interface SetTokenBalancesAction {
|
||||
type: TypeKeys.WALLET_SET_TOKEN_BALANCES;
|
||||
payload: {
|
||||
[key: string]: BigNumber;
|
||||
[key: string]: TokenValue;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { TFetchCCRates } from 'actions/rates';
|
||||
import { Identicon } from 'components/ui';
|
||||
import { Identicon, UnitDisplay } from 'components/ui';
|
||||
import { NetworkConfig } from 'config/data';
|
||||
import { Ether } from 'libs/units';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import { Wei } from 'libs/units';
|
||||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { formatNumber } from 'utils/formatters';
|
||||
import './AccountInfo.scss';
|
||||
|
||||
interface Props {
|
||||
balance: Ether;
|
||||
balance: Wei;
|
||||
wallet: IWallet;
|
||||
network: NetworkConfig;
|
||||
fetchCCRates: TFetchCCRates;
|
||||
|
@ -54,7 +53,7 @@ export default class AccountInfo extends React.Component<Props, State> {
|
|||
public render() {
|
||||
const { network, balance } = this.props;
|
||||
const { blockExplorer, tokenExplorer } = network;
|
||||
const { address } = this.state;
|
||||
const { address, showLongBalance } = this.state;
|
||||
|
||||
return (
|
||||
<div className="AccountInfo">
|
||||
|
@ -80,9 +79,11 @@ export default class AccountInfo extends React.Component<Props, State> {
|
|||
className="AccountInfo-list-item-clickable mono wrap"
|
||||
onClick={this.toggleShowLongBalance}
|
||||
>
|
||||
{this.state.showLongBalance
|
||||
? balance ? balance.toString() : '???'
|
||||
: balance ? formatNumber(balance.amount) : '???'}
|
||||
<UnitDisplay
|
||||
value={balance}
|
||||
unit={'ether'}
|
||||
displayShortBalance={!showLongBalance}
|
||||
/>
|
||||
</span>
|
||||
{` ${network.name}`}
|
||||
</li>
|
||||
|
@ -90,28 +91,28 @@ export default class AccountInfo extends React.Component<Props, State> {
|
|||
</div>
|
||||
|
||||
{(!!blockExplorer || !!tokenExplorer) && (
|
||||
<div className="AccountInfo-section">
|
||||
<h5 className="AccountInfo-section-header">
|
||||
{translate('sidebar_TransHistory')}
|
||||
</h5>
|
||||
<ul className="AccountInfo-list">
|
||||
{!!blockExplorer && (
|
||||
<li className="AccountInfo-list-item">
|
||||
<a href={blockExplorer.address(address)} target="_blank">
|
||||
{`${network.name} (${blockExplorer.name})`}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{!!tokenExplorer && (
|
||||
<li className="AccountInfo-list-item">
|
||||
<a href={tokenExplorer.address(address)} target="_blank">
|
||||
{`Tokens (${tokenExplorer.name})`}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<div className="AccountInfo-section">
|
||||
<h5 className="AccountInfo-section-header">
|
||||
{translate('sidebar_TransHistory')}
|
||||
</h5>
|
||||
<ul className="AccountInfo-list">
|
||||
{!!blockExplorer && (
|
||||
<li className="AccountInfo-list-item">
|
||||
<a href={blockExplorer.address(address)} target="_blank">
|
||||
{`${network.name} (${blockExplorer.name})`}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{!!tokenExplorer && (
|
||||
<li className="AccountInfo-list-item">
|
||||
<a href={tokenExplorer.address(address)} target="_blank">
|
||||
{`Tokens (${tokenExplorer.name})`}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { Ether } from 'libs/units';
|
||||
import { Wei } from 'libs/units';
|
||||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { formatNumber } from 'utils/formatters';
|
||||
import './EquivalentValues.scss';
|
||||
import { State } from 'reducers/rates';
|
||||
import { symbols } from 'actions/rates';
|
||||
import { UnitDisplay } from 'components/ui';
|
||||
|
||||
interface Props {
|
||||
balance?: Ether;
|
||||
balance?: Wei;
|
||||
rates?: State['rates'];
|
||||
ratesError?: State['ratesError'];
|
||||
}
|
||||
|
@ -33,9 +33,15 @@ export default class EquivalentValues extends React.Component<Props, {}> {
|
|||
</span>
|
||||
<span className="EquivalentValues-values-currency-value">
|
||||
{' '}
|
||||
{balance
|
||||
? formatNumber(balance.amount.times(rates[key]))
|
||||
: '???'}
|
||||
{balance ? (
|
||||
<UnitDisplay
|
||||
unit={'ether'}
|
||||
value={balance.muln(rates[key])}
|
||||
displayShortBalance={2}
|
||||
/>
|
||||
) : (
|
||||
'???'
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import removeIcon from 'assets/images/icon-remove.svg';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import React from 'react';
|
||||
import { formatNumber } from 'utils/formatters';
|
||||
import { TokenValue } from 'libs/units';
|
||||
import { UnitDisplay } from 'components/ui';
|
||||
import './TokenRow.scss';
|
||||
|
||||
interface Props {
|
||||
balance: BigNumber;
|
||||
balance: TokenValue;
|
||||
symbol: string;
|
||||
custom?: boolean;
|
||||
decimal: number;
|
||||
onRemove(symbol: string): void;
|
||||
}
|
||||
interface State {
|
||||
|
@ -18,9 +19,11 @@ export default class TokenRow extends React.Component<Props, State> {
|
|||
public state = {
|
||||
showLongBalance: false
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { balance, symbol, custom } = this.props;
|
||||
const { balance, symbol, custom, decimal } = this.props;
|
||||
const { showLongBalance } = this.state;
|
||||
|
||||
return (
|
||||
<tr className="TokenRow">
|
||||
<td
|
||||
|
@ -28,21 +31,24 @@ export default class TokenRow extends React.Component<Props, State> {
|
|||
title={`${balance.toString()} (Double-Click)`}
|
||||
onDoubleClick={this.toggleShowLongBalance}
|
||||
>
|
||||
{!!custom &&
|
||||
{!!custom && (
|
||||
<img
|
||||
src={removeIcon}
|
||||
className="TokenRow-balance-remove"
|
||||
title="Remove Token"
|
||||
onClick={this.onRemove}
|
||||
tabIndex={0}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
<span>
|
||||
{showLongBalance ? balance.toString() : formatNumber(balance)}
|
||||
<UnitDisplay
|
||||
value={balance}
|
||||
decimal={decimal}
|
||||
displayShortBalance={!showLongBalance}
|
||||
/>
|
||||
</span>
|
||||
</td>
|
||||
<td className="TokenRow-symbol">
|
||||
{symbol}
|
||||
</td>
|
||||
<td className="TokenRow-symbol">{symbol}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,25 +25,24 @@ export default class TokenBalances extends React.Component<Props, State> {
|
|||
public render() {
|
||||
const { tokens } = this.props;
|
||||
const shownTokens = tokens.filter(
|
||||
token => !token.balance.eq(0) || token.custom || this.state.showAllTokens
|
||||
token => !token.balance.eqn(0) || token.custom || this.state.showAllTokens
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="TokenBalances">
|
||||
<h5 className="TokenBalances-title">
|
||||
{translate('sidebar_TokenBal')}
|
||||
</h5>
|
||||
<h5 className="TokenBalances-title">{translate('sidebar_TokenBal')}</h5>
|
||||
<table className="TokenBalances-rows">
|
||||
<tbody>
|
||||
{shownTokens.map(token =>
|
||||
{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>
|
||||
|
||||
|
@ -58,16 +57,15 @@ export default class TokenBalances extends React.Component<Props, State> {
|
|||
className="btn btn-default btn-xs"
|
||||
onClick={this.toggleShowCustomTokenForm}
|
||||
>
|
||||
<span>
|
||||
{translate('SEND_custom')}
|
||||
</span>
|
||||
<span>{translate('SEND_custom')}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{this.state.showCustomTokenForm &&
|
||||
{this.state.showCustomTokenForm && (
|
||||
<div className="TokenBalances-form">
|
||||
<AddCustomTokenForm onSave={this.addCustomToken} />
|
||||
</div>}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
import { showNotification, TShowNotification } from 'actions/notifications';
|
||||
import { fetchCCRates as dFetchCCRates, TFetchCCRates } from 'actions/rates';
|
||||
import { NetworkConfig } from 'config/data';
|
||||
import { Ether } from 'libs/units';
|
||||
import { Wei } from 'libs/units';
|
||||
import { IWallet } from 'libs/wallet/IWallet';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -27,7 +27,7 @@ import OfflineToggle from './OfflineToggle';
|
|||
|
||||
interface Props {
|
||||
wallet: IWallet;
|
||||
balance: Ether;
|
||||
balance: Wei;
|
||||
network: NetworkConfig;
|
||||
tokenBalances: TokenBalance[];
|
||||
rates: State['rates'];
|
||||
|
|
|
@ -91,9 +91,9 @@ export default class Header extends Component<Props, {}> {
|
|||
|
||||
<div className="Header-branding-right-dropdown">
|
||||
<LanguageDropDown
|
||||
ariaLabel={`change language. current language ${languages[
|
||||
selectedLanguage
|
||||
]}`}
|
||||
ariaLabel={`change language. current language ${
|
||||
languages[selectedLanguage]
|
||||
}`}
|
||||
options={Object.values(languages)}
|
||||
value={languages[selectedLanguage]}
|
||||
extra={
|
||||
|
@ -111,7 +111,9 @@ export default class Header extends Component<Props, {}> {
|
|||
|
||||
<div className="Header-branding-right-dropdown">
|
||||
<ColorDropdown
|
||||
ariaLabel={`change node. current node ${selectedNode.network} node by ${selectedNode.service}`}
|
||||
ariaLabel={`change node. current node ${
|
||||
selectedNode.network
|
||||
} node by ${selectedNode.service}`}
|
||||
options={nodeOptions}
|
||||
value={nodeSelection}
|
||||
extra={
|
||||
|
|
|
@ -7,12 +7,14 @@ import {
|
|||
SetDesiredTokenAction
|
||||
} from 'actions/deterministicWallets';
|
||||
import Modal, { IButton } from 'components/ui/Modal';
|
||||
import { AppState } from 'reducers';
|
||||
import { NetworkConfig } from 'config/data';
|
||||
import { isValidPath } from 'libs/validators';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { getTokens, MergedToken } from 'selectors/wallet';
|
||||
import { UnitDisplay } from 'components/ui';
|
||||
import './DeterministicWalletsModal.scss';
|
||||
|
||||
const WALLETS_PER_PAGE = 5;
|
||||
|
@ -264,15 +266,12 @@ class DeterministicWalletsModal extends React.Component<Props, State> {
|
|||
);
|
||||
};
|
||||
|
||||
private renderWalletRow(wallet) {
|
||||
private renderWalletRow(wallet: DeterministicWalletData) {
|
||||
const { desiredToken, network } = this.props;
|
||||
const { selectedAddress } = this.state;
|
||||
|
||||
// Get renderable values, but keep 'em short
|
||||
const value = wallet.value ? wallet.value.toEther().toPrecision(4) : '';
|
||||
const tokenValue = wallet.tokenValues[desiredToken]
|
||||
? wallet.tokenValues[desiredToken].toPrecision(4)
|
||||
: '';
|
||||
const token = wallet.tokenValues[desiredToken];
|
||||
|
||||
return (
|
||||
<tr
|
||||
|
@ -290,10 +289,24 @@ class DeterministicWalletsModal extends React.Component<Props, State> {
|
|||
{wallet.address}
|
||||
</td>
|
||||
<td>
|
||||
{value} {network.unit}
|
||||
<UnitDisplay
|
||||
unit={'ether'}
|
||||
value={wallet.value}
|
||||
symbol={network.unit}
|
||||
displayShortBalance={true}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{tokenValue} {desiredToken}
|
||||
{token ? (
|
||||
<UnitDisplay
|
||||
decimal={token.decimal}
|
||||
value={token.value}
|
||||
symbol={desiredToken}
|
||||
displayShortBalance={true}
|
||||
/>
|
||||
) : (
|
||||
'???'
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
|
@ -308,7 +321,7 @@ class DeterministicWalletsModal extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
wallets: state.deterministicWallets.wallets,
|
||||
desiredToken: state.deterministicWallets.desiredToken,
|
||||
|
|
|
@ -54,9 +54,9 @@ export default class KeystoreDecrypt extends Component {
|
|||
<div className={file.length && passReq ? '' : 'hidden'}>
|
||||
<p>{translate('ADD_Label_3')}</p>
|
||||
<input
|
||||
className={`form-control ${password.length > 0
|
||||
? 'is-valid'
|
||||
: 'is-invalid'}`}
|
||||
className={`form-control ${
|
||||
password.length > 0 ? 'is-valid' : 'is-invalid'
|
||||
}`}
|
||||
value={password}
|
||||
onChange={this.onPasswordChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { toTokenBase } from 'libs/units';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
interface IChildren {
|
||||
onUserInput: UnitConverter['onUserInput'];
|
||||
convertedUnit: string;
|
||||
}
|
||||
interface IFakeEvent {
|
||||
currentTarget: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
decimal: number;
|
||||
children({ onUserInput, convertedUnit }: IChildren): React.ReactElement<any>;
|
||||
onChange(baseUnit: IFakeEvent);
|
||||
}
|
||||
|
||||
interface State {
|
||||
userInput: string;
|
||||
}
|
||||
|
||||
const initialState = { userInput: '' };
|
||||
|
||||
export class UnitConverter extends Component<Props, State> {
|
||||
public state: State = initialState;
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
const { userInput } = this.state;
|
||||
|
||||
if (this.props.decimal !== nextProps.decimal) {
|
||||
this.baseUnitCb(userInput, nextProps.decimal);
|
||||
}
|
||||
}
|
||||
|
||||
public onUserInput = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
const { value } = e.currentTarget;
|
||||
const { decimal } = this.props;
|
||||
this.baseUnitCb(value, decimal);
|
||||
this.setState({ userInput: value });
|
||||
};
|
||||
|
||||
public render() {
|
||||
return this.props.children({
|
||||
onUserInput: this.onUserInput,
|
||||
convertedUnit: this.state.userInput
|
||||
});
|
||||
}
|
||||
private baseUnitCb = (value: string, decimal: number) => {
|
||||
const baseUnit = toTokenBase(value, decimal).toString();
|
||||
const fakeEvent = {
|
||||
currentTarget: {
|
||||
value: baseUnit
|
||||
}
|
||||
};
|
||||
this.props.onChange(fakeEvent);
|
||||
};
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './UnitConverter';
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
fromTokenBase,
|
||||
getDecimal,
|
||||
UnitKey,
|
||||
Wei,
|
||||
TokenValue
|
||||
} from 'libs/units';
|
||||
import { formatNumber as format } from 'utils/formatters';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* @description base value of the token / ether, incase of waiting for API calls, we can return '???'
|
||||
* @type {TokenValue | Wei}
|
||||
* @memberof Props
|
||||
*/
|
||||
value?: TokenValue | Wei;
|
||||
/**
|
||||
* @description Symbol to display to the right of the value, such as 'ETH'
|
||||
* @type {string}
|
||||
* @memberof Props
|
||||
*/
|
||||
symbol?: string;
|
||||
/**
|
||||
* @description display the long balance, if false, trims it to 3 decimal places, if a number is specified then that number is the number of digits to be displayed.
|
||||
* @type {boolean}
|
||||
* @memberof Props
|
||||
*/
|
||||
displayShortBalance?: boolean | number;
|
||||
}
|
||||
|
||||
interface EthProps extends Props {
|
||||
unit: UnitKey;
|
||||
}
|
||||
interface TokenProps extends Props {
|
||||
decimal: number;
|
||||
}
|
||||
|
||||
const isEthereumUnit = (param: EthProps | TokenProps): param is EthProps =>
|
||||
!!(param as EthProps).unit;
|
||||
|
||||
const UnitDisplay: React.SFC<EthProps | TokenProps> = params => {
|
||||
const { value, symbol, displayShortBalance } = params;
|
||||
|
||||
if (!value) {
|
||||
return <span>???</span>;
|
||||
}
|
||||
|
||||
const convertedValue = isEthereumUnit(params)
|
||||
? fromTokenBase(value, getDecimal(params.unit))
|
||||
: fromTokenBase(value, params.decimal);
|
||||
|
||||
let formattedValue;
|
||||
|
||||
if (displayShortBalance) {
|
||||
const digits =
|
||||
typeof displayShortBalance === 'number' && displayShortBalance;
|
||||
formattedValue = digits
|
||||
? format(convertedValue, digits)
|
||||
: format(convertedValue);
|
||||
} else {
|
||||
formattedValue = convertedValue;
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{formattedValue}
|
||||
{symbol ? ` ${symbol}` : ''}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnitDisplay;
|
|
@ -6,3 +6,4 @@ export { default as Modal } from './Modal';
|
|||
export { default as UnlockHeader } from './UnlockHeader';
|
||||
export { default as QRCode } from './QRCode';
|
||||
export { default as NewTabLink } from './NewTabLink';
|
||||
export { default as UnitDisplay } from './UnitDisplay';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Big from 'bignumber.js';
|
||||
import BN from 'bn.js';
|
||||
import { Wei } from 'libs/units';
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
generateCompleteTransaction as makeAndSignTx,
|
||||
|
@ -12,7 +13,6 @@ import {
|
|||
} from 'containers/Tabs/Contracts/components/TxModal';
|
||||
import {
|
||||
TxCompare,
|
||||
Props as TCProps,
|
||||
TTxCompare
|
||||
} from 'containers/Tabs/Contracts/components/TxCompare';
|
||||
import { withTx } from 'containers/Tabs/Contracts/components//withTx';
|
||||
|
@ -81,25 +81,13 @@ export const deployHOC = PassedComponent => {
|
|||
}
|
||||
|
||||
private displayCompareTx = (): React.ReactElement<TTxCompare> => {
|
||||
const { nonce, gasLimit, data, value, signedTx, to } = this.state;
|
||||
const { gasPrice, chainId } = this.props;
|
||||
const { signedTx, nonce } = this.state;
|
||||
|
||||
if (!nonce || !signedTx) {
|
||||
throw Error('Can not display raw tx, nonce empty or no signed tx');
|
||||
}
|
||||
|
||||
const props: TCProps = {
|
||||
nonce,
|
||||
gasPrice,
|
||||
chainId,
|
||||
data,
|
||||
gasLimit,
|
||||
to,
|
||||
value,
|
||||
signedTx
|
||||
};
|
||||
|
||||
return <TxCompare {...props} />;
|
||||
return <TxCompare signedTx={signedTx} />;
|
||||
};
|
||||
|
||||
private displayDeployModal = (): React.ReactElement<TTxModal> => {
|
||||
|
@ -143,7 +131,7 @@ export const deployHOC = PassedComponent => {
|
|||
props.wallet,
|
||||
props.nodeLib,
|
||||
props.gasPrice,
|
||||
new Big(gasLimit),
|
||||
Wei(gasLimit),
|
||||
props.chainId,
|
||||
transactionInput,
|
||||
true
|
||||
|
@ -154,7 +142,7 @@ export const deployHOC = PassedComponent => {
|
|||
const address = await this.props.wallet.getAddressString();
|
||||
const nonce = await this.props.nodeLib
|
||||
.getTransactionCount(address)
|
||||
.then(n => new Big(n).toString());
|
||||
.then(n => new BN(n).toString());
|
||||
return this.asyncSetState({ nonce, address });
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Wei, Ether } from 'libs/units';
|
||||
import { Wei } from 'libs/units';
|
||||
import { IWallet } from 'libs/wallet/IWallet';
|
||||
import { RPCNode } from 'libs/nodes';
|
||||
import { NodeConfig, NetworkConfig } from 'config/data';
|
||||
|
@ -7,7 +7,7 @@ import { TShowNotification } from 'actions/notifications';
|
|||
|
||||
export interface Props {
|
||||
wallet: IWallet;
|
||||
balance: Ether;
|
||||
balance: Wei;
|
||||
node: NodeConfig;
|
||||
nodeLib: RPCNode;
|
||||
chainId: NetworkConfig['chainId'];
|
||||
|
|
|
@ -8,6 +8,8 @@ import WalletDecrypt from 'components/WalletDecrypt';
|
|||
import { TShowNotification } from 'actions/notifications';
|
||||
import classnames from 'classnames';
|
||||
import { isValidGasPrice, isValidValue } from 'libs/validators';
|
||||
import { UnitConverter } from 'components/renderCbs';
|
||||
import { getDecimal } from 'libs/units';
|
||||
|
||||
export interface Props {
|
||||
contractFunctions: any;
|
||||
|
@ -164,19 +166,26 @@ export default class InteractExplorer extends Component<Props, State> {
|
|||
</label>
|
||||
<label className="InteractExplorer-field form-group">
|
||||
<h4 className="InteractExplorer-field-label">Value</h4>
|
||||
<input
|
||||
name="value"
|
||||
value={value}
|
||||
<UnitConverter
|
||||
decimal={getDecimal('ether')}
|
||||
onChange={handleInput('value')}
|
||||
placeholder="0"
|
||||
className={classnames(
|
||||
'InteractExplorer-field-input',
|
||||
'form-control',
|
||||
{
|
||||
'is-invalid': !validValue
|
||||
}
|
||||
>
|
||||
{({ convertedUnit, onUserInput }) => (
|
||||
<input
|
||||
name="value"
|
||||
value={convertedUnit}
|
||||
onChange={onUserInput}
|
||||
placeholder="0"
|
||||
className={classnames(
|
||||
'InteractExplorer-field-input',
|
||||
'form-control',
|
||||
{
|
||||
'is-invalid': !validValue
|
||||
}
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</UnitConverter>
|
||||
</label>
|
||||
<button
|
||||
className="InteractExplorer-func-submit btn btn-primary"
|
||||
|
|
|
@ -9,10 +9,9 @@ import {
|
|||
TTxModal
|
||||
} from 'containers/Tabs/Contracts/components/TxModal';
|
||||
import { IUserSendParams } from 'libs/contracts/ABIFunction';
|
||||
import Big from 'bignumber.js';
|
||||
import BN from 'bn.js';
|
||||
import {
|
||||
TxCompare,
|
||||
Props as TCProps,
|
||||
TTxCompare
|
||||
} from 'containers/Tabs/Contracts/components/TxCompare';
|
||||
|
||||
|
@ -111,26 +110,14 @@ class Interact extends Component<IWithTx, State> {
|
|||
}
|
||||
|
||||
private makeCompareTx = (): React.ReactElement<TTxCompare> => {
|
||||
const { nonce, gasLimit, data, value, to, gasPrice } = this.state.rawTx;
|
||||
const { nonce } = this.state.rawTx;
|
||||
const { signedTx } = this.state;
|
||||
const { chainId } = this.props;
|
||||
|
||||
if (!nonce || !signedTx) {
|
||||
throw Error('Can not display raw tx, nonce empty or no signed tx');
|
||||
}
|
||||
|
||||
const props: TCProps = {
|
||||
nonce,
|
||||
gasPrice,
|
||||
chainId,
|
||||
data,
|
||||
gasLimit,
|
||||
to,
|
||||
value,
|
||||
signedTx
|
||||
};
|
||||
|
||||
return <TxCompare {...props} />;
|
||||
return <TxCompare signedTx={signedTx} />;
|
||||
};
|
||||
|
||||
private makeModal = (): React.ReactElement<TTxModal> => {
|
||||
|
@ -182,7 +169,7 @@ class Interact extends Component<IWithTx, State> {
|
|||
const userInputs: IUserSendParams = {
|
||||
input: parsedInputs,
|
||||
to: address,
|
||||
gasLimit: new Big(gasLimit),
|
||||
gasLimit: new BN(gasLimit),
|
||||
value
|
||||
};
|
||||
|
||||
|
@ -198,8 +185,8 @@ class Interact extends Component<IWithTx, State> {
|
|||
}
|
||||
};
|
||||
|
||||
private handleInput = name => ev =>
|
||||
this.setState({ [name]: ev.target.value });
|
||||
private handleInput = name => (ev: React.FormEvent<any>) =>
|
||||
this.setState({ [name]: ev.currentTarget.value });
|
||||
}
|
||||
|
||||
export default withTx(Interact);
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Wei } from 'libs/units';
|
||||
import translate from 'translations';
|
||||
import { decodeTransaction } from 'libs/transaction';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import Code from 'components/ui/Code';
|
||||
export interface Props {
|
||||
nonce: string;
|
||||
gasPrice: Wei;
|
||||
gasLimit: string;
|
||||
to: string;
|
||||
value: string;
|
||||
data: string;
|
||||
chainId: number;
|
||||
signedTx: string;
|
||||
}
|
||||
|
||||
|
@ -17,23 +11,18 @@ export const TxCompare = (props: Props) => {
|
|||
if (!props.signedTx) {
|
||||
return null;
|
||||
}
|
||||
const { signedTx, ...rawTx } = props;
|
||||
const rawTx = decodeTransaction(new EthTx(props.signedTx), false);
|
||||
|
||||
const Left = () => (
|
||||
<div className="form-group">
|
||||
<h4>{translate('SEND_raw')}</h4>
|
||||
<Code>
|
||||
{JSON.stringify(
|
||||
{ ...rawTx, gasPrice: rawTx.gasPrice.toString(16) },
|
||||
null,
|
||||
2
|
||||
)}
|
||||
</Code>
|
||||
<Code>{JSON.stringify(rawTx, null, 2)}</Code>
|
||||
</div>
|
||||
);
|
||||
const Right = () => (
|
||||
<div className="form-group">
|
||||
<h4> {translate('SEND_signed')} </h4>
|
||||
<Code>{signedTx}</Code>
|
||||
<Code>{props.signedTx}</Code>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as configSelectors from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { GWei, Wei, Ether } from 'libs/units';
|
||||
import { toWei, Wei, getDecimal } from 'libs/units';
|
||||
import { connect } from 'react-redux';
|
||||
import { showNotification, TShowNotification } from 'actions/notifications';
|
||||
import { broadcastTx, TBroadcastTx } from 'actions/wallet';
|
||||
|
@ -10,7 +10,7 @@ import { NodeConfig, NetworkConfig } from 'config/data';
|
|||
|
||||
export interface IWithTx {
|
||||
wallet: IWallet;
|
||||
balance: Ether;
|
||||
balance: Wei;
|
||||
node: NodeConfig;
|
||||
nodeLib: RPCNode;
|
||||
chainId: NetworkConfig['chainId'];
|
||||
|
@ -27,7 +27,10 @@ const mapStateToProps = (state: AppState) => ({
|
|||
nodeLib: configSelectors.getNodeLib(state),
|
||||
chainId: configSelectors.getNetworkConfig(state).chainId,
|
||||
networkName: configSelectors.getNetworkConfig(state).name,
|
||||
gasPrice: new GWei(configSelectors.getGasPriceGwei(state)).toWei()
|
||||
gasPrice: toWei(
|
||||
`${configSelectors.getGasPriceGwei(state)}`,
|
||||
getDecimal('gwei')
|
||||
)
|
||||
});
|
||||
|
||||
export const withTx = passedComponent =>
|
||||
|
|
|
@ -1,45 +1,54 @@
|
|||
import React from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import UnitDropdown from './UnitDropdown';
|
||||
import { Ether } from 'libs/units';
|
||||
|
||||
import { Wei } from 'libs/units';
|
||||
import { UnitConverter } from 'components/renderCbs';
|
||||
interface Props {
|
||||
value: string;
|
||||
decimal: number;
|
||||
unit: string;
|
||||
tokens: string[];
|
||||
balance: number | null | Ether;
|
||||
onChange?(value: string, unit: string): void;
|
||||
balance: number | null | Wei;
|
||||
isReadOnly: boolean;
|
||||
onAmountChange(value: string, unit: string): void;
|
||||
onUnitChange(unit: string): void;
|
||||
}
|
||||
|
||||
export default class AmountField extends React.Component {
|
||||
public props: Props;
|
||||
|
||||
get active() {
|
||||
return !this.props.isReadOnly;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { value, unit, onChange, balance } = this.props;
|
||||
const isReadonly = !onChange;
|
||||
const { unit, balance, decimal, isReadOnly } = this.props;
|
||||
return (
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-11">
|
||||
<label>{translate('SEND_amount')}</label>
|
||||
<div className="input-group">
|
||||
<input
|
||||
className={`form-control ${isFinite(Number(value)) &&
|
||||
Number(value) > 0
|
||||
? 'is-valid'
|
||||
: 'is-invalid'}`}
|
||||
type="text"
|
||||
placeholder={translateRaw('SEND_amount_short')}
|
||||
value={value}
|
||||
disabled={isReadonly}
|
||||
onChange={isReadonly ? void 0 : this.onValueChange}
|
||||
/>
|
||||
<UnitConverter decimal={decimal} onChange={this.callWithBaseUnit}>
|
||||
{({ onUserInput, convertedUnit }) => (
|
||||
<input
|
||||
className={`form-control ${isFinite(Number(convertedUnit)) &&
|
||||
Number(convertedUnit) > 0
|
||||
? 'is-valid'
|
||||
: 'is-invalid'}`}
|
||||
type="text"
|
||||
placeholder={translateRaw('SEND_amount_short')}
|
||||
value={convertedUnit}
|
||||
disabled={isReadOnly}
|
||||
onChange={onUserInput}
|
||||
/>
|
||||
)}
|
||||
</UnitConverter>
|
||||
<UnitDropdown
|
||||
value={unit}
|
||||
options={['ether'].concat(this.props.tokens)}
|
||||
onChange={isReadonly ? void 0 : this.onUnitChange}
|
||||
onChange={isReadOnly ? void 0 : this.onUnitChange}
|
||||
/>
|
||||
</div>
|
||||
{!isReadonly &&
|
||||
{!isReadOnly &&
|
||||
balance && (
|
||||
<span className="help-block">
|
||||
<a onClick={this.onSendEverything}>
|
||||
|
@ -54,24 +63,12 @@ export default class AmountField extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
public onUnitChange = (unit: string) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(this.props.value, unit);
|
||||
}
|
||||
};
|
||||
public onUnitChange = (unit: string) =>
|
||||
this.active && this.props.onUnitChange(unit); // thsi needs to be converted unit
|
||||
|
||||
public onValueChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(
|
||||
(e.target as HTMLInputElement).value,
|
||||
this.props.unit
|
||||
);
|
||||
}
|
||||
};
|
||||
public callWithBaseUnit = ({ currentTarget: { value } }) =>
|
||||
this.active && this.props.onAmountChange(value, this.props.unit);
|
||||
|
||||
public onSendEverything = () => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange('everything', this.props.unit);
|
||||
}
|
||||
};
|
||||
public onSendEverything = () =>
|
||||
this.active && this.props.onAmountChange('everything', this.props.unit);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
} from 'selectors/config';
|
||||
import { getTokens, getTxFromState, MergedToken } from 'selectors/wallet';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { UnitDisplay } from 'components/ui';
|
||||
import './ConfirmationModal.scss';
|
||||
|
||||
interface Props {
|
||||
|
@ -27,6 +28,7 @@ interface Props {
|
|||
network: NetworkConfig;
|
||||
lang: string;
|
||||
broadCastTxStatus: BroadcastTransactionStatus;
|
||||
decimal: number;
|
||||
onConfirm(signedTx: string): void;
|
||||
onClose(): void;
|
||||
}
|
||||
|
@ -72,7 +74,8 @@ class ConfirmationModal extends React.Component<Props, State> {
|
|||
network,
|
||||
onClose,
|
||||
broadCastTxStatus,
|
||||
transaction
|
||||
transaction,
|
||||
decimal
|
||||
} = this.props;
|
||||
const { timeToRead } = this.state;
|
||||
const { toAddress, value, gasPrice, data, from, nonce } = decodeTransaction(
|
||||
|
@ -109,77 +112,88 @@ class ConfirmationModal extends React.Component<Props, State> {
|
|||
disableButtons={isBroadcasting}
|
||||
isOpen={true}
|
||||
>
|
||||
{
|
||||
<div className="ConfModal">
|
||||
{isBroadcasting ? (
|
||||
<div className="ConfModal-loading">
|
||||
<Spinner size="5x" />
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="ConfModal-summary">
|
||||
<div className="ConfModal-summary-icon ConfModal-summary-icon--from">
|
||||
<Identicon size="100%" address={from} />
|
||||
</div>
|
||||
<div className="ConfModal-summary-amount">
|
||||
<div className="ConfModal-summary-amount-arrow" />
|
||||
<div className="ConfModal-summary-amount-currency">
|
||||
{value} {symbol}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ConfModal-summary-icon ConfModal-summary-icon--to">
|
||||
<Identicon size="100%" address={toAddress} />
|
||||
<div className="ConfModal">
|
||||
{isBroadcasting ? (
|
||||
<div className="ConfModal-loading">
|
||||
<Spinner size="5x" />
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="ConfModal-summary">
|
||||
<div className="ConfModal-summary-icon ConfModal-summary-icon--from">
|
||||
<Identicon size="100%" address={from} />
|
||||
</div>
|
||||
<div className="ConfModal-summary-amount">
|
||||
<div className="ConfModal-summary-amount-arrow" />
|
||||
<div className="ConfModal-summary-amount-currency">
|
||||
<UnitDisplay
|
||||
decimal={decimal}
|
||||
value={value}
|
||||
symbol={symbol}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="ConfModal-details">
|
||||
<li className="ConfModal-details-detail">
|
||||
You are sending from <code>{from}</code>
|
||||
</li>
|
||||
<li className="ConfModal-details-detail">
|
||||
You are sending to <code>{toAddress}</code>
|
||||
</li>
|
||||
<li className="ConfModal-details-detail">
|
||||
You are sending with a nonce of <code>{nonce}</code>
|
||||
</li>
|
||||
<li className="ConfModal-details-detail">
|
||||
You are sending{' '}
|
||||
<strong>
|
||||
{value} {symbol}
|
||||
</strong>{' '}
|
||||
with a gas price of <strong>{gasPrice} gwei</strong>
|
||||
</li>
|
||||
<li className="ConfModal-details-detail">
|
||||
You are interacting with the{' '}
|
||||
<strong>{node.network}</strong> network provided by{' '}
|
||||
<strong>{node.service}</strong>
|
||||
</li>
|
||||
{!token && (
|
||||
<li className="ConfModal-details-detail">
|
||||
{data ? (
|
||||
<span>
|
||||
You are sending the following data:{' '}
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={data}
|
||||
rows={3}
|
||||
disabled={true}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
'There is no data attached to this transaction'
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<div className="ConfModal-confirm">
|
||||
{translate('SENDModal_Content_3')}
|
||||
<div className="ConfModal-summary-icon ConfModal-summary-icon--to">
|
||||
<Identicon size="100%" address={toAddress} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
<ul className="ConfModal-details">
|
||||
<li className="ConfModal-details-detail">
|
||||
You are sending from <code>{from}</code>
|
||||
</li>
|
||||
<li className="ConfModal-details-detail">
|
||||
You are sending to <code>{toAddress}</code>
|
||||
</li>
|
||||
<li className="ConfModal-details-detail">
|
||||
You are sending with a nonce of <code>{nonce}</code>
|
||||
</li>
|
||||
<li className="ConfModal-details-detail">
|
||||
You are sending{' '}
|
||||
<strong>
|
||||
<UnitDisplay
|
||||
decimal={decimal}
|
||||
value={value}
|
||||
symbol={symbol}
|
||||
/>
|
||||
</strong>{' '}
|
||||
with a gas price of{' '}
|
||||
<strong>
|
||||
<UnitDisplay
|
||||
unit={'gwei'}
|
||||
value={gasPrice}
|
||||
symbol={'gwei'}
|
||||
/>
|
||||
</strong>
|
||||
</li>
|
||||
<li className="ConfModal-details-detail">
|
||||
You are interacting with the <strong>{node.network}</strong>{' '}
|
||||
network provided by <strong>{node.service}</strong>
|
||||
</li>
|
||||
{!token && (
|
||||
<li className="ConfModal-details-detail">
|
||||
{data ? (
|
||||
<span>
|
||||
You are sending the following data:{' '}
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={data}
|
||||
rows={3}
|
||||
disabled={true}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
'There is no data attached to this transaction'
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<div className="ConfModal-confirm">
|
||||
{translate('SENDModal_Content_3')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import Big from 'bignumber.js';
|
||||
// COMPONENTS
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import TabSection from 'containers/TabSection';
|
||||
|
@ -30,7 +29,7 @@ import {
|
|||
getBalanceMinusGasCosts,
|
||||
TransactionInput
|
||||
} from 'libs/transaction';
|
||||
import { Ether, GWei, UnitKey, Wei } from 'libs/units';
|
||||
import { UnitKey, Wei, getDecimal, toWei } from 'libs/units';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
// LIBS
|
||||
import { IWallet, Web3Wallet } from 'libs/wallet';
|
||||
|
@ -93,7 +92,7 @@ interface State {
|
|||
|
||||
interface Props {
|
||||
wallet: IWallet;
|
||||
balance: Ether;
|
||||
balance: Wei;
|
||||
nodeLib: RPCNode;
|
||||
network: NetworkConfig;
|
||||
tokens: MergedToken[];
|
||||
|
@ -249,7 +248,6 @@ export class SendTransaction extends React.Component<Props, State> {
|
|||
const unlocked = !!this.props.wallet;
|
||||
const {
|
||||
to,
|
||||
value,
|
||||
unit,
|
||||
gasLimit,
|
||||
data,
|
||||
|
@ -262,6 +260,10 @@ export class SendTransaction extends React.Component<Props, State> {
|
|||
} = this.state;
|
||||
const { offline, forceOffline, balance } = this.props;
|
||||
const customMessage = customMessages.find(m => m.to === to);
|
||||
const decimal =
|
||||
unit === 'ether'
|
||||
? getDecimal('ether')
|
||||
: (this.state.token && this.state.token.decimal) || 0;
|
||||
const isWeb3Wallet = this.props.wallet instanceof Web3Wallet;
|
||||
return (
|
||||
<TabSection>
|
||||
|
@ -298,28 +300,30 @@ export class SendTransaction extends React.Component<Props, State> {
|
|||
onChange={readOnly ? null : this.onAddressChange}
|
||||
/>
|
||||
<AmountField
|
||||
value={value}
|
||||
unit={unit}
|
||||
decimal={decimal}
|
||||
balance={balance}
|
||||
tokens={this.props.tokenBalances
|
||||
.filter(token => !token.balance.eq(0))
|
||||
.filter(token => !token.balance.eqn(0))
|
||||
.map(token => token.symbol)
|
||||
.sort()}
|
||||
onChange={readOnly ? void 0 : this.onAmountChange}
|
||||
onAmountChange={this.onAmountChange}
|
||||
isReadOnly={readOnly}
|
||||
onUnitChange={this.onUnitChange}
|
||||
/>
|
||||
<GasField
|
||||
value={gasLimit}
|
||||
onChange={readOnly ? void 0 : this.onGasChange}
|
||||
/>
|
||||
{(offline || forceOffline) && (
|
||||
<div>
|
||||
<NonceField
|
||||
value={nonce}
|
||||
onChange={this.onNonceChange}
|
||||
placeholder={'0'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<NonceField
|
||||
value={nonce}
|
||||
onChange={this.onNonceChange}
|
||||
placeholder={'0'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{unit === 'ether' && (
|
||||
<DataField
|
||||
value={data}
|
||||
|
@ -433,6 +437,7 @@ export class SendTransaction extends React.Component<Props, State> {
|
|||
{transaction &&
|
||||
showTxConfirm && (
|
||||
<ConfirmationModal
|
||||
decimal={decimal}
|
||||
fromAddress={this.state.walletAddress}
|
||||
signedTx={transaction.signedTx}
|
||||
onClose={this.hideConfirmTx}
|
||||
|
@ -565,12 +570,11 @@ export class SendTransaction extends React.Component<Props, State> {
|
|||
if (unit === 'ether') {
|
||||
const { balance, gasPrice } = this.props;
|
||||
const { gasLimit } = this.state;
|
||||
const weiBalance = balance.toWei();
|
||||
const bigGasLimit = new Big(gasLimit);
|
||||
const bigGasLimit = Wei(gasLimit);
|
||||
value = getBalanceMinusGasCosts(
|
||||
bigGasLimit,
|
||||
gasPrice,
|
||||
weiBalance
|
||||
balance
|
||||
).toString();
|
||||
} else {
|
||||
const tokenBalance = this.props.tokenBalances.find(
|
||||
|
@ -588,23 +592,29 @@ export class SendTransaction extends React.Component<Props, State> {
|
|||
if (value === 'everything') {
|
||||
value = this.handleEverythingAmountChange(value, unit);
|
||||
}
|
||||
let transaction = this.state.transaction;
|
||||
let generateDisabled = this.state.generateDisabled;
|
||||
if (unit && unit !== this.state.unit) {
|
||||
value = '';
|
||||
transaction = null;
|
||||
generateDisabled = true;
|
||||
}
|
||||
const token = this.props.tokens.find(x => x.symbol === unit);
|
||||
|
||||
this.setState({
|
||||
value,
|
||||
unit,
|
||||
token,
|
||||
transaction,
|
||||
generateDisabled
|
||||
unit
|
||||
});
|
||||
};
|
||||
|
||||
public onUnitChange = (unit: UnitKey) => {
|
||||
const token = this.props.tokens.find(x => x.symbol === unit);
|
||||
let stateToSet: any = { token };
|
||||
|
||||
if (unit !== this.state.unit) {
|
||||
stateToSet = {
|
||||
...stateToSet,
|
||||
transaction: null,
|
||||
generateDisabled: true,
|
||||
unit
|
||||
};
|
||||
}
|
||||
|
||||
this.setState(stateToSet);
|
||||
};
|
||||
|
||||
public resetJustTx = async (): Promise<any> =>
|
||||
new Promise(resolve =>
|
||||
this.setState(
|
||||
|
@ -628,7 +638,7 @@ export class SendTransaction extends React.Component<Props, State> {
|
|||
to,
|
||||
data
|
||||
};
|
||||
const bigGasLimit = new Big(gasLimit);
|
||||
const bigGasLimit = Wei(gasLimit);
|
||||
|
||||
if (!(wallet instanceof Web3Wallet)) {
|
||||
return;
|
||||
|
@ -673,7 +683,7 @@ export class SendTransaction extends React.Component<Props, State> {
|
|||
to,
|
||||
data
|
||||
};
|
||||
const bigGasLimit = new Big(gasLimit);
|
||||
const bigGasLimit = Wei(gasLimit);
|
||||
try {
|
||||
const signedTx = await generateCompleteTransaction(
|
||||
wallet,
|
||||
|
@ -722,7 +732,7 @@ function mapStateToProps(state: AppState) {
|
|||
nodeLib: getNodeLib(state),
|
||||
network: getNetworkConfig(state),
|
||||
tokens: getTokens(state),
|
||||
gasPrice: new GWei(getGasPriceGwei(state)).toWei(),
|
||||
gasPrice: toWei(`${getGasPriceGwei(state)}`, getDecimal('gwei')),
|
||||
transactions: state.wallet.transactions,
|
||||
offline: state.config.offline,
|
||||
forceOffline: state.config.forceOffline
|
||||
|
|
|
@ -18,7 +18,9 @@ function promiseFromChildProcess(command): Promise<any> {
|
|||
}
|
||||
|
||||
async function privToAddrViaDocker(privKeyWallet: IFullWallet) {
|
||||
const command = `docker run -e key=${privKeyWallet.getPrivateKeyString()} ${dockerImage}:${dockerTag}`;
|
||||
const command = `docker run -e key=${privKeyWallet.getPrivateKeyString()} ${
|
||||
dockerImage
|
||||
}:${dockerTag}`;
|
||||
const dockerOutput = await promiseFromChildProcess(command);
|
||||
const newlineStrippedDockerOutput = dockerOutput.replace(
|
||||
/(\r\n|\n|\r)/gm,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import abi from 'ethereumjs-abi';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import Big, { BigNumber } from 'bignumber.js';
|
||||
import BN from 'bn.js';
|
||||
import { INode } from 'libs/nodes/INode';
|
||||
import { FuncParams, FunctionOutputMappings, Output, Input } from './types';
|
||||
import {
|
||||
|
@ -12,7 +12,7 @@ import { ISetConfigForTx } from './index';
|
|||
export interface IUserSendParams {
|
||||
input;
|
||||
to: string;
|
||||
gasLimit: BigNumber;
|
||||
gasLimit: BN;
|
||||
value: string;
|
||||
}
|
||||
export type ISendParams = IUserSendParams & ISetConfigForTx;
|
||||
|
@ -153,18 +153,11 @@ EncodedCall:${data}`);
|
|||
|
||||
return valueMapping[type]
|
||||
? valueMapping[type](value)
|
||||
: this.isBigNumber(value) ? value.toString() : value;
|
||||
: BN.isBN(value) ? value.toString() : value;
|
||||
};
|
||||
|
||||
private parsePreEncodedValue = (_: string, value: any) =>
|
||||
this.isBigNumber(value) ? value.toString() : value;
|
||||
|
||||
private isBigNumber = (object: object) =>
|
||||
object instanceof Big ||
|
||||
(object &&
|
||||
object.constructor &&
|
||||
(object.constructor.name === 'BigNumber' ||
|
||||
object.constructor.name === 'BN'));
|
||||
BN.isBN(value) ? value.toString() : value;
|
||||
|
||||
private makeFuncParams = () =>
|
||||
this.inputs.reduce((accumulator, currInput) => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { BigNumber } from 'bignumber.js';
|
||||
import BN from 'bn.js';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import Contract, { ABI } from 'libs/contract';
|
||||
|
||||
interface Transfer {
|
||||
to: string;
|
||||
value: string;
|
||||
value: BN;
|
||||
}
|
||||
|
||||
const erc20Abi: ABI = [
|
||||
|
@ -59,7 +59,7 @@ class ERC20 extends Contract {
|
|||
return this.call('balanceOf', [address]);
|
||||
}
|
||||
|
||||
public transfer(to: string, value: BigNumber): string {
|
||||
public transfer(to: string, value: BN): string {
|
||||
return this.call('transfer', [to, value.toString()]);
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ class ERC20 extends Contract {
|
|||
const decodedArgs = this.decodeArgs(this.getMethodAbi('transfer'), data);
|
||||
return {
|
||||
to: toChecksumAddress(`0x${decodedArgs[0].toString(16)}`),
|
||||
value: decodedArgs[1].toString(10)
|
||||
value: decodedArgs[1]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { BigNumber } from 'bignumber.js';
|
||||
import { Token } from 'config/data';
|
||||
import { TransactionWithoutGas } from 'libs/messages';
|
||||
import { Wei } from 'libs/units';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
|
||||
export interface TxObj {
|
||||
to: string;
|
||||
|
@ -9,9 +8,9 @@ export interface TxObj {
|
|||
}
|
||||
export interface INode {
|
||||
getBalance(address: string): Promise<Wei>;
|
||||
getTokenBalance(address: string, token: Token): Promise<BigNumber>;
|
||||
getTokenBalances(address: string, tokens: Token[]): Promise<BigNumber[]>;
|
||||
estimateGas(tx: TransactionWithoutGas): Promise<BigNumber>;
|
||||
getTokenBalance(address: string, token: Token): Promise<TokenValue>;
|
||||
getTokenBalances(address: string, tokens: Token[]): Promise<TokenValue[]>;
|
||||
estimateGas(tx: TransactionWithoutGas): Promise<Wei>;
|
||||
getTransactionCount(address: string): Promise<string>;
|
||||
sendRawTx(tx: string): Promise<string>;
|
||||
sendCallRequest(txObj: TxObj): Promise<string>;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import Big, { BigNumber } from 'bignumber.js';
|
||||
import { Token } from 'config/data';
|
||||
import { TransactionWithoutGas } from 'libs/messages';
|
||||
import { Wei } from 'libs/units';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
import { INode, TxObj } from '../INode';
|
||||
import RPCClient from './client';
|
||||
import RPCRequests from './requests';
|
||||
|
@ -30,50 +29,46 @@ export default class RpcNode implements INode {
|
|||
if (response.error) {
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
return new Wei(String(response.result));
|
||||
return Wei(response.result);
|
||||
});
|
||||
}
|
||||
|
||||
public estimateGas(transaction: TransactionWithoutGas): Promise<BigNumber> {
|
||||
public estimateGas(transaction: TransactionWithoutGas): Promise<Wei> {
|
||||
return this.client
|
||||
.call(this.requests.estimateGas(transaction))
|
||||
.then(response => {
|
||||
if (response.error) {
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
return new Big(String(response.result));
|
||||
return Wei(response.result);
|
||||
});
|
||||
}
|
||||
|
||||
public getTokenBalance(address: string, token: Token): Promise<BigNumber> {
|
||||
public getTokenBalance(address: string, token: Token): Promise<TokenValue> {
|
||||
return this.client
|
||||
.call(this.requests.getTokenBalance(address, token))
|
||||
.then(response => {
|
||||
if (response.error) {
|
||||
// TODO - Error handling
|
||||
return new Big(0);
|
||||
return TokenValue('0');
|
||||
}
|
||||
return new Big(String(response.result)).div(
|
||||
new Big(10).pow(token.decimal)
|
||||
);
|
||||
return TokenValue(response.result);
|
||||
});
|
||||
}
|
||||
|
||||
public getTokenBalances(
|
||||
address: string,
|
||||
tokens: Token[]
|
||||
): Promise<BigNumber[]> {
|
||||
): Promise<TokenValue[]> {
|
||||
return this.client
|
||||
.batch(tokens.map(t => this.requests.getTokenBalance(address, t)))
|
||||
.then(response => {
|
||||
return response.map((item, idx) => {
|
||||
return response.map(item => {
|
||||
// FIXME wrap in maybe-like
|
||||
if (item.error) {
|
||||
return new Big(0);
|
||||
return TokenValue('0');
|
||||
}
|
||||
return new Big(String(item.result)).div(
|
||||
new Big(10).pow(tokens[idx].decimal)
|
||||
);
|
||||
return TokenValue(item.result);
|
||||
});
|
||||
});
|
||||
// TODO - Error handling
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Ref: https://github.com/ethereum/wiki/wiki/JSON-RPC
|
||||
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import BN from 'bn.js';
|
||||
import { toBuffer } from 'ethereumjs-util';
|
||||
|
||||
// When encoding QUANTITIES (integers, numbers): encode as hex, prefix with "0x", the most compact representation (slight exception: zero should be represented as "0x0").
|
||||
export function hexEncodeQuantity(value: BigNumber): string {
|
||||
export function hexEncodeQuantity(value: BN): string {
|
||||
return '0x' + (value.toString(16) || '0');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import Big, { BigNumber } from 'bignumber.js';
|
||||
import { Token } from 'config/data';
|
||||
import { TransactionWithoutGas } from 'libs/messages';
|
||||
import { Wei } from 'libs/units';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
import { INode, TxObj } from '../INode';
|
||||
import ERC20 from 'libs/erc20';
|
||||
|
||||
|
@ -18,6 +17,7 @@ export default class Web3Node implements INode {
|
|||
if (err) {
|
||||
return reject(err.message);
|
||||
}
|
||||
// web3 return string
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
|
@ -29,12 +29,13 @@ export default class Web3Node implements INode {
|
|||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(new Wei(res.toString()));
|
||||
// web3 returns BigNumber
|
||||
resolve(Wei(res.toString()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public estimateGas(transaction: TransactionWithoutGas): Promise<BigNumber> {
|
||||
public estimateGas(transaction: TransactionWithoutGas): Promise<Wei> {
|
||||
return new Promise((resolve, reject) =>
|
||||
this.web3.eth.estimateGas(
|
||||
{
|
||||
|
@ -45,13 +46,14 @@ export default class Web3Node implements INode {
|
|||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(new Big(res.toString()));
|
||||
// web3 returns number
|
||||
resolve(Wei(res));
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public getTokenBalance(address: string, token: Token): Promise<BigNumber> {
|
||||
public getTokenBalance(address: string, token: Token): Promise<TokenValue> {
|
||||
return new Promise(resolve => {
|
||||
this.web3.eth.call(
|
||||
{
|
||||
|
@ -62,13 +64,10 @@ export default class Web3Node implements INode {
|
|||
(err, res) => {
|
||||
if (err) {
|
||||
// TODO - Error handling
|
||||
return resolve(new Big(0));
|
||||
return resolve(TokenValue('0'));
|
||||
}
|
||||
|
||||
const bigResult = new Big(res.toString());
|
||||
const bigTokenBase = new Big(10).pow(token.decimal);
|
||||
|
||||
resolve(bigResult.div(bigTokenBase));
|
||||
// web3 returns string
|
||||
resolve(TokenValue(res));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -77,11 +76,11 @@ export default class Web3Node implements INode {
|
|||
public getTokenBalances(
|
||||
address: string,
|
||||
tokens: Token[]
|
||||
): Promise<BigNumber[]> {
|
||||
): Promise<TokenValue[]> {
|
||||
return new Promise(resolve => {
|
||||
const batch = this.web3.createBatch();
|
||||
const totalCount = tokens.length;
|
||||
const returnArr = new Array<BigNumber>(totalCount);
|
||||
const returnArr = new Array<TokenValue>(totalCount);
|
||||
let finishCount = 0;
|
||||
|
||||
tokens.forEach((token, index) =>
|
||||
|
@ -92,20 +91,19 @@ export default class Web3Node implements INode {
|
|||
data: ERC20.balanceOf(address)
|
||||
},
|
||||
'pending',
|
||||
(err, res) => finish(token, index, err, res)
|
||||
(err, res) => finish(index, err, res)
|
||||
)
|
||||
)
|
||||
);
|
||||
batch.execute();
|
||||
|
||||
function finish(token, index, err, res) {
|
||||
function finish(index, err, res) {
|
||||
if (err) {
|
||||
// TODO - Error handling
|
||||
returnArr[index] = new Big(0);
|
||||
returnArr[index] = TokenValue('0');
|
||||
} else {
|
||||
returnArr[index] = new Big(res.toString()).div(
|
||||
new Big(10).pow(token.decimal)
|
||||
);
|
||||
// web3 returns string
|
||||
returnArr[index] = TokenValue(res);
|
||||
}
|
||||
|
||||
finishCount++;
|
||||
|
@ -122,6 +120,7 @@ export default class Web3Node implements INode {
|
|||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
// web3 returns number
|
||||
resolve(txCount.toString());
|
||||
})
|
||||
);
|
||||
|
@ -133,6 +132,7 @@ export default class Web3Node implements INode {
|
|||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
// web3 return string
|
||||
resolve(txHash);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -5,19 +5,11 @@ import ERC20 from 'libs/erc20';
|
|||
import { TransactionWithoutGas } from 'libs/messages';
|
||||
import { RPCNode } from 'libs/nodes';
|
||||
import { INode } from 'libs/nodes/INode';
|
||||
import {
|
||||
Ether,
|
||||
toTokenUnit,
|
||||
UnitKey,
|
||||
Wei,
|
||||
toTokenDisplay,
|
||||
toUnit
|
||||
} from 'libs/units';
|
||||
import { UnitKey, Wei, TokenValue, toTokenBase } from 'libs/units';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import { stripHexPrefixAndLower, valueToHex, sanitizeHex } from 'libs/values';
|
||||
import { stripHexPrefixAndLower, sanitizeHex, toHexWei } from 'libs/values';
|
||||
import { IWallet, Web3Wallet } from 'libs/wallet';
|
||||
import { translateRaw } from 'translations';
|
||||
import Big, { BigNumber } from 'bignumber.js';
|
||||
|
||||
export interface TransactionInput {
|
||||
token?: Token | null;
|
||||
|
@ -37,7 +29,7 @@ export interface BaseTransaction {
|
|||
to: string;
|
||||
value: string;
|
||||
data: string;
|
||||
gasLimit: BigNumber | string;
|
||||
gasLimit: Wei | string;
|
||||
gasPrice: Wei | string;
|
||||
chainId: number;
|
||||
}
|
||||
|
@ -82,12 +74,12 @@ export function getTransactionFields(tx: EthTx) {
|
|||
function getValue(
|
||||
token: Token | null | undefined,
|
||||
tx: ExtendedRawTransaction
|
||||
): BigNumber {
|
||||
): Wei {
|
||||
let value;
|
||||
if (token) {
|
||||
value = new Big(ERC20.$transfer(tx.data).value);
|
||||
value = Wei(ERC20.$transfer(tx.data).value);
|
||||
} else {
|
||||
value = new Big(tx.value);
|
||||
value = Wei(tx.value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -99,11 +91,14 @@ async function getBalance(
|
|||
) {
|
||||
const { from } = tx;
|
||||
const ETHBalance = await node.getBalance(from);
|
||||
let balance;
|
||||
let balance: Wei;
|
||||
if (token) {
|
||||
balance = toTokenUnit(await node.getTokenBalance(tx.from, token), token);
|
||||
balance = toTokenBase(
|
||||
await node.getTokenBalance(tx.from, token).toString(),
|
||||
token.decimal
|
||||
);
|
||||
} else {
|
||||
balance = ETHBalance.amount;
|
||||
balance = ETHBalance;
|
||||
}
|
||||
return {
|
||||
balance,
|
||||
|
@ -115,7 +110,7 @@ async function balanceCheck(
|
|||
node: INode,
|
||||
tx: ExtendedRawTransaction,
|
||||
token: Token | null | undefined,
|
||||
value: BigNumber,
|
||||
value: Wei,
|
||||
gasCost: Wei
|
||||
) {
|
||||
// Ensure their balance exceeds the amount they're sending
|
||||
|
@ -125,9 +120,9 @@ async function balanceCheck(
|
|||
}
|
||||
// ensure gas cost is not greaterThan current eth balance
|
||||
// TODO check that eth balance is not lesser than txAmount + gasCost
|
||||
if (gasCost.amount.gt(ETHBalance.amount)) {
|
||||
if (gasCost.gt(ETHBalance)) {
|
||||
throw new Error(
|
||||
`gasCost: ${gasCost.amount} greaterThan ETHBalance: ${ETHBalance.amount}`
|
||||
`gasCost: ${gasCost.toString()} greaterThan ETHBalance: ${ETHBalance.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +131,7 @@ function generateTxValidation(
|
|||
to: string,
|
||||
token: Token | null | undefined,
|
||||
data: string,
|
||||
gasLimit: BigNumber | string,
|
||||
gasLimit: Wei | string,
|
||||
gasPrice: Wei | string,
|
||||
skipEthAddressValidation: boolean
|
||||
) {
|
||||
|
@ -154,16 +149,16 @@ function generateTxValidation(
|
|||
// Reject gas limit under 21000 (Minimum for transaction)
|
||||
// Reject if limit over 5000000
|
||||
// TODO: Make this dynamic, the limit shifts
|
||||
if (gasLimit.lessThan(21000)) {
|
||||
if (gasLimit.ltn(21000)) {
|
||||
throw new Error('Gas limit must be at least 21000 for transactions');
|
||||
}
|
||||
// Reject gasLimit over 5000000gwei
|
||||
if (gasLimit.greaterThan(5000000)) {
|
||||
if (gasLimit.gtn(5000000)) {
|
||||
throw new Error(translateRaw('GETH_GasLimit'));
|
||||
}
|
||||
// Reject gasPrice over 1000gwei (1000000000000)
|
||||
const gwei = new Big('1000000000000');
|
||||
if (gasPrice.amount.greaterThan(gwei)) {
|
||||
const gwei = Wei('1000000000000');
|
||||
if (gasPrice.gt(gwei)) {
|
||||
throw new Error(
|
||||
'Gas price too high. Please contact support if this was not a mistake.'
|
||||
);
|
||||
|
@ -186,7 +181,7 @@ export async function generateCompleteTransactionFromRawTransaction(
|
|||
throw Error('Gas Limit and Gas Price should be of type bignumber');
|
||||
}
|
||||
// computed gas cost (gasprice * gaslimit)
|
||||
const gasCost: Wei = new Wei(gasPrice.amount.times(gasLimit));
|
||||
const gasCost: Wei = Wei(gasPrice.mul(gasLimit));
|
||||
// get amount value (either in ETH or in Token)
|
||||
const value = getValue(token, tx);
|
||||
// if not offline, ensure that balance exceeds costs
|
||||
|
@ -225,14 +220,14 @@ export async function formatTxInput(
|
|||
return {
|
||||
to,
|
||||
from: await wallet.getAddressString(),
|
||||
value: valueToHex(new Ether(value)),
|
||||
value: toHexWei(value), //turn users ether to wei
|
||||
data
|
||||
};
|
||||
} else {
|
||||
if (!token) {
|
||||
throw new Error('No matching token');
|
||||
}
|
||||
const bigAmount = new Big(value);
|
||||
const bigAmount = TokenValue(value);
|
||||
const ERC20Data = ERC20.transfer(to, bigAmount);
|
||||
return {
|
||||
to: token.address,
|
||||
|
@ -247,7 +242,7 @@ export async function confirmAndSendWeb3Transaction(
|
|||
wallet: Web3Wallet,
|
||||
nodeLib: RPCNode,
|
||||
gasPrice: Wei,
|
||||
gasLimit: BigNumber,
|
||||
gasLimit: Wei,
|
||||
chainId: number,
|
||||
transactionInput: TransactionInput
|
||||
): Promise<string> {
|
||||
|
@ -273,7 +268,7 @@ export async function generateCompleteTransaction(
|
|||
wallet: IWallet,
|
||||
nodeLib: RPCNode,
|
||||
gasPrice: Wei,
|
||||
gasLimit: BigNumber,
|
||||
gasLimit: Wei,
|
||||
chainId: number,
|
||||
transactionInput: TransactionInput,
|
||||
skipValidation: boolean,
|
||||
|
@ -307,34 +302,34 @@ export async function generateCompleteTransaction(
|
|||
|
||||
// TODO determine best place for helper function
|
||||
export function getBalanceMinusGasCosts(
|
||||
gasLimit: BigNumber,
|
||||
gasLimit: Wei,
|
||||
gasPrice: Wei,
|
||||
balance: Wei
|
||||
): Ether {
|
||||
const weiGasCosts = gasPrice.amount.times(gasLimit);
|
||||
const weiBalanceMinusGasCosts = balance.amount.minus(weiGasCosts);
|
||||
return new Ether(weiBalanceMinusGasCosts);
|
||||
): Wei {
|
||||
const weiGasCosts = gasPrice.mul(gasLimit);
|
||||
const weiBalanceMinusGasCosts = balance.sub(weiGasCosts);
|
||||
return Wei(weiBalanceMinusGasCosts);
|
||||
}
|
||||
|
||||
export function decodeTransaction(transaction: EthTx, token: Token | false) {
|
||||
const { to, value, data, gasPrice, nonce, from } = getTransactionFields(
|
||||
transaction
|
||||
);
|
||||
let fixedValue;
|
||||
let fixedValue: TokenValue;
|
||||
let toAddress;
|
||||
|
||||
if (token) {
|
||||
const tokenData = ERC20.$transfer(data);
|
||||
fixedValue = toTokenDisplay(new Big(tokenData.value), token).toString();
|
||||
fixedValue = tokenData.value;
|
||||
toAddress = tokenData.to;
|
||||
} else {
|
||||
fixedValue = toUnit(new Big(value, 16), 'wei', 'ether').toString();
|
||||
fixedValue = Wei(value);
|
||||
toAddress = to;
|
||||
}
|
||||
|
||||
return {
|
||||
value: fixedValue,
|
||||
gasPrice: toUnit(new Big(gasPrice, 16), 'wei', 'gwei').toString(),
|
||||
gasPrice: Wei(gasPrice),
|
||||
data,
|
||||
toAddress,
|
||||
nonce,
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
import { Token } from 'config/data';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import { addHexPrefix, padToEven, toChecksumAddress } from 'ethereumjs-util';
|
||||
import ERC20 from 'libs/erc20';
|
||||
import { TransactionWithoutGas } from 'libs/messages';
|
||||
import { RPCNode } from 'libs/nodes';
|
||||
import { INode } from 'libs/nodes/INode';
|
||||
import { UnitKey, Wei, TokenValue, toTokenBase } from 'libs/units';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import { stripHexPrefixAndLower, toHexWei, sanitizeHex } from 'libs/values';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import { translateRaw } from 'translations';
|
||||
|
||||
export interface TransactionInput {
|
||||
token?: Token | null;
|
||||
unit: UnitKey;
|
||||
value: string;
|
||||
to: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface BroadcastTransactionStatus {
|
||||
isBroadcasting: boolean;
|
||||
signedTx: string;
|
||||
successfullyBroadcast: boolean;
|
||||
}
|
||||
|
||||
export interface BaseTransaction {
|
||||
to: string;
|
||||
value: string;
|
||||
data: string;
|
||||
gasLimit: Wei | string;
|
||||
gasPrice: Wei | string;
|
||||
chainId: number;
|
||||
}
|
||||
|
||||
export interface RawTransaction extends BaseTransaction {
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
export interface ExtendedRawTransaction extends RawTransaction {
|
||||
// non-standard, legacy
|
||||
from: string;
|
||||
}
|
||||
|
||||
export interface CompleteTransaction extends RawTransaction {
|
||||
rawTx: string;
|
||||
signedTx: string;
|
||||
}
|
||||
|
||||
// Get useable fields from an EthTx object.
|
||||
export function getTransactionFields(tx: EthTx) {
|
||||
// For some crazy reason, toJSON spits out an array, not keyed values.
|
||||
const [nonce, gasPrice, gasLimit, to, value, data, v, r, s] = tx.toJSON();
|
||||
|
||||
return {
|
||||
// No value comes back as '0x', but most things expect '0x00'
|
||||
value: value === '0x' ? '0x00' : value,
|
||||
// If data is 0x, it might as well not be there
|
||||
data: data === '0x' ? null : data,
|
||||
// To address is unchecksummed, which could cause mismatches in comparisons
|
||||
to: toChecksumAddress(to),
|
||||
from: sanitizeHex(tx.getSenderAddress().toString('hex')),
|
||||
// Everything else is as-is
|
||||
nonce,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
v,
|
||||
r,
|
||||
s
|
||||
};
|
||||
}
|
||||
|
||||
function getValue(
|
||||
token: Token | null | undefined,
|
||||
tx: ExtendedRawTransaction
|
||||
): Wei {
|
||||
let value;
|
||||
if (token) {
|
||||
value = Wei(ERC20.$transfer(tx.data).value);
|
||||
} else {
|
||||
value = Wei(tx.value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async function getBalance(
|
||||
node: INode,
|
||||
tx: ExtendedRawTransaction,
|
||||
token: Token | null | undefined
|
||||
) {
|
||||
const { from } = tx;
|
||||
const ETHBalance = await node.getBalance(from);
|
||||
let balance: Wei;
|
||||
if (token) {
|
||||
balance = toTokenBase(
|
||||
await node.getTokenBalance(tx.from, token).toString(),
|
||||
token.decimal
|
||||
);
|
||||
} else {
|
||||
balance = ETHBalance;
|
||||
}
|
||||
return {
|
||||
balance,
|
||||
ETHBalance
|
||||
};
|
||||
}
|
||||
|
||||
async function balanceCheck(
|
||||
node: INode,
|
||||
tx: ExtendedRawTransaction,
|
||||
token: Token | null | undefined,
|
||||
value: Wei,
|
||||
gasCost: Wei
|
||||
) {
|
||||
// Ensure their balance exceeds the amount they're sending
|
||||
const { balance, ETHBalance } = await getBalance(node, tx, token);
|
||||
if (value.gt(balance)) {
|
||||
throw new Error(translateRaw('GETH_Balance'));
|
||||
}
|
||||
// ensure gas cost is not greaterThan current eth balance
|
||||
// TODO check that eth balance is not lesser than txAmount + gasCost
|
||||
if (gasCost.gt(ETHBalance)) {
|
||||
throw new Error(
|
||||
`gasCost: ${gasCost.toString()} greaterThan ETHBalance: ${ETHBalance.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function generateTxValidation(
|
||||
to: string,
|
||||
token: Token | null | undefined,
|
||||
data: string,
|
||||
gasLimit: Wei | string,
|
||||
gasPrice: Wei | string,
|
||||
skipEthAddressValidation: boolean
|
||||
) {
|
||||
// Reject bad addresses
|
||||
if (!isValidETHAddress(to) && !skipEthAddressValidation) {
|
||||
throw new Error(translateRaw('ERROR_5'));
|
||||
}
|
||||
// Reject token transactions without data
|
||||
if (token && !data) {
|
||||
throw new Error('Tokens must be sent with data');
|
||||
}
|
||||
if (typeof gasLimit === 'string' || typeof gasPrice === 'string') {
|
||||
throw Error('Gas Limit and Gas Price should be of type bignumber');
|
||||
}
|
||||
// Reject gas limit under 21000 (Minimum for transaction)
|
||||
// Reject if limit over 5000000
|
||||
// TODO: Make this dynamic, the limit shifts
|
||||
if (gasLimit.ltn(21000)) {
|
||||
throw new Error('Gas limit must be at least 21000 for transactions');
|
||||
}
|
||||
// Reject gasLimit over 5000000gwei
|
||||
if (gasLimit.gtn(5000000)) {
|
||||
throw new Error(translateRaw('GETH_GasLimit'));
|
||||
}
|
||||
// Reject gasPrice over 1000gwei (1000000000000)
|
||||
const gwei = Wei('1000000000000');
|
||||
if (gasPrice.gt(gwei)) {
|
||||
throw new Error(
|
||||
'Gas price too high. Please contact support if this was not a mistake.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateCompleteTransactionFromRawTransaction(
|
||||
node: INode,
|
||||
tx: ExtendedRawTransaction,
|
||||
wallet: IWallet,
|
||||
token: Token | null | undefined,
|
||||
skipValidation: boolean,
|
||||
offline?: boolean
|
||||
): Promise<CompleteTransaction> {
|
||||
const { to, data, gasLimit, gasPrice, chainId, nonce } = tx;
|
||||
// validation
|
||||
generateTxValidation(to, token, data, gasLimit, gasPrice, skipValidation);
|
||||
// duplicated from generateTxValidation -- typescript bug
|
||||
if (typeof gasLimit === 'string' || typeof gasPrice === 'string') {
|
||||
throw Error('Gas Limit and Gas Price should be of type bignumber');
|
||||
}
|
||||
// computed gas cost (gasprice * gaslimit)
|
||||
const gasCost: Wei = Wei(gasPrice.mul(gasLimit));
|
||||
// get amount value (either in ETH or in Token)
|
||||
const value = getValue(token, tx);
|
||||
// if not offline, ensure that balance exceeds costs
|
||||
if (!offline) {
|
||||
await balanceCheck(node, tx, token, value, gasCost);
|
||||
}
|
||||
// Taken from v3's `sanitizeHex`, ensures that the value is a %2 === 0
|
||||
// prefix'd hex value.
|
||||
const cleanHex = hex => addHexPrefix(padToEven(stripHexPrefixAndLower(hex)));
|
||||
const cleanedRawTx = {
|
||||
nonce: cleanHex(nonce),
|
||||
gasPrice: cleanHex(gasPrice.toString(16)),
|
||||
gasLimit: cleanHex(gasLimit.toString(16)),
|
||||
to: toChecksumAddress(cleanHex(to)),
|
||||
value: token ? '0x00' : cleanHex(value.toString(16)),
|
||||
data: data ? cleanHex(data) : '',
|
||||
chainId: chainId || 1
|
||||
};
|
||||
|
||||
// Sign the transaction
|
||||
const rawTxJson = JSON.stringify(cleanedRawTx);
|
||||
const signedTx = await wallet.signRawTransaction(cleanedRawTx);
|
||||
|
||||
return {
|
||||
...cleanedRawTx,
|
||||
rawTx: rawTxJson,
|
||||
signedTx
|
||||
};
|
||||
}
|
||||
|
||||
export async function formatTxInput(
|
||||
wallet: IWallet,
|
||||
{ token, unit, value, to, data }: TransactionInput
|
||||
): Promise<TransactionWithoutGas> {
|
||||
if (unit === 'ether') {
|
||||
return {
|
||||
to,
|
||||
from: await wallet.getAddressString(),
|
||||
value: toHexWei(value), //turn users ether to wei
|
||||
data
|
||||
};
|
||||
} else {
|
||||
if (!token) {
|
||||
throw new Error('No matching token');
|
||||
}
|
||||
const bigAmount = TokenValue(value);
|
||||
const ERC20Data = ERC20.transfer(to, bigAmount);
|
||||
return {
|
||||
to: token.address,
|
||||
from: await wallet.getAddressString(),
|
||||
value: '0x0',
|
||||
data: ERC20Data
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateCompleteTransaction(
|
||||
wallet: IWallet,
|
||||
nodeLib: RPCNode,
|
||||
gasPrice: Wei,
|
||||
gasLimit: Wei,
|
||||
chainId: number,
|
||||
transactionInput: TransactionInput,
|
||||
skipValidation: boolean,
|
||||
nonce?: number | null,
|
||||
offline?: boolean
|
||||
): Promise<CompleteTransaction> {
|
||||
const { token } = transactionInput;
|
||||
const { from, to, value, data } = await formatTxInput(
|
||||
wallet,
|
||||
transactionInput
|
||||
);
|
||||
const transaction: ExtendedRawTransaction = {
|
||||
nonce: nonce ? `0x${nonce}` : await nodeLib.getTransactionCount(from),
|
||||
from,
|
||||
to,
|
||||
gasLimit,
|
||||
value,
|
||||
data,
|
||||
chainId,
|
||||
gasPrice
|
||||
};
|
||||
return await generateCompleteTransactionFromRawTransaction(
|
||||
nodeLib,
|
||||
transaction,
|
||||
wallet,
|
||||
token,
|
||||
skipValidation,
|
||||
offline
|
||||
);
|
||||
}
|
||||
|
||||
// TODO determine best place for helper function
|
||||
export function getBalanceMinusGasCosts(
|
||||
gasLimit: Wei,
|
||||
gasPrice: Wei,
|
||||
balance: Wei
|
||||
): Wei {
|
||||
const weiGasCosts = gasPrice.mul(gasLimit);
|
||||
const weiBalanceMinusGasCosts = balance.sub(weiGasCosts);
|
||||
return Wei(weiBalanceMinusGasCosts);
|
||||
}
|
||||
|
||||
export function decodeTransaction(transaction: EthTx, token: Token | false) {
|
||||
const { to, value, data, gasPrice, nonce, from } = getTransactionFields(
|
||||
transaction
|
||||
);
|
||||
let fixedValue: TokenValue;
|
||||
let toAddress;
|
||||
|
||||
if (token) {
|
||||
const tokenData = ERC20.$transfer(data);
|
||||
fixedValue = tokenData.value;
|
||||
toAddress = tokenData.to;
|
||||
} else {
|
||||
fixedValue = Wei(value);
|
||||
toAddress = to;
|
||||
}
|
||||
|
||||
return {
|
||||
value: fixedValue,
|
||||
gasPrice: Wei(gasPrice),
|
||||
data,
|
||||
toAddress,
|
||||
nonce,
|
||||
from
|
||||
};
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
import Big, { BigNumber } from 'bignumber.js';
|
||||
import { Token } from 'config/data';
|
||||
import BN from 'bn.js';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
|
||||
type UnitKey = keyof typeof Units;
|
||||
type Wei = BN;
|
||||
type TokenValue = BN;
|
||||
|
||||
const Units = {
|
||||
wei: '1',
|
||||
|
@ -27,79 +31,74 @@ const Units = {
|
|||
gether: '1000000000000000000000000000',
|
||||
tether: '1000000000000000000000000000000'
|
||||
};
|
||||
|
||||
export type TUnit = typeof Units;
|
||||
export type UnitKey = keyof TUnit;
|
||||
|
||||
class Unit {
|
||||
public unit: UnitKey;
|
||||
public amount: BigNumber;
|
||||
|
||||
constructor(amount: BigNumber, unit: UnitKey) {
|
||||
this.unit = unit;
|
||||
this.amount = amount;
|
||||
const handleValues = (input: string | BN) => {
|
||||
if (typeof input === 'string') {
|
||||
return input.startsWith('0x')
|
||||
? new BN(stripHexPrefix(input), 16)
|
||||
: new BN(input);
|
||||
}
|
||||
|
||||
public toString(base?: number) {
|
||||
return this.amount.toString(base);
|
||||
if (typeof input === 'number') {
|
||||
return new BN(input);
|
||||
}
|
||||
|
||||
public toPrecision(precision?: number) {
|
||||
return this.amount.toPrecision(precision);
|
||||
if (BN.isBN(input)) {
|
||||
return input;
|
||||
}
|
||||
throw Error('unsupported value conversion');
|
||||
};
|
||||
|
||||
public toWei(): Wei {
|
||||
return new Wei(toWei(this.amount, this.unit));
|
||||
const Wei = (input: string | BN): Wei => handleValues(input);
|
||||
|
||||
const TokenValue = (input: string | BN) => handleValues(input);
|
||||
|
||||
const getDecimal = (key: UnitKey) => Units[key].length - 1;
|
||||
|
||||
const stripRightZeros = (str: string) => {
|
||||
const strippedStr = str.replace(/0+$/, '');
|
||||
return strippedStr === '' ? null : strippedStr;
|
||||
};
|
||||
|
||||
const baseToConvertedUnit = (value: string, decimal: number) => {
|
||||
if (decimal === 0) {
|
||||
return value;
|
||||
}
|
||||
const paddedValue = value.padStart(decimal + 1, '0'); //0.1 ==>
|
||||
const integerPart = paddedValue.slice(0, -decimal);
|
||||
const fractionPart = stripRightZeros(paddedValue.slice(-decimal));
|
||||
return fractionPart ? `${integerPart}.${fractionPart}` : `${integerPart}`;
|
||||
};
|
||||
|
||||
public toGWei(): GWei {
|
||||
return new GWei(toUnit(this.amount, this.unit, 'gwei'));
|
||||
const convertedToBaseUnit = (value: string, decimal: number) => {
|
||||
if (decimal === 0) {
|
||||
return value;
|
||||
}
|
||||
const [integerPart, fractionPart = ''] = value.split('.');
|
||||
const paddedFraction = fractionPart.padEnd(decimal, '0');
|
||||
return `${integerPart}${paddedFraction}`;
|
||||
};
|
||||
|
||||
public toEther(): Ether {
|
||||
return new Ether(toUnit(this.amount, this.unit, 'ether'));
|
||||
}
|
||||
}
|
||||
const fromWei = (wei: Wei, unit: UnitKey) => {
|
||||
const decimal = getDecimal(unit);
|
||||
return baseToConvertedUnit(wei.toString(), decimal);
|
||||
};
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
export class Ether extends Unit {
|
||||
constructor(amount: BigNumber | number | string) {
|
||||
super(new Big(amount), 'ether');
|
||||
}
|
||||
}
|
||||
const toWei = (value: string, decimal: number): Wei => {
|
||||
const wei = convertedToBaseUnit(value, decimal);
|
||||
return Wei(wei);
|
||||
};
|
||||
|
||||
export class Wei extends Unit {
|
||||
constructor(amount: BigNumber | number | string) {
|
||||
super(new Big(amount), 'wei');
|
||||
}
|
||||
}
|
||||
const fromTokenBase = (value: TokenValue, decimal: number) =>
|
||||
baseToConvertedUnit(value.toString(), decimal);
|
||||
|
||||
export class GWei extends Unit {
|
||||
constructor(amount: BigNumber | number | string) {
|
||||
super(new Big(amount), 'gwei');
|
||||
}
|
||||
}
|
||||
const toTokenBase = (value: string, decimal: number) =>
|
||||
TokenValue(convertedToBaseUnit(value, decimal));
|
||||
|
||||
function getValueOfUnit(unit: UnitKey) {
|
||||
return new Big(Units[unit]);
|
||||
}
|
||||
|
||||
export function toWei(num: BigNumber, unit: UnitKey): BigNumber {
|
||||
return num.times(getValueOfUnit(unit));
|
||||
}
|
||||
|
||||
export function toUnit(
|
||||
num: BigNumber,
|
||||
fromUnit: UnitKey,
|
||||
convertToUnit: UnitKey
|
||||
): BigNumber {
|
||||
return toWei(num, fromUnit).div(getValueOfUnit(convertToUnit));
|
||||
}
|
||||
|
||||
export function toTokenUnit(num: BigNumber, token: Token): BigNumber {
|
||||
return num.times(new Big(10).pow(token.decimal));
|
||||
}
|
||||
|
||||
export function toTokenDisplay(num: BigNumber, token: Token): BigNumber {
|
||||
return num.times(new Big(10).pow(-token.decimal));
|
||||
}
|
||||
export {
|
||||
TokenValue,
|
||||
fromWei,
|
||||
toWei,
|
||||
toTokenBase,
|
||||
fromTokenBase,
|
||||
Wei,
|
||||
getDecimal,
|
||||
UnitKey
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Ether } from 'libs/units';
|
||||
|
||||
import { Wei } from 'libs/units';
|
||||
export function stripHexPrefix(value: string) {
|
||||
return value.replace('0x', '');
|
||||
}
|
||||
|
@ -8,11 +7,8 @@ export function stripHexPrefixAndLower(value: string): string {
|
|||
return stripHexPrefix(value).toLowerCase();
|
||||
}
|
||||
|
||||
export function valueToHex(value: Ether): string {
|
||||
// Values are in ether, so convert to wei for RPC calls
|
||||
const wei = value.toWei();
|
||||
// Finally, hex it up!
|
||||
return `0x${wei.toString(16)}`;
|
||||
export function toHexWei(weiString: string): string {
|
||||
return `0x${Wei(weiString).toString(16)}`;
|
||||
}
|
||||
|
||||
export function padLeftEven(hex: string) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Big from 'bignumber.js';
|
||||
import BN from 'bn.js';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import { RawTransaction } from 'libs/transaction';
|
||||
|
@ -6,6 +6,7 @@ import { stripHexPrefixAndLower } from 'libs/values';
|
|||
import TrezorConnect from 'vendor/trezor-connect';
|
||||
import { DeterministicWallet } from './deterministic';
|
||||
import { IWallet } from '../IWallet';
|
||||
|
||||
export class TrezorWallet extends DeterministicWallet implements IWallet {
|
||||
public signRawTransaction(tx: RawTransaction): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -30,7 +31,7 @@ export class TrezorWallet extends DeterministicWallet implements IWallet {
|
|||
// https://github.com/kvhnuke/etherwallet/blob/v3.10.2.6/app/scripts/uiFuncs.js#L24
|
||||
const txToSerialize = {
|
||||
...tx,
|
||||
v: addHexPrefix(new Big(result.v).toString(16)),
|
||||
v: addHexPrefix(new BN(result.v).toString(16)),
|
||||
r: addHexPrefix(result.r),
|
||||
s: addHexPrefix(result.s)
|
||||
};
|
||||
|
|
|
@ -42,7 +42,10 @@ export function deterministicWallets(
|
|||
}
|
||||
}
|
||||
|
||||
function updateWalletValues(wallets, newWallet) {
|
||||
function updateWalletValues(
|
||||
wallets: DeterministicWalletData[],
|
||||
newWallet: Partial<DeterministicWalletData>
|
||||
) {
|
||||
return wallets.map(w => {
|
||||
if (w.address === newWallet.address) {
|
||||
return {
|
||||
|
|
|
@ -5,18 +5,17 @@ import {
|
|||
WalletAction,
|
||||
TypeKeys
|
||||
} from 'actions/wallet';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
import { BroadcastTransactionStatus } from 'libs/transaction';
|
||||
import { Ether } from 'libs/units';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import { getTxFromBroadcastTransactionStatus } from 'selectors/wallet';
|
||||
|
||||
export interface State {
|
||||
inst?: IWallet | null;
|
||||
// in ETH
|
||||
balance?: Ether | null;
|
||||
balance?: Wei | null;
|
||||
tokens: {
|
||||
[key: string]: BigNumber;
|
||||
[key: string]: TokenValue;
|
||||
};
|
||||
transactions: BroadcastTransactionStatus[];
|
||||
}
|
||||
|
@ -33,8 +32,8 @@ function setWallet(state: State, action: SetWalletAction): State {
|
|||
}
|
||||
|
||||
function setBalance(state: State, action: SetBalanceAction): State {
|
||||
const ethBalance = action.payload.toEther();
|
||||
return { ...state, balance: ethBalance };
|
||||
const weiBalance = action.payload;
|
||||
return { ...state, balance: weiBalance };
|
||||
}
|
||||
|
||||
function setTokenBalances(state: State, action: SetTokenBalancesAction): State {
|
||||
|
|
|
@ -20,7 +20,10 @@ import {
|
|||
changeNode,
|
||||
changeNodeIntent
|
||||
} from 'actions/config';
|
||||
import { State as ConfigState, INITIAL_STATE as configInitialState } from 'reducers/config';
|
||||
import {
|
||||
State as ConfigState,
|
||||
INITIAL_STATE as configInitialState
|
||||
} from 'reducers/config';
|
||||
|
||||
export const getConfig = (state: AppState): ConfigState => state.config;
|
||||
|
||||
|
@ -43,7 +46,6 @@ function* handlePollOfflineStatus(): SagaIterator {
|
|||
yield cancel(pollOfflineStatusTask);
|
||||
}
|
||||
|
||||
|
||||
// @HACK For now we reload the app when doing a language swap to force non-connected
|
||||
// data to reload. Also the use of timeout to avoid using additional actions for now.
|
||||
function* reload(): SagaIterator {
|
||||
|
|
|
@ -23,6 +23,7 @@ import { getNodeLib } from 'selectors/config';
|
|||
import { getDesiredToken, getWallets } from 'selectors/deterministicWallets';
|
||||
import { getTokens } from 'selectors/wallet';
|
||||
import translate from 'translations';
|
||||
import { TokenValue } from 'libs/units';
|
||||
|
||||
function* getDeterministicWallets(
|
||||
action: GetDeterministicWalletsAction
|
||||
|
@ -105,7 +106,7 @@ function* updateWalletTokenValues(): SagaIterator {
|
|||
const calls = wallets.map(w => {
|
||||
return apply(node, node.getTokenBalance, [w.address, token]);
|
||||
});
|
||||
const tokenBalances = yield all(calls);
|
||||
const tokenBalances: TokenValue[] = yield all(calls);
|
||||
|
||||
for (let i = 0; i < wallets.length; i++) {
|
||||
yield put(
|
||||
|
@ -113,7 +114,7 @@ function* updateWalletTokenValues(): SagaIterator {
|
|||
...wallets[i],
|
||||
tokenValues: {
|
||||
...wallets[i].tokenValues,
|
||||
[desiredToken]: tokenBalances[i]
|
||||
[desiredToken]: { value: tokenBalances[i], decimal: token.decimal }
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
@ -10,10 +10,10 @@ import {
|
|||
UnlockMnemonicAction,
|
||||
UnlockPrivateKeyAction
|
||||
} from 'actions/wallet';
|
||||
import { Wei } from 'libs/units';
|
||||
import { changeNodeIntent } from 'actions/config';
|
||||
import TransactionSucceeded from 'components/ExtendedNotifications/TransactionSucceeded';
|
||||
import { INode } from 'libs/nodes/INode';
|
||||
import { Wei } from 'libs/units';
|
||||
import {
|
||||
IWallet,
|
||||
MnemonicWallet,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Big, { BigNumber } from 'bignumber.js';
|
||||
import { TokenValue } from 'libs/units';
|
||||
import { Token } from 'config/data';
|
||||
import { BroadcastTransactionStatus } from 'libs/transaction';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
|
@ -11,8 +11,9 @@ export function getWalletInst(state: AppState): IWallet | null | undefined {
|
|||
|
||||
export interface TokenBalance {
|
||||
symbol: string;
|
||||
balance: BigNumber;
|
||||
balance: TokenValue;
|
||||
custom: boolean;
|
||||
decimal: number;
|
||||
}
|
||||
|
||||
export type MergedToken = Token & {
|
||||
|
@ -38,8 +39,9 @@ export function getTokenBalances(state: AppState): TokenBalance[] {
|
|||
symbol: t.symbol,
|
||||
balance: state.wallet.tokens[t.symbol]
|
||||
? state.wallet.tokens[t.symbol]
|
||||
: new Big(0),
|
||||
custom: t.custom
|
||||
: TokenValue('0'),
|
||||
custom: t.custom,
|
||||
decimal: t.decimal
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -369,7 +369,7 @@ declare module 'bn.js' {
|
|||
* @description reduct
|
||||
*/
|
||||
|
||||
modn(b: number): BN;
|
||||
modn(b: number): number; //API consistency https://github.com/indutny/bn.js/pull/130
|
||||
|
||||
/**
|
||||
* @description rounded division
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { BigNumber } from 'bignumber.js';
|
||||
import { Wei } from 'libs/units';
|
||||
|
||||
export function toFixedIfLarger(num: number, fixedSize: number = 6): string {
|
||||
return parseFloat(num.toFixed(fixedSize)).toString();
|
||||
|
@ -8,9 +8,53 @@ export function combineAndUpper(...args: string[]) {
|
|||
return args.reduce((acc, item) => acc.concat(item.toUpperCase()), '');
|
||||
}
|
||||
|
||||
const toFixed = (num: string, digits: number = 3) => {
|
||||
const [integerPart, fractionPart = ''] = num.split('.');
|
||||
if (fractionPart.length === digits) {
|
||||
return num;
|
||||
}
|
||||
if (fractionPart.length < digits) {
|
||||
return `${integerPart}.${fractionPart.padEnd(digits, '0')}`;
|
||||
}
|
||||
|
||||
let decimalPoint = integerPart.length;
|
||||
|
||||
const formattedFraction = fractionPart.slice(0, digits);
|
||||
|
||||
const integerArr = `${integerPart}${formattedFraction}`
|
||||
.split('')
|
||||
.map(str => +str);
|
||||
|
||||
let carryOver = Math.floor((+fractionPart[digits] + 5) / 10);
|
||||
|
||||
// grade school addition / rounding
|
||||
|
||||
for (let i = integerArr.length - 1; i >= 0; i--) {
|
||||
const currVal = integerArr[i] + carryOver;
|
||||
const newVal = currVal % 10;
|
||||
carryOver = Math.floor(currVal / 10);
|
||||
integerArr[i] = newVal;
|
||||
if (i === 0 && carryOver > 0) {
|
||||
integerArr.unshift(0);
|
||||
decimalPoint++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
const strArr = integerArr.map(n => n.toString());
|
||||
|
||||
strArr.splice(decimalPoint, 0, '.');
|
||||
|
||||
if (strArr[strArr.length - 1] === '.') {
|
||||
strArr.pop();
|
||||
}
|
||||
|
||||
return strArr.join('');
|
||||
};
|
||||
|
||||
// Use in place of angular number filter
|
||||
export function formatNumber(num: BigNumber, digits: number = 3): string {
|
||||
const parts = num.toFixed(digits).split('.');
|
||||
export function formatNumber(num: string, digits?: number): string {
|
||||
const parts = toFixed(num, digits).split('.');
|
||||
|
||||
// Remove trailing zeroes on decimal (If there is a decimal)
|
||||
if (parts[1]) {
|
||||
|
@ -29,10 +73,7 @@ export function formatNumber(num: BigNumber, digits: number = 3): string {
|
|||
}
|
||||
|
||||
// TODO: Comment up this function to make it clear what's happening here.
|
||||
export function formatGasLimit(
|
||||
limit: BigNumber,
|
||||
transactionUnit: string = 'ether'
|
||||
) {
|
||||
export function formatGasLimit(limit: Wei, transactionUnit: string = 'ether') {
|
||||
let limitStr = limit.toString();
|
||||
|
||||
// I'm guessing this is some known off-by-one-error from the node?
|
||||
|
@ -47,7 +88,7 @@ export function formatGasLimit(
|
|||
// TODO: Make this dynamic, potentially. Would require promisifying this fn.
|
||||
// TODO: Figure out if this is only true for ether. Do other currencies have
|
||||
// this limit?
|
||||
if (limit.gte(4000000)) {
|
||||
if (limit.gten(4000000)) {
|
||||
limitStr = '-1';
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
"npm": ">= 5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bignumber.js": "4.0.2",
|
||||
"bip39": "2.4.0",
|
||||
"bn.js": "4.11.8",
|
||||
"bootstrap-sass": "3.3.7",
|
||||
|
@ -45,7 +44,6 @@
|
|||
"whatwg-fetch": "2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bignumber.js": "4.0.2",
|
||||
"@types/classnames": "2.2.3",
|
||||
"@types/history": "4.6.1",
|
||||
"@types/jest": "21.1.5",
|
||||
|
@ -118,6 +116,7 @@
|
|||
"build:demo": "BUILD_GH_PAGES=true webpack --config webpack_config/webpack.prod.js",
|
||||
"prebuild:demo": "check-node-version --package",
|
||||
"test": "jest --config=jest_config/jest.config.json --coverage",
|
||||
"updateSnapshot": "jest --config=jest_config/jest.config.json --updateSnapshot",
|
||||
"pretest": "check-node-version --package",
|
||||
"dev": "node webpack_config/server.js",
|
||||
"predev": "check-node-version --package",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Big from 'bignumber.js';
|
||||
import { toWei } from 'libs/units';
|
||||
import ERC20 from 'libs/erc20';
|
||||
const MEW_ADDRESS = '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8';
|
||||
|
||||
|
@ -15,7 +15,8 @@ describe('ERC20', () => {
|
|||
describe('transfer', () => {
|
||||
it('should generate the correct data for a transfer', () => {
|
||||
// Test data generated by sending 1 GNT to the MEW address
|
||||
const value = new Big('1').times(new Big(10).pow(18));
|
||||
|
||||
const value = toWei('1', 18);
|
||||
const data = ERC20.transfer(MEW_ADDRESS, value);
|
||||
expect(data).toBe(
|
||||
'0xa9059cbb0000000000000000000000007cb57b5a97eabe94205c07890be4c1ad31e486a80000000000000000000000000000000000000000000000000de0b6b3a7640000'
|
||||
|
@ -31,9 +32,7 @@ describe('ERC20', () => {
|
|||
);
|
||||
|
||||
expect(tx.to).toBe(MEW_ADDRESS);
|
||||
expect(tx.value).toBe(
|
||||
new Big('0.001').times(new Big(10).pow(18)).toString()
|
||||
);
|
||||
expect(tx.value.toString()).toBe(toWei('0.001', 18).toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Ref: https://github.com/ethereum/wiki/wiki/JSON-RPC
|
||||
import { hexEncodeQuantity, hexEncodeData } from 'libs/nodes/rpc/utils';
|
||||
import Big from 'bignumber.js';
|
||||
import BN from 'bn.js';
|
||||
|
||||
// 0x41 (65 in decimal)
|
||||
// 0x400 (1024 in decimal)
|
||||
|
@ -9,13 +9,13 @@ import Big from 'bignumber.js';
|
|||
// WRONG: ff (must be prefixed 0x)
|
||||
describe('hexEncodeQuantity', () => {
|
||||
it('convert dec to hex', () => {
|
||||
expect(hexEncodeQuantity(new Big(65))).toEqual('0x41');
|
||||
expect(hexEncodeQuantity(new BN(65))).toEqual('0x41');
|
||||
});
|
||||
it('should strip leading zeroes', () => {
|
||||
expect(hexEncodeQuantity(new Big(1024))).toEqual('0x400');
|
||||
expect(hexEncodeQuantity(new BN(1024))).toEqual('0x400');
|
||||
});
|
||||
it('should handle zeroes correctly', () => {
|
||||
expect(hexEncodeQuantity(new Big(0))).toEqual('0x0');
|
||||
expect(hexEncodeQuantity(new BN(0))).toEqual('0x0');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,49 +1,98 @@
|
|||
import Big from 'bignumber.js';
|
||||
import { toWei, toUnit, UnitKey } from '../../common/libs/units';
|
||||
import {
|
||||
fromWei,
|
||||
Wei,
|
||||
toWei,
|
||||
toTokenBase,
|
||||
fromTokenBase,
|
||||
getDecimal,
|
||||
TokenValue
|
||||
} from 'libs/units';
|
||||
|
||||
const Units = {
|
||||
wei: '1',
|
||||
kwei: '1000',
|
||||
ada: '1000',
|
||||
femtoether: '1000',
|
||||
mwei: '1000000',
|
||||
babbage: '1000000',
|
||||
picoether: '1000000',
|
||||
gwei: '1000000000',
|
||||
shannon: '1000000000',
|
||||
nanoether: '1000000000',
|
||||
nano: '1000000000',
|
||||
szabo: '1000000000000',
|
||||
microether: '1000000000000',
|
||||
micro: '1000000000000',
|
||||
finney: '1000000000000000',
|
||||
milliether: '1000000000000000',
|
||||
milli: '1000000000000000',
|
||||
ether: '1000000000000000000',
|
||||
kether: '1000000000000000000000',
|
||||
grand: '1000000000000000000000',
|
||||
einstein: '1000000000000000000000',
|
||||
mether: '1000000000000000000000000',
|
||||
gether: '1000000000000000000000000000',
|
||||
tether: '1000000000000000000000000000000'
|
||||
};
|
||||
|
||||
describe('Units', () => {
|
||||
describe('toWei', () => {
|
||||
const conversions = [
|
||||
{
|
||||
value: '0.001371',
|
||||
unit: 'ether' as UnitKey,
|
||||
wei: '1371000000000000'
|
||||
},
|
||||
{
|
||||
value: '9',
|
||||
unit: 'gwei' as UnitKey,
|
||||
wei: '9000000000'
|
||||
}
|
||||
];
|
||||
describe('ethereum units', () => {
|
||||
describe('ether', () => {
|
||||
const wei = Wei(Units.ether);
|
||||
const ether = fromWei(wei, 'ether');
|
||||
it('should equal one ether', () => {
|
||||
expect(ether).toEqual('1');
|
||||
});
|
||||
|
||||
conversions.forEach(c => {
|
||||
it(`should return '${c.wei}' given ${c.value} ${c.unit}`, () => {
|
||||
const big = new Big(c.value);
|
||||
expect(toWei(big, c.unit).toString()).toEqual(c.wei);
|
||||
it('should equal 1 * 10^18 wei', () => {
|
||||
const converted = toWei(ether, getDecimal('ether'));
|
||||
expect(converted.toString()).toEqual(Units.ether);
|
||||
});
|
||||
});
|
||||
describe('gwei', () => {
|
||||
const wei = Wei(`2${Units.gwei}`);
|
||||
const gwei = fromWei(wei, 'gwei');
|
||||
it('should equal 21 gwei', () => {
|
||||
expect(gwei).toEqual('21');
|
||||
});
|
||||
it('should equal 21 * 10^9 wei', () => {
|
||||
const converted = toWei(gwei, getDecimal('gwei'));
|
||||
expect(converted.toString()).toEqual(wei.toString());
|
||||
});
|
||||
});
|
||||
describe('kwei', () => {
|
||||
const wei = Wei('1623');
|
||||
const kwei = fromWei(wei, 'kwei');
|
||||
it('should equal 1.623 kwei', () => {
|
||||
expect(kwei).toEqual('1.623');
|
||||
});
|
||||
it('should equal 1.623 * 10^3 wei', () => {
|
||||
const converted = toWei(kwei, getDecimal('kwei'));
|
||||
expect(converted.toString()).toEqual('1623');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toUnit', () => {
|
||||
const conversions = [
|
||||
{
|
||||
value: '.41849',
|
||||
fromUnit: 'ether' as UnitKey,
|
||||
toUnit: 'gwei' as UnitKey,
|
||||
output: '418490000'
|
||||
},
|
||||
{
|
||||
value: '4924.71',
|
||||
fromUnit: 'nanoether' as UnitKey,
|
||||
toUnit: 'szabo' as UnitKey,
|
||||
output: '4.92471'
|
||||
}
|
||||
];
|
||||
|
||||
conversions.forEach(c => {
|
||||
it(`should return '${c.output}' when converting ${c.value} ${c.fromUnit} to ${c.toUnit}`, () => {
|
||||
const big = new Big(c.value);
|
||||
expect(toUnit(big, c.fromUnit, c.toUnit).toString()).toEqual(c.output);
|
||||
describe('token units', () => {
|
||||
describe('token 1', () => {
|
||||
const tokens = '732156.34592016';
|
||||
const decimal = 18;
|
||||
const tokenBase = toTokenBase(tokens, decimal);
|
||||
it('should equal 732156345920160000000000', () => {
|
||||
expect(tokenBase.toString()).toEqual('732156345920160000000000');
|
||||
});
|
||||
it('should equal 732156.34592016', () => {
|
||||
expect(fromTokenBase(tokenBase, decimal)).toEqual(tokens);
|
||||
});
|
||||
});
|
||||
describe('token 2', () => {
|
||||
const tokens = '8000';
|
||||
const decimal = 8;
|
||||
const converted = fromTokenBase(TokenValue(tokens), decimal);
|
||||
it('should equal 0.00008', () => {
|
||||
expect(converted).toEqual('0.00008');
|
||||
});
|
||||
it('should equal 8000', () => {
|
||||
expect(toTokenBase(converted, decimal));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,12 +4,7 @@ exports[`render snapshot 1`] = `
|
|||
<SendTransaction
|
||||
broadcastTx={[Function]}
|
||||
forceOffline={false}
|
||||
gasPrice={
|
||||
Wei {
|
||||
"amount": "21000000000",
|
||||
"unit": "wei",
|
||||
}
|
||||
}
|
||||
gasPrice={"4e3b29200"}
|
||||
location={
|
||||
Object {
|
||||
"search": "?to=73640ebefe93e4d0d6e9030ee9c1866ad1f3b9f1feeb403e978c4952d8369b39",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { wallet, INITIAL_STATE } from 'reducers/wallet';
|
||||
import * as walletActions from 'actions/wallet';
|
||||
import Big from 'bignumber.js';
|
||||
import { TokenValue } from 'libs/units';
|
||||
|
||||
describe('wallet reducer', () => {
|
||||
it('should handle WALLET_SET', () => {
|
||||
|
@ -23,7 +23,7 @@ describe('wallet reducer', () => {
|
|||
});
|
||||
|
||||
it('should handle WALLET_SET_TOKEN_BALANCES', () => {
|
||||
const tokenBalances = { OMG: new Big(20) };
|
||||
const tokenBalances = { OMG: TokenValue('20') };
|
||||
expect(
|
||||
wallet(undefined, walletActions.setTokenBalances(tokenBalances))
|
||||
).toEqual({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Big from 'bignumber.js';
|
||||
import { Wei } from 'libs/units';
|
||||
import {
|
||||
toFixedIfLarger,
|
||||
formatNumber,
|
||||
|
@ -24,35 +24,57 @@ describe('toFixedIfLarger', () => {
|
|||
describe('formatNumber', () => {
|
||||
const pairs = [
|
||||
{
|
||||
input: new Big('0.0127491'),
|
||||
input: '0.0127491',
|
||||
output: '0.013',
|
||||
digits: undefined
|
||||
},
|
||||
{
|
||||
input: new Big('21.87468421'),
|
||||
input: '21.87468421',
|
||||
output: '21.875',
|
||||
digits: undefined
|
||||
},
|
||||
{
|
||||
input: new Big(0),
|
||||
input: '0',
|
||||
output: '0',
|
||||
digits: undefined
|
||||
},
|
||||
{
|
||||
input: new Big('354.4728173'),
|
||||
input: '354.4728173',
|
||||
output: '354.4728',
|
||||
digits: 4
|
||||
},
|
||||
{
|
||||
input: new Big('100.48391'),
|
||||
input: '100.48391',
|
||||
output: '100',
|
||||
digits: 0
|
||||
},
|
||||
{
|
||||
input: '239.999632',
|
||||
output: '240',
|
||||
digits: 0
|
||||
},
|
||||
{
|
||||
input: '999.999',
|
||||
output: '1,000',
|
||||
digits: 0
|
||||
},
|
||||
{
|
||||
input: '0.9',
|
||||
output: '1',
|
||||
digits: 0
|
||||
},
|
||||
{
|
||||
input: '0.09',
|
||||
output: '0.1',
|
||||
digits: 1
|
||||
}
|
||||
];
|
||||
|
||||
pairs.forEach(pair => {
|
||||
const digits = pair.digits;
|
||||
it(`should convert ${pair.input.toString()} to ${pair.output} when using ${digits} digits`, () => {
|
||||
it(`should convert ${pair.input.toString()} to ${pair.output} when using ${
|
||||
digits
|
||||
} digits`, () => {
|
||||
expect(formatNumber(pair.input, pair.digits)).toEqual(pair.output);
|
||||
});
|
||||
});
|
||||
|
@ -60,14 +82,14 @@ describe('formatNumber', () => {
|
|||
|
||||
describe('formatGasLimit', () => {
|
||||
it('should fix transaction gas limit off-by-one errors', () => {
|
||||
expect(formatGasLimit(new Big(21001), 'ether')).toEqual('21000');
|
||||
expect(formatGasLimit(Wei('21001'), 'ether')).toEqual('21000');
|
||||
});
|
||||
|
||||
it('should mark the gas limit `-1` if you exceed the limit per block', () => {
|
||||
expect(formatGasLimit(new Big(999999999999999), 'ether')).toEqual('-1');
|
||||
expect(formatGasLimit(Wei('999999999999999'), 'ether')).toEqual('-1');
|
||||
});
|
||||
|
||||
it('should not alter a valid gas limit', () => {
|
||||
expect(formatGasLimit(new Big(1234))).toEqual('1234');
|
||||
expect(formatGasLimit(Wei('1234'))).toEqual('1234');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue