WIP: Generating transaction on Send tab (Pt 1) (#100)

* Generating transaction ,placing into read only textareas.

* Fix async wallet getAddress cases.

* Chain id from network

* remove leftover console log

* Check balance before generating transaction.

* Translate error msgs, check for invalid address.

* Errors for gas limit and gas price issues.
This commit is contained in:
William O'Beirne 2017-08-11 17:54:10 -04:00 committed by Daniel Ternyak
parent c594619acd
commit 9b2156ed4f
17 changed files with 297 additions and 82 deletions

View File

@ -33,7 +33,6 @@ export class BalanceSidebar extends React.Component {
}; };
componentDidMount() { componentDidMount() {
if (!this.props.wallet) return;
this.props.wallet this.props.wallet
.getAddress() .getAddress()
.then(addr => { .then(addr => {
@ -48,6 +47,7 @@ export class BalanceSidebar extends React.Component {
render() { render() {
const { wallet, balance, network, tokenBalances, rates } = this.props; const { wallet, balance, network, tokenBalances, rates } = this.props;
const { blockExplorer, tokenExplorer } = network; const { blockExplorer, tokenExplorer } = network;
const { address } = this.state;
if (!wallet) { if (!wallet) {
return null; return null;
} }
@ -58,9 +58,9 @@ export class BalanceSidebar extends React.Component {
{translate('sidebar_AccountAddr')} {translate('sidebar_AccountAddr')}
</h5> </h5>
<ul className="account-info"> <ul className="account-info">
<Identicon address={this.state.address} /> <Identicon address={address} />
<span className="mono wrap"> <span className="mono wrap">
{this.state.address} {address}
</span> </span>
</ul> </ul>
<hr /> <hr />
@ -96,10 +96,7 @@ export class BalanceSidebar extends React.Component {
{!!blockExplorer && {!!blockExplorer &&
<li> <li>
<a <a
href={blockExplorer.address.replace( href={blockExplorer.address.replace('[[address]]', address)}
'[[address]]',
this.state.address
)}
target="_blank" target="_blank"
> >
{`${network.name} (${blockExplorer.name})`} {`${network.name} (${blockExplorer.name})`}
@ -108,10 +105,7 @@ export class BalanceSidebar extends React.Component {
{!!tokenExplorer && {!!tokenExplorer &&
<li> <li>
<a <a
href={tokenExplorer.address.replace( href={tokenExplorer.address.replace('[[address]]', address)}
'[[address]]',
this.state.address
)}
target="_blank" target="_blank"
> >
{`Tokens (${tokenExplorer.name})`} {`Tokens (${tokenExplorer.name})`}
@ -187,7 +181,7 @@ export class BalanceSidebar extends React.Component {
}; };
} }
function mapStateToProps(state: State, props: Props) { function mapStateToProps(state: State) {
return { return {
wallet: getWalletInst(state), wallet: getWalletInst(state),
balance: state.wallet.balance, balance: state.wallet.balance,

View File

@ -75,6 +75,6 @@ export default class GasPriceDropdown extends Component {
}; };
updateGasPrice = (e: SyntheticInputEvent) => { updateGasPrice = (e: SyntheticInputEvent) => {
this.props.onChange(e.currentTarget.valueAsNumber); this.props.onChange(parseInt(e.target.value, 10));
}; };
} }

View File

@ -79,6 +79,9 @@ export default class UnitDropdown extends React.Component {
this.setState({ this.setState({
expanded: false expanded: false
}); });
this.props.onChange(value);
if (this.props.onChange) {
this.props.onChange(value);
}
}; };
} }

View File

@ -1,7 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import translate from 'translations'; import translate from 'translations';
import { UnlockHeader } from 'components/ui'; import { UnlockHeader } from 'components/ui';
import { import {
@ -21,27 +20,40 @@ import BaseWallet from 'libs/wallet/base';
import customMessages from './messages'; import customMessages from './messages';
import { donationAddressMap } from 'config/data'; import { donationAddressMap } from 'config/data';
import { isValidETHAddress } from 'libs/validators'; import { isValidETHAddress } from 'libs/validators';
import { getNodeLib } from 'selectors/config'; import {
getNodeLib,
getNetworkConfig,
getGasPriceGwei
} from 'selectors/config';
import { getTokens } from 'selectors/wallet'; import { getTokens } from 'selectors/wallet';
import type { BaseNode } from 'libs/nodes'; import type { Token, NetworkConfig } from 'config/data';
import type { Token } from 'config/data';
import Big from 'bignumber.js'; import Big from 'bignumber.js';
import { valueToHex } from 'libs/values'; import { valueToHex } from 'libs/values';
import ERC20 from 'libs/erc20'; import ERC20 from 'libs/erc20';
import type { TokenBalance } from 'selectors/wallet'; import type { TokenBalance } from 'selectors/wallet';
import { getTokenBalances } from 'selectors/wallet'; import { getTokenBalances } from 'selectors/wallet';
import type { TransactionWithoutGas } from 'libs/transaction'; import type { RPCNode } from 'libs/nodes';
import type {
TransactionWithoutGas,
BroadcastTransaction
} from 'libs/transaction';
import type { UNIT } from 'libs/units';
import { toWei } from 'libs/units';
import { formatGasLimit } from 'utils/formatters'; import { formatGasLimit } from 'utils/formatters';
import { showNotification } from 'actions/notifications';
import type { ShowNotificationAction } from 'actions/notifications';
type State = { type State = {
hasQueryString: boolean, hasQueryString: boolean,
readOnly: boolean, readOnly: boolean,
to: string, to: string,
value: string, value: string,
unit: string, // $FlowFixMe - Comes from getParam not validating unit
unit: UNIT,
gasLimit: string, gasLimit: string,
data: string, data: string,
gasChanged: boolean gasChanged: boolean,
transaction: ?BroadcastTransaction
}; };
function getParam(query: { [string]: string }, key: string) { function getParam(query: { [string]: string }, key: string) {
@ -65,9 +77,16 @@ type Props = {
}, },
wallet: BaseWallet, wallet: BaseWallet,
balance: Big, balance: Big,
node: BaseNode, nodeLib: RPCNode,
network: NetworkConfig,
tokens: Token[], tokens: Token[],
tokenBalances: TokenBalance[] tokenBalances: TokenBalance[],
gasPrice: number,
showNotification: (
level: string,
msg: string,
duration?: number
) => ShowNotificationAction
}; };
export class SendTransaction extends React.Component { export class SendTransaction extends React.Component {
@ -81,7 +100,8 @@ export class SendTransaction extends React.Component {
unit: 'ether', unit: 'ether',
gasLimit: '21000', gasLimit: '21000',
data: '', data: '',
gasChanged: false gasChanged: false,
transaction: null
}; };
componentDidMount() { componentDidMount() {
@ -112,8 +132,6 @@ export class SendTransaction extends React.Component {
render() { render() {
const unlocked = !!this.props.wallet; const unlocked = !!this.props.wallet;
const unitReadable = 'UNITREADABLE';
const nodeUnit = 'NODEUNIT';
const hasEnoughBalance = false; const hasEnoughBalance = false;
const { const {
to, to,
@ -122,7 +140,8 @@ export class SendTransaction extends React.Component {
gasLimit, gasLimit,
data, data,
readOnly, readOnly,
hasQueryString hasQueryString,
transaction
} = this.state; } = this.state;
const customMessage = customMessages.find(m => m.to === to); const customMessage = customMessages.find(m => m.to === to);
@ -213,17 +232,23 @@ export class SendTransaction extends React.Component {
<label> <label>
{translate('SEND_raw')} {translate('SEND_raw')}
</label> </label>
<textarea className="form-control" rows="4" readOnly> <textarea
{'' /*rawTx*/} className="form-control"
</textarea> value={transaction ? transaction.rawTx : ''}
rows="4"
readOnly
/>
</div> </div>
<div className="col-sm-6"> <div className="col-sm-6">
<label> <label>
{translate('SEND_signed')} {translate('SEND_signed')}
</label> </label>
<textarea className="form-control" rows="4" readOnly> <textarea
{'' /*signedTx*/} className="form-control"
</textarea> value={transaction ? transaction.signedTx : ''}
rows="4"
readOnly
/>
</div> </div>
</div> </div>
@ -279,26 +304,24 @@ export class SendTransaction extends React.Component {
); );
} }
// FIXME MOVE ME async getTransactionFromState(): Promise<TransactionWithoutGas> {
getTransactionFromState(): ?TransactionWithoutGas { const { wallet } = this.props;
// FIXME add gas price
if (this.state.unit === 'ether') { if (this.state.unit === 'ether') {
return { return {
to: this.state.to, to: this.state.to,
from: this.props.wallet.getAddress(), from: await wallet.getAddress(),
// gasPrice: `0x${new Number(50 * 1000000000).toString(16)}`,
value: valueToHex(this.state.value) value: valueToHex(this.state.value)
}; };
} }
const token = this.props.tokens.find(x => x.symbol === this.state.unit); const token = this.props.tokens.find(x => x.symbol === this.state.unit);
if (!token) { if (!token) {
return; throw new Error('No matching token');
} }
return { return {
to: token.address, to: token.address,
from: this.props.wallet.getAddress(), from: await wallet.getAddress(),
// gasPrice: `0x${new Number(50 * 1000000000).toString(16)}`,
value: '0x0', value: '0x0',
data: ERC20.transfer( data: ERC20.transfer(
this.state.to, this.state.to,
@ -307,8 +330,8 @@ export class SendTransaction extends React.Component {
}; };
} }
estimateGas() { async estimateGas() {
const trans = this.getTransactionFromState(); const trans = await this.getTransactionFromState();
if (!trans) { if (!trans) {
return; return;
} }
@ -317,7 +340,7 @@ export class SendTransaction extends React.Component {
// call comes back, we don't want to replace the gasLimit in state. // call comes back, we don't want to replace the gasLimit in state.
const state = this.state; const state = this.state;
this.props.node.estimateGas(trans).then(gasLimit => { this.props.nodeLib.estimateGas(trans).then(gasLimit => {
if (this.state === state) { if (this.state === state) {
this.setState({ gasLimit: formatGasLimit(gasLimit, state.unit) }); this.setState({ gasLimit: formatGasLimit(gasLimit, state.unit) });
} }
@ -381,16 +404,42 @@ export class SendTransaction extends React.Component {
unit unit
}); });
}; };
generateTx = async () => {
const { nodeLib, wallet } = this.props;
const address = await wallet.getAddress();
try {
const transaction = await nodeLib.generateTransaction(
{
to: this.state.to,
from: address,
value: this.state.value,
gasLimit: this.state.gasLimit,
gasPrice: this.props.gasPrice,
data: this.state.data,
chainId: this.props.network.chainId
},
wallet
);
this.setState({ transaction });
} catch (err) {
this.props.showNotification('danger', err.message, 5000);
}
};
} }
function mapStateToProps(state: AppState) { function mapStateToProps(state: AppState) {
return { return {
wallet: state.wallet.inst, wallet: state.wallet.inst,
balance: state.wallet.balance, balance: state.wallet.balance,
node: getNodeLib(state), tokenBalances: getTokenBalances(state),
nodeLib: getNodeLib(state),
network: getNetworkConfig(state),
tokens: getTokens(state), tokens: getTokens(state),
tokenBalances: getTokenBalances(state) gasPrice: toWei(new Big(getGasPriceGwei(state)), 'gwei')
}; };
} }
export default connect(mapStateToProps)(SendTransaction); export default connect(mapStateToProps, { showNotification })(SendTransaction);

View File

@ -1,7 +1,12 @@
// @flow // @flow
import Big from 'bignumber.js'; import Big from 'bignumber.js';
import type { TransactionWithoutGas } from 'libs/transaction'; import type {
TransactionWithoutGas,
Transaction,
BroadcastTransaction
} from 'libs/transaction';
import type { Token } from 'config/data'; import type { Token } from 'config/data';
import type { BaseWallet } from 'libs/wallet';
export default class BaseNode { export default class BaseNode {
async getBalance(_address: string): Promise<Big> { async getBalance(_address: string): Promise<Big> {
@ -15,4 +20,11 @@ export default class BaseNode {
async estimateGas(_tx: TransactionWithoutGas): Promise<Big> { async estimateGas(_tx: TransactionWithoutGas): Promise<Big> {
throw new Error('Implement me'); throw new Error('Implement me');
} }
async generateTransaction(
_tx: Transaction,
_wallet: BaseWallet
): Promise<BroadcastTransaction> {
throw new Error('Implement me');
}
} }

View File

@ -6,7 +6,8 @@ import type {
JsonRpcResponse, JsonRpcResponse,
CallRequest, CallRequest,
GetBalanceRequest, GetBalanceRequest,
EstimateGasRequest EstimateGasRequest,
GetTransactionCountRequest
} from './types'; } from './types';
// FIXME is it safe to generate that much entropy? // FIXME is it safe to generate that much entropy?
@ -41,6 +42,17 @@ export function ethCall<T: *>(transaction: T): CallRequest {
}; };
} }
export function getTransactionCount(
address: string
): GetTransactionCountRequest {
return {
id: id(),
jsonrpc: '2.0',
method: 'eth_getTransactionCount',
params: [address, 'pending']
};
}
export default class RPCClient { export default class RPCClient {
endpoint: string; endpoint: string;
constructor(endpoint: string) { constructor(endpoint: string) {

View File

@ -1,10 +1,24 @@
// @flow // @flow
import Big from 'bignumber.js'; import Big from 'bignumber.js';
import { addHexPrefix } from 'ethereumjs-util';
import translate from 'translations';
import BaseNode from '../base'; import BaseNode from '../base';
import type { TransactionWithoutGas } from 'libs/transaction'; import type {
import RPCClient, { getBalance, estimateGas, ethCall } from './client'; TransactionWithoutGas,
Transaction,
BroadcastTransaction
} from 'libs/transaction';
import RPCClient, {
getBalance,
estimateGas,
ethCall,
getTransactionCount
} from './client';
import type { Token } from 'config/data'; import type { Token } from 'config/data';
import ERC20 from 'libs/erc20'; import ERC20 from 'libs/erc20';
import type { BaseWallet } from 'libs/wallet';
import { toWei } from 'libs/units';
import { isValidETHAddress } from 'libs/validators';
export default class RpcNode extends BaseNode { export default class RpcNode extends BaseNode {
client: RPCClient; client: RPCClient;
@ -48,8 +62,94 @@ export default class RpcNode extends BaseNode {
if (item.error) { if (item.error) {
return new Big(0); return new Big(0);
} }
return new Big(Number(item.result)).div(new Big(10).pow(tokens[idx].decimal)); return new Big(Number(item.result)).div(
new Big(10).pow(tokens[idx].decimal)
);
}); });
}); });
} }
async generateTransaction(
tx: Transaction,
wallet: BaseWallet
): Promise<BroadcastTransaction> {
// Reject bad addresses
if (!isValidETHAddress(tx.to)) {
return Promise.reject(new Error(translate('ERROR_5')));
}
// Reject gas limit under 21000 (Minimum for transaction)
// Reject if limit over 5000000
// TODO: Make this dynamic, the limit shifts
const limitBig = new Big(tx.gasLimit);
if (limitBig.lessThan(21000)) {
return Promise.reject(
new Error(
translate('Gas limit must be at least 21000 for transactions')
)
);
}
if (limitBig.greaterThan(5000000)) {
return Promise.reject(new Error(translate('GETH_GasLimit')));
}
// Reject gas over 1000gwei (1000000000000)
const priceBig = new Big(tx.gasPrice);
if (priceBig.greaterThan(new Big('1000000000000'))) {
return Promise.reject(
new Error(
'Gas price too high. Please contact support if this was not a mistake.'
)
);
}
const calls = [getBalance(tx.from), getTransactionCount(tx.from)];
return this.client.batch(calls).then(async results => {
const [balance, txCount] = results;
if (balance.error) {
throw new Error(`Failed to retrieve balance for ${tx.from}`);
}
if (txCount.error) {
throw new Error(`Failed to retrieve transaction count for ${tx.from}`);
}
// TODO: Handle token values
const valueWei = new Big(toWei(new Big(tx.value), 'ether'));
const balanceWei = new Big(balance.result);
if (valueWei.gte(balanceWei)) {
throw new Error(translate('GETH_Balance'));
}
const rawTx = {
nonce: addHexPrefix(txCount.result),
gasPrice: addHexPrefix(new Big(tx.gasPrice).toString(16)),
gasLimit: addHexPrefix(new Big(tx.gasLimit).toString(16)),
to: addHexPrefix(tx.to),
value: addHexPrefix(valueWei.toString(16)),
data: tx.data ? addHexPrefix(tx.data) : '',
chainId: tx.chainId || 1
};
const rawTxJson = JSON.stringify(rawTx);
const signedTx = await wallet.signRawTransaction(rawTx);
// Repeat all of this shit for Flow typechecking. Sealed objects don't
// like spreads, so we have to be explicit.
return {
nonce: rawTx.nonce,
gasPrice: rawTx.gasPrice,
gasLimit: rawTx.gasLimit,
to: rawTx.to,
value: rawTx.value,
data: rawTx.data,
chainId: rawTx.chainId,
rawTx: rawTxJson,
signedTx: signedTx
};
});
}
} }

View File

@ -59,4 +59,13 @@ export type EstimateGasRequest = RPCRequestBase & {
] ]
}; };
export type RPCRequest = GetBalanceRequest | CallRequest | EstimateGasRequest; export type GetTransactionCountRequest = RPCRequestBase & {
method: 'eth_getTransactionCount',
params: [DATA, DEFAULT_BLOCK]
};
export type RPCRequest =
| GetBalanceRequest
| CallRequest
| EstimateGasRequest
| GetTransactionCountRequest;

View File

@ -3,9 +3,12 @@
import EthTx from 'ethereumjs-tx'; import EthTx from 'ethereumjs-tx';
import { sha3, ecsign } from 'ethereumjs-util'; import { sha3, ecsign } from 'ethereumjs-util';
import { isValidRawTx } from 'libs/validators'; import { isValidRawTx } from 'libs/validators';
import type { RawTx } from 'libs/validators'; import type { RawTransaction } from 'libs/transaction';
export function signRawTxWithPrivKey(privKey: Buffer, rawTx: RawTx): string { export function signRawTxWithPrivKey(
privKey: Buffer,
rawTx: RawTransaction
): string {
if (!isValidRawTx(rawTx)) { if (!isValidRawTx(rawTx)) {
throw new Error('Invalid raw transaction'); throw new Error('Invalid raw transaction');
} }

View File

@ -1,14 +1,31 @@
// @flow // @flow
export type TransactionWithoutGas = {| export type TransactionWithoutGas = {|
from?: string, from: string,
to: string, to: string,
gasPrice?: string, gasLimit?: string | number,
value?: string, value: string | number,
data?: string data?: string,
chainId?: number
|}; |};
export type Transaction = {| export type Transaction = {|
...TransactionWithoutGas, ...TransactionWithoutGas,
gas: string gasPrice: string | number
|};
export type RawTransaction = {|
nonce: string,
gasPrice: string,
gasLimit: string,
to: string,
value: string,
data: string,
chainId: number
|};
export type BroadcastTransaction = {|
...RawTransaction,
rawTx: string,
signedTx: string
|}; |};

View File

@ -29,7 +29,7 @@ const UNITS = {
tether: '1000000000000000000000000000000' tether: '1000000000000000000000000000000'
}; };
type UNIT = $Keys<typeof UNITS>; export type UNIT = $Keys<typeof UNITS>;
function getValueOfUnit(unit: UNIT) { function getValueOfUnit(unit: UNIT) {
return new Big(UNITS[unit]); return new Big(UNITS[unit]);

View File

@ -2,6 +2,7 @@
import WalletAddressValidator from 'wallet-address-validator'; import WalletAddressValidator from 'wallet-address-validator';
import { normalise } from './ens'; import { normalise } from './ens';
import { toChecksumAddress } from 'ethereumjs-util'; import { toChecksumAddress } from 'ethereumjs-util';
import type { RawTransaction } from 'libs/transaction';
export function isValidETHAddress(address: string): boolean { export function isValidETHAddress(address: string): boolean {
if (!address) { if (!address) {
@ -85,17 +86,7 @@ export function isPositiveIntegerOrZero(number: number): boolean {
return number >= 0 && parseInt(number) === number; return number >= 0 && parseInt(number) === number;
} }
export type RawTx = { export function isValidRawTx(rawTx: RawTransaction): boolean {
nonce: string,
gasPrice: string,
gasLimit: string,
to: string,
value: string,
data: string,
chainId: number
};
export function isValidRawTx(rawTx: RawTx): boolean {
const propReqs = [ const propReqs = [
{ name: 'nonce', type: 'string', lenReq: true }, { name: 'nonce', type: 'string', lenReq: true },
{ name: 'gasPrice', type: 'string', lenReq: true }, { name: 'gasPrice', type: 'string', lenReq: true },

View File

@ -1,16 +1,21 @@
// @flow // @flow
import { stripHex } from 'libs/values'; import { stripHex } from 'libs/values';
import type { RawTransaction } from 'libs/transaction';
export default class BaseWallet { export default class BaseWallet {
getAddress(): Promise<any> { getAddress(): Promise<string> {
return Promise.reject('Implement me'); return Promise.reject('Implement me');
} }
getNakedAddress(): Promise<any> { getNakedAddress(): Promise<string> {
return new Promise(resolve => { return new Promise(resolve => {
this.getAddress().then(address => { this.getAddress().then(address => {
resolve(stripHex(address)); resolve(stripHex(address));
}); });
}); });
} }
signRawTransaction(_tx: RawTransaction): Promise<string> {
return Promise.reject('Implement me');
}
} }

View File

@ -8,7 +8,7 @@ import {
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import { pkeyToKeystore } from 'libs/keystore'; import { pkeyToKeystore } from 'libs/keystore';
import { signRawTxWithPrivKey, signMessageWithPrivKey } from 'libs/signing'; import { signRawTxWithPrivKey, signMessageWithPrivKey } from 'libs/signing';
import type { RawTx } from 'libs/validators'; import type { RawTransaction } from 'libs/transaction';
export default class PrivKeyWallet extends BaseWallet { export default class PrivKeyWallet extends BaseWallet {
privKey: Buffer; privKey: Buffer;
@ -21,7 +21,7 @@ export default class PrivKeyWallet extends BaseWallet {
this.address = publicToAddress(this.pubKey); this.address = publicToAddress(this.pubKey);
} }
getAddress(): Promise<any> { getAddress(): Promise<string> {
return Promise.resolve( return Promise.resolve(
toChecksumAddress(`0x${this.address.toString('hex')}`) toChecksumAddress(`0x${this.address.toString('hex')}`)
); );
@ -47,7 +47,7 @@ export default class PrivKeyWallet extends BaseWallet {
return Promise.resolve(); return Promise.resolve();
} }
signRawTransaction(rawTx: RawTx): Promise<any> { signRawTransaction(rawTx: RawTransaction): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
resolve(signRawTxWithPrivKey(this.privKey, rawTx)); resolve(signRawTxWithPrivKey(this.privKey, rawTx));

View File

@ -19,3 +19,7 @@ export function getNetworkConfig(state: State): NetworkConfig {
export function getNetworkContracts(state: State): ?Array<NetworkContract> { export function getNetworkContracts(state: State): ?Array<NetworkContract> {
return getNetworkConfig(state).contracts; return getNetworkConfig(state).contracts;
} }
export function getGasPriceGwei(state: State): number {
return state.config.gasPriceGwei;
}

14
package-lock.json generated
View File

@ -3771,6 +3771,20 @@
"ethereum-blockies": { "ethereum-blockies": {
"version": "git+https://github.com/MyEtherWallet/blockies.git#c0d0b782e77e17228f6fe1c387b956987e45b670" "version": "git+https://github.com/MyEtherWallet/blockies.git#c0d0b782e77e17228f6fe1c387b956987e45b670"
}, },
"ethereum-common": {
"version": "0.0.18",
"resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz",
"integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8="
},
"ethereumjs-tx": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.3.tgz",
"integrity": "sha1-7OBR0+/b53GtKlGNYWMsoqt17Ls=",
"requires": {
"ethereum-common": "0.0.18",
"ethereumjs-util": "5.1.2"
}
},
"ethereumjs-util": { "ethereumjs-util": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.2.tgz", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.2.tgz",

View File

@ -2,24 +2,26 @@ import {
isValidBTCAddress, isValidBTCAddress,
isValidETHAddress isValidETHAddress
} from '../../common/libs/validators'; } from '../../common/libs/validators';
import { donationAddressMap } from '../../common/config/data';
const VALID_BTC_ADDRESS = '1MEWT2SGbqtz6mPCgFcnea8XmWV5Z4Wc6';
const VALID_ETH_ADDRESS = '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8';
describe('Validator', () => { describe('Validator', () => {
it('should validate correct BTC address as true', () => { it('should validate correct BTC address as true', () => {
expect(isValidBTCAddress(donationAddressMap.BTC)).toBeTruthy(); expect(isValidBTCAddress(VALID_BTC_ADDRESS)).toBeTruthy();
}); });
it('should validate incorrect BTC address as false', () => { it('should validate incorrect BTC address as false', () => {
expect( expect(
isValidBTCAddress('nonsense' + donationAddressMap.BTC + 'nonsense') isValidBTCAddress('nonsense' + VALID_BTC_ADDRESS + 'nonsense')
).toBeFalsy(); ).toBeFalsy();
}); });
it('should validate correct ETH address as true', () => { it('should validate correct ETH address as true', () => {
expect(isValidETHAddress(donationAddressMap.ETH)).toBeTruthy(); expect(isValidETHAddress(VALID_ETH_ADDRESS)).toBeTruthy();
}); });
it('should validate incorrect ETH address as false', () => { it('should validate incorrect ETH address as false', () => {
expect( expect(
isValidETHAddress('nonsense' + donationAddressMap.ETH + 'nonsense') isValidETHAddress('nonsense' + VALID_ETH_ADDRESS + 'nonsense')
).toBeFalsy(); ).toBeFalsy();
}); });
}); });