Add RSK network w/ network agnostic refactors (#1939)
* Rsk network with checksum * Initial change from chainid everywhere to selectors * Fix ternary * Check in address book changes to lower case. Currently a little busted. * Fix validation * Fix tests. * Identicon back to SFC * Remove unnecessary prop * Paper Wallet Refactor (#1950) * Convert print wallet to save png image. Move all styles into CSS. * Fix re-print paper wallet with save modal * Address PR comments
This commit is contained in:
parent
4b45f813b3
commit
994fa03828
|
@ -20,7 +20,7 @@ export function setAddressLabel(payload: AddressLabel): SetAddressLabel {
|
|||
}
|
||||
|
||||
export type TClearAddressLabel = typeof clearAddressLabel;
|
||||
export function clearAddressLabel(payload: string): ClearAddressLabel {
|
||||
export function clearAddressLabel(payload: AddressLabel['address']): ClearAddressLabel {
|
||||
return {
|
||||
type: TypeKeys.CLEAR_ADDRESS_LABEL,
|
||||
payload
|
||||
|
@ -44,7 +44,7 @@ export function changeAddressLabelEntry(payload: AddressLabelEntry): ChangeAddre
|
|||
}
|
||||
|
||||
export type TSaveAddressLabelEntry = typeof saveAddressLabelEntry;
|
||||
export function saveAddressLabelEntry(payload: string): SaveAddressLabelEntry {
|
||||
export function saveAddressLabelEntry(payload: AddressLabelEntry['id']): SaveAddressLabelEntry {
|
||||
return {
|
||||
type: TypeKeys.SAVE_ADDRESS_LABEL_ENTRY,
|
||||
payload
|
||||
|
@ -52,7 +52,7 @@ export function saveAddressLabelEntry(payload: string): SaveAddressLabelEntry {
|
|||
}
|
||||
|
||||
export type TClearAddressLabelEntry = typeof clearAddressLabelEntry;
|
||||
export function clearAddressLabelEntry(payload: string): ClearAddressLabelEntry {
|
||||
export function clearAddressLabelEntry(payload: AddressLabelEntry['id']): ClearAddressLabelEntry {
|
||||
return {
|
||||
type: TypeKeys.CLEAR_ADDRESS_LABEL_ENTRY,
|
||||
payload
|
||||
|
@ -60,7 +60,7 @@ export function clearAddressLabelEntry(payload: string): ClearAddressLabelEntry
|
|||
}
|
||||
|
||||
export type TRemoveAddressLabelEntry = typeof removeAddressLabelEntry;
|
||||
export function removeAddressLabelEntry(payload: string): RemoveAddressLabelEntry {
|
||||
export function removeAddressLabelEntry(payload: AddressLabelEntry['id']): RemoveAddressLabelEntry {
|
||||
return {
|
||||
type: TypeKeys.REMOVE_ADDRESS_LABEL_ENTRY,
|
||||
payload
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
getAddressLabelRows,
|
||||
getAddressBookTableEntry
|
||||
} from 'selectors/addressBook';
|
||||
import { getChecksumAddressFn } from 'selectors/config';
|
||||
import { Input, Identicon } from 'components/ui';
|
||||
import AddressBookTableRow from './AddressBookTableRow';
|
||||
import './AddressBookTable.scss';
|
||||
|
@ -32,6 +33,7 @@ interface StateProps {
|
|||
entry: ReturnType<typeof getAddressBookTableEntry>;
|
||||
addressLabels: ReturnType<typeof getAddressLabels>;
|
||||
labelAddresses: ReturnType<typeof getLabelAddresses>;
|
||||
toChecksumAddress: ReturnType<typeof getChecksumAddressFn>;
|
||||
}
|
||||
|
||||
type Props = DispatchProps & StateProps;
|
||||
|
@ -200,7 +202,9 @@ class AddressBookTable extends React.Component<Props, State> {
|
|||
|
||||
private makeLabelRow = (row: any, index: number) => {
|
||||
const { editingRow } = this.state;
|
||||
const { id, address, label, temporaryLabel, labelError } = row;
|
||||
const { id, label, temporaryLabel, labelError } = row;
|
||||
const address = this.props.toChecksumAddress(row.address);
|
||||
|
||||
const isEditing = index === editingRow;
|
||||
const onChange = (newLabel: string) =>
|
||||
this.props.changeAddressLabelEntry({
|
||||
|
@ -308,7 +312,8 @@ const mapStateToProps: MapStateToProps<StateProps, {}, AppState> = state => ({
|
|||
rows: getAddressLabelRows(state),
|
||||
entry: getAddressBookTableEntry(state),
|
||||
addressLabels: getAddressLabels(state),
|
||||
labelAddresses: getLabelAddresses(state)
|
||||
labelAddresses: getLabelAddresses(state),
|
||||
toChecksumAddress: getChecksumAddressFn(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps: DispatchProps = {
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { AddressFieldFactory } from './AddressFieldFactory';
|
||||
import { donationAddressMap } from 'config';
|
||||
import translate from 'translations';
|
||||
import { Input } from 'components/ui';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import { getChecksumAddressFn } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
|
||||
interface Props {
|
||||
interface OwnProps {
|
||||
isReadOnly?: boolean;
|
||||
isSelfAddress?: boolean;
|
||||
isCheckSummed?: boolean;
|
||||
showLabelMatch?: boolean;
|
||||
}
|
||||
|
||||
export const AddressField: React.SFC<Props> = ({
|
||||
interface StateProps {
|
||||
toChecksumAddress: ReturnType<typeof getChecksumAddressFn>;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
const AddressField: React.SFC<Props> = ({
|
||||
isReadOnly,
|
||||
isSelfAddress,
|
||||
isCheckSummed,
|
||||
showLabelMatch
|
||||
showLabelMatch,
|
||||
toChecksumAddress
|
||||
}) => (
|
||||
<AddressFieldFactory
|
||||
isSelfAddress={isSelfAddress}
|
||||
|
@ -44,3 +53,7 @@ export const AddressField: React.SFC<Props> = ({
|
|||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
export default connect((state: AppState): StateProps => ({
|
||||
toChecksumAddress: getChecksumAddressFn(state)
|
||||
}))(AddressField);
|
||||
|
|
|
@ -11,12 +11,12 @@ import {
|
|||
removeAddressLabelEntry,
|
||||
TRemoveAddressLabelEntry
|
||||
} from 'actions/addressBook';
|
||||
import { getAccountAddressEntry, getAddressLabels } from 'selectors/addressBook';
|
||||
import { getAccountAddressEntry, getAddressLabelEntryFromAddress } from 'selectors/addressBook';
|
||||
import { Address, Identicon, Input } from 'components/ui';
|
||||
|
||||
interface StateProps {
|
||||
entry: ReturnType<typeof getAccountAddressEntry>;
|
||||
addressLabels: ReturnType<typeof getAddressLabels>;
|
||||
addressLabel: string;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -65,13 +65,12 @@ class AccountAddress extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { address, addressLabels } = this.props;
|
||||
const { address, addressLabel } = this.props;
|
||||
const { copied } = this.state;
|
||||
const label = addressLabels[address];
|
||||
const labelContent = this.generateLabelContent();
|
||||
const labelButton = this.generateLabelButton();
|
||||
const addressClassName = `AccountInfo-address-addr ${
|
||||
label ? 'AccountInfo-address-addr--small' : ''
|
||||
addressLabel ? 'AccountInfo-address-addr--small' : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
|
@ -120,10 +119,9 @@ class AccountAddress extends React.Component<Props, State> {
|
|||
private setLabelInputRef = (node: HTMLInputElement) => (this.labelInput = node);
|
||||
|
||||
private generateLabelContent = () => {
|
||||
const { address, addressLabels, entry: { temporaryLabel, labelError } } = this.props;
|
||||
const { addressLabel, entry: { temporaryLabel, labelError } } = this.props;
|
||||
const { editingLabel, labelInputTouched } = this.state;
|
||||
const storedLabel = addressLabels[address];
|
||||
const newLabelSameAsPrevious = temporaryLabel === storedLabel;
|
||||
const newLabelSameAsPrevious = temporaryLabel === addressLabel;
|
||||
const labelInputTouchedWithError = labelInputTouched && !newLabelSameAsPrevious && labelError;
|
||||
|
||||
let labelContent = null;
|
||||
|
@ -134,7 +132,7 @@ class AccountAddress extends React.Component<Props, State> {
|
|||
<Input
|
||||
title={translateRaw('ADD_LABEL')}
|
||||
placeholder={translateRaw('NEW_LABEL')}
|
||||
defaultValue={storedLabel}
|
||||
defaultValue={addressLabel}
|
||||
onChange={this.handleLabelChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onFocus={this.setTemporaryLabelTouched}
|
||||
|
@ -149,20 +147,15 @@ class AccountAddress extends React.Component<Props, State> {
|
|||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
labelContent = (
|
||||
<label title={storedLabel} className="AccountInfo-address-label">
|
||||
{storedLabel}
|
||||
</label>
|
||||
);
|
||||
labelContent = <label className="AccountInfo-address-label">{addressLabel}</label>;
|
||||
}
|
||||
|
||||
return labelContent;
|
||||
};
|
||||
|
||||
private generateLabelButton = () => {
|
||||
const { address, addressLabels } = this.props;
|
||||
const { addressLabel } = this.props;
|
||||
const { editingLabel } = this.state;
|
||||
const label = addressLabels[address];
|
||||
const labelButton = editingLabel ? (
|
||||
<React.Fragment>
|
||||
<i className="fa fa-save" />
|
||||
|
@ -175,10 +168,10 @@ class AccountAddress extends React.Component<Props, State> {
|
|||
<i className="fa fa-pencil" />
|
||||
<span
|
||||
role="button"
|
||||
title={label ? translateRaw('EDIT_LABEL') : translateRaw('ADD_LABEL_9')}
|
||||
title={addressLabel ? translateRaw('EDIT_LABEL') : translateRaw('ADD_LABEL_9')}
|
||||
onClick={this.startEditingLabel}
|
||||
>
|
||||
{label ? translate('EDIT_LABEL') : translate('ADD_LABEL_9')}
|
||||
{addressLabel ? translate('EDIT_LABEL') : translate('ADD_LABEL_9')}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -187,13 +180,12 @@ class AccountAddress extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
private handleBlur = () => {
|
||||
const { address, addressLabels, entry: { id, label, temporaryLabel, labelError } } = this.props;
|
||||
const storedLabel = addressLabels[address];
|
||||
const { address, addressLabel, entry: { id, label, temporaryLabel, labelError } } = this.props;
|
||||
|
||||
this.clearTemporaryLabelTouched();
|
||||
this.stopEditingLabel();
|
||||
|
||||
if (temporaryLabel === storedLabel) {
|
||||
if (temporaryLabel === addressLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -255,10 +247,16 @@ class AccountAddress extends React.Component<Props, State> {
|
|||
private clearTemporaryLabelTouched = () => this.setState({ labelInputTouched: false });
|
||||
}
|
||||
|
||||
const mapStateToProps: MapStateToProps<StateProps, {}, AppState> = (state: AppState) => ({
|
||||
entry: getAccountAddressEntry(state),
|
||||
addressLabels: getAddressLabels(state)
|
||||
});
|
||||
const mapStateToProps: MapStateToProps<StateProps, {}, AppState> = (
|
||||
state: AppState,
|
||||
ownProps: OwnProps
|
||||
) => {
|
||||
const labelEntry = getAddressLabelEntryFromAddress(state, ownProps.address);
|
||||
return {
|
||||
entry: getAccountAddressEntry(state),
|
||||
addressLabel: labelEntry ? labelEntry.label : ''
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps: DispatchProps = {
|
||||
changeAddressLabelEntry,
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import { UnitDisplay, NewTabLink } from 'components/ui';
|
||||
import { IWallet, HardwareWallet, Balance } from 'libs/wallet';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import { getNetworkConfig, getOffline } from 'selectors/config';
|
||||
import { getNetworkConfig, getOffline, getChecksumAddressFn } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import { TRefreshAccountBalance, refreshAccountBalance } from 'actions/wallet';
|
||||
|
@ -21,6 +20,7 @@ interface StateProps {
|
|||
balance: Balance;
|
||||
network: ReturnType<typeof getNetworkConfig>;
|
||||
isOffline: ReturnType<typeof getOffline>;
|
||||
toChecksumAddress: ReturnType<typeof getChecksumAddressFn>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -73,7 +73,7 @@ class AccountInfo extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { network, isOffline, balance, wallet } = this.props;
|
||||
const { network, isOffline, balance, toChecksumAddress, wallet } = this.props;
|
||||
const { address, showLongBalance, confirmAddr } = this.state;
|
||||
|
||||
let blockExplorer;
|
||||
|
@ -199,7 +199,8 @@ function mapStateToProps(state: AppState): StateProps {
|
|||
return {
|
||||
balance: state.wallet.balance,
|
||||
network: getNetworkConfig(state),
|
||||
isOffline: getOffline(state)
|
||||
isOffline: getOffline(state),
|
||||
toChecksumAddress: getChecksumAddressFn(state)
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps: DispatchProps = { refreshAccountBalance };
|
||||
|
|
|
@ -8,21 +8,21 @@ import { connect } from 'react-redux';
|
|||
import { SerializedTransaction } from 'components/renderCbs';
|
||||
import { AppState } from 'reducers';
|
||||
import { getFrom, getUnit, isEtherTransaction } from 'selectors/transaction';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import translate from 'translations';
|
||||
import { getChecksumAddressFn } from 'selectors/config';
|
||||
|
||||
interface StateProps {
|
||||
from: AppState['transaction']['meta']['from'];
|
||||
unit: AppState['transaction']['meta']['unit'];
|
||||
from: ReturnType<typeof getFrom>;
|
||||
unit: ReturnType<typeof getUnit>;
|
||||
isToken: boolean;
|
||||
toChecksumAddress: ReturnType<typeof getChecksumAddressFn>;
|
||||
}
|
||||
|
||||
const size = '3rem';
|
||||
|
||||
class AddressesClass extends Component<StateProps> {
|
||||
public render() {
|
||||
const { from, isToken, unit } = this.props;
|
||||
|
||||
const { from, isToken, unit, toChecksumAddress } = this.props;
|
||||
return (
|
||||
<SerializedTransaction
|
||||
withSerializedTransaction={(_, { to, data }) => {
|
||||
|
@ -92,7 +92,8 @@ class AddressesClass extends Component<StateProps> {
|
|||
const mapStateToProps = (state: AppState): StateProps => ({
|
||||
from: getFrom(state),
|
||||
isToken: !isEtherTransaction(state),
|
||||
unit: getUnit(state)
|
||||
unit: getUnit(state),
|
||||
toChecksumAddress: getChecksumAddressFn(state)
|
||||
});
|
||||
|
||||
export const Addresses = connect(mapStateToProps)(AddressesClass);
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
// Use px and don't use variables here, we want the wallet to look
|
||||
// consistent regardless of theme or screen size.
|
||||
|
||||
$wallet-width: 680px;
|
||||
$wallet-height: 280px;
|
||||
|
||||
.PaperWallet {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: $wallet-width;
|
||||
height: $wallet-height;
|
||||
border: 1px solid #163151;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
|
||||
&-sidebar {
|
||||
float: left;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&-block {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 27.5%;
|
||||
padding: 20px;
|
||||
|
||||
&-box {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
|
||||
&.is-shaded {
|
||||
background: rgba(#000, 0.02);
|
||||
}
|
||||
}
|
||||
|
||||
&-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
transform: translate(-50%, -50%) rotate(-90deg);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #0b7290;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&-info {
|
||||
float: left;
|
||||
width: 85%;
|
||||
padding: 0 20px;
|
||||
|
||||
&-text {
|
||||
margin: 0 0 5px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
font-family: "Roboto Mono", "Menlo", "Monaco", "Consolas", "Courier New", monospace;
|
||||
font-weight: 300;
|
||||
|
||||
&-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-identicon {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
bottom: 45px;
|
||||
|
||||
&-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
&-text {
|
||||
float: left;
|
||||
width: 130px;
|
||||
padding: 0 5px;
|
||||
margin: 12px 0 0;
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,94 +1,10 @@
|
|||
import { Identicon, QRCode } from 'components/ui';
|
||||
import React from 'react';
|
||||
import html2canvas from 'html2canvas';
|
||||
import { addHexPrefix, toChecksumAddress } from 'ethereumjs-util';
|
||||
|
||||
import ethLogo from 'assets/images/logo-ethereum-1.png';
|
||||
import { Identicon, QRCode } from 'components/ui';
|
||||
import notesBg from 'assets/images/notes-bg.png';
|
||||
import sidebarImg from 'assets/images/print-sidebar.png';
|
||||
|
||||
const walletWidth = 680;
|
||||
const walletHeight = 280;
|
||||
|
||||
const styles: any = {
|
||||
container: {
|
||||
position: 'relative',
|
||||
margin: '0 auto',
|
||||
width: `${walletWidth}px`,
|
||||
height: `${walletHeight}px`,
|
||||
border: '1px solid #163151',
|
||||
userSelect: 'none',
|
||||
cursor: 'default'
|
||||
},
|
||||
|
||||
// Images
|
||||
sidebar: {
|
||||
float: 'left',
|
||||
height: '100%',
|
||||
width: 'auto'
|
||||
},
|
||||
ethLogo: {
|
||||
position: 'absolute',
|
||||
left: '86px',
|
||||
height: '100%',
|
||||
width: 'auto',
|
||||
zIndex: '-1'
|
||||
},
|
||||
|
||||
// Blocks / QR Codes
|
||||
block: {
|
||||
position: 'relative',
|
||||
float: 'left',
|
||||
width: '27.5%',
|
||||
padding: '20px'
|
||||
},
|
||||
blockText: {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '100%',
|
||||
width: '100%',
|
||||
margin: 0,
|
||||
transform: 'translate(-50%, -50%) rotate(-90deg)',
|
||||
fontSize: '13px',
|
||||
fontWeight: '600',
|
||||
color: '#0b7290',
|
||||
textAlign: 'center',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '1px'
|
||||
},
|
||||
// Address / private key info
|
||||
infoContainer: {
|
||||
float: 'left',
|
||||
width: '85%',
|
||||
padding: '0 20px'
|
||||
},
|
||||
infoText: {
|
||||
margin: '0 0 8px',
|
||||
textAlign: 'left',
|
||||
fontSize: '14px',
|
||||
fontFamily: '"Roboto Mono", Menlo, Monaco, Consolas, "Courier New", monospace',
|
||||
fontWeight: 300
|
||||
},
|
||||
infoLabel: {
|
||||
fontWeight: 600
|
||||
},
|
||||
identiconContainer: {
|
||||
position: 'absolute',
|
||||
right: '15px',
|
||||
bottom: '45px'
|
||||
},
|
||||
identiconText: {
|
||||
float: 'left',
|
||||
width: '130px',
|
||||
padding: '0 5px',
|
||||
margin: '12px 0 0',
|
||||
fontSize: '9px',
|
||||
textAlign: 'center'
|
||||
},
|
||||
box: {
|
||||
width: 150,
|
||||
height: 150
|
||||
}
|
||||
};
|
||||
import './index.scss';
|
||||
|
||||
interface Props {
|
||||
address: string;
|
||||
|
@ -96,54 +12,65 @@ interface Props {
|
|||
}
|
||||
|
||||
export default class PaperWallet extends React.Component<Props, {}> {
|
||||
private container: HTMLElement | null;
|
||||
|
||||
public render() {
|
||||
const { privateKey } = this.props;
|
||||
const address = toChecksumAddress(addHexPrefix(this.props.address));
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<img src={sidebarImg} style={styles.sidebar} alt="MyCrypto Logo" />
|
||||
<img src={ethLogo} style={styles.ethLogo} alt="ETH Logo" />
|
||||
<div className="PaperWallet" ref={el => (this.container = el)}>
|
||||
<img src={sidebarImg} className="PaperWallet-sidebar" alt="MyCrypto Logo" />
|
||||
|
||||
<div style={styles.block}>
|
||||
<div style={styles.box}>
|
||||
<div className="PaperWallet-block">
|
||||
<div className="PaperWallet-block-box">
|
||||
<QRCode data={address} />
|
||||
</div>
|
||||
<p style={styles.blockText}>YOUR ADDRESS</p>
|
||||
<p className="PaperWallet-block-text">YOUR ADDRESS</p>
|
||||
</div>
|
||||
|
||||
<div style={styles.block}>
|
||||
<img src={notesBg} style={styles.box} aria-hidden={true} />
|
||||
<p style={styles.blockText}>AMOUNT / NOTES</p>
|
||||
<div className="PaperWallet-block">
|
||||
<img src={notesBg} className="PaperWallet-block-box is-shaded" aria-hidden={true} />
|
||||
<p className="PaperWallet-block-text">AMOUNT / NOTES</p>
|
||||
</div>
|
||||
|
||||
<div style={styles.block}>
|
||||
<div style={styles.box}>
|
||||
<div className="PaperWallet-block">
|
||||
<div className="PaperWallet-block-box">
|
||||
<QRCode data={privateKey} />
|
||||
</div>
|
||||
<p style={styles.blockText}>YOUR PRIVATE KEY</p>
|
||||
<p className="PaperWallet-block-text">YOUR PRIVATE KEY</p>
|
||||
</div>
|
||||
|
||||
<div style={styles.infoContainer}>
|
||||
<p style={styles.infoText}>
|
||||
<strong style={styles.infoLabel}>Your Address:</strong>
|
||||
<div className="PaperWallet-info">
|
||||
<p className="PaperWallet-info-text">
|
||||
<strong className="PaperWallet-info-text-label">Your Address:</strong>
|
||||
<br />
|
||||
{address}
|
||||
</p>
|
||||
<p style={styles.infoText}>
|
||||
<strong style={styles.infoLabel}>Your Private Key:</strong>
|
||||
<p className="PaperWallet-info-text">
|
||||
<strong className="PaperWallet-info-text-label">Your Private Key:</strong>
|
||||
<br />
|
||||
{privateKey}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={styles.identiconContainer}>
|
||||
<div style={{ float: 'left' }}>
|
||||
<div className="PaperWallet-identicon">
|
||||
<div className="PaperWallet-identicon-left">
|
||||
<Identicon address={address} size={'42px'} />
|
||||
</div>
|
||||
<p style={styles.identiconText}>Always look for this icon when sending to this wallet</p>
|
||||
<p className="PaperWallet-identicon-text">
|
||||
Always look for this icon when sending to this wallet
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public toPNG = async () => {
|
||||
if (!this.container) {
|
||||
return '';
|
||||
}
|
||||
const canvas = await html2canvas(this.container);
|
||||
return canvas.toDataURL('image/png');
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,53 +1,53 @@
|
|||
import { PaperWallet } from 'components';
|
||||
import React from 'react';
|
||||
import printElement from 'utils/printElement';
|
||||
import { PaperWallet } from 'components';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
|
||||
export const print = (address: string, privateKey: string) => () =>
|
||||
address &&
|
||||
privateKey &&
|
||||
printElement(<PaperWallet address={address} privateKey={privateKey} />, {
|
||||
popupFeatures: {
|
||||
scrollbars: 'no'
|
||||
},
|
||||
styles: `
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 1rem;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
}
|
||||
`
|
||||
});
|
||||
import translate from 'translations';
|
||||
|
||||
interface Props {
|
||||
address: string;
|
||||
privateKey: string;
|
||||
}
|
||||
|
||||
const PrintableWallet: React.SFC<Props> = ({ address, privateKey }) => {
|
||||
const pkey = stripHexPrefix(privateKey);
|
||||
interface State {
|
||||
paperWalletImage: string;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PaperWallet address={address} privateKey={pkey} />
|
||||
<a
|
||||
role="button"
|
||||
aria-label={translateRaw('X_PRINT')}
|
||||
aria-describedby="x_PrintDesc"
|
||||
className="btn btn-lg btn-primary btn-block"
|
||||
onClick={print(address, pkey)}
|
||||
style={{ margin: '10px auto 0', maxWidth: '260px' }}
|
||||
>
|
||||
{translate('X_PRINT')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default class PrintableWallet extends React.Component<Props, State> {
|
||||
public state: State = {
|
||||
paperWalletImage: ''
|
||||
};
|
||||
|
||||
export default PrintableWallet;
|
||||
private paperWallet: PaperWallet | null;
|
||||
|
||||
public componentDidMount() {
|
||||
setTimeout(() => {
|
||||
if (!this.paperWallet) {
|
||||
return this.componentDidMount();
|
||||
}
|
||||
|
||||
this.paperWallet.toPNG().then(png => this.setState({ paperWalletImage: png }));
|
||||
}, 500);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { address, privateKey } = this.props;
|
||||
const { paperWalletImage } = this.state;
|
||||
const pkey = stripHexPrefix(privateKey);
|
||||
const disabled = paperWalletImage ? '' : 'disabled';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PaperWallet address={address} privateKey={pkey} ref={c => (this.paperWallet = c)} />
|
||||
<a
|
||||
role="button"
|
||||
href={paperWalletImage}
|
||||
className={`btn btn-lg btn-primary btn-block ${disabled}`}
|
||||
style={{ margin: '10px auto 0', maxWidth: '260px' }}
|
||||
download={`paper-wallet-0x${address.substr(0, 6)}`}
|
||||
>
|
||||
{translate('X_SAVE_PAPER')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Select, { Option } from 'react-select';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import {
|
||||
DeterministicWalletData,
|
||||
|
@ -201,14 +200,13 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
|
||||
private getAddresses(props: Props = this.props) {
|
||||
const { dPath, publicKey, chainCode, seed } = props;
|
||||
|
||||
if (dPath && ((publicKey && chainCode) || seed)) {
|
||||
if (isValidPath(dPath.value)) {
|
||||
this.props.getDeterministicWallets({
|
||||
seed,
|
||||
dPath: dPath.value,
|
||||
publicKey,
|
||||
chainCode,
|
||||
dPath: dPath.value,
|
||||
limit: WALLETS_PER_PAGE,
|
||||
offset: WALLETS_PER_PAGE * this.state.page
|
||||
});
|
||||
|
@ -280,7 +278,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
|
|||
private renderWalletRow(wallet: DeterministicWalletData) {
|
||||
const { desiredToken, network, addressLabels } = this.props;
|
||||
const { selectedAddress } = this.state;
|
||||
const label = addressLabels[toChecksumAddress(wallet.address)];
|
||||
const label = addressLabels[wallet.address.toLowerCase()];
|
||||
const spanClassName = label ? 'DWModal-addresses-table-address-text' : '';
|
||||
|
||||
// Get renderable values, but keep 'em short
|
||||
|
|
|
@ -2,19 +2,20 @@ import React, { PureComponent } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import Select, { Option } from 'react-select';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import { AddressOnlyWallet } from 'libs/wallet';
|
||||
import { getRecentAddresses } from 'selectors/wallet';
|
||||
import { AppState } from 'reducers';
|
||||
import { Input, Identicon } from 'components/ui';
|
||||
import './ViewOnly.scss';
|
||||
import { getIsValidAddressFn } from 'selectors/config';
|
||||
|
||||
interface OwnProps {
|
||||
onUnlock(param: any): void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
recentAddresses: AppState['wallet']['recentAddresses'];
|
||||
recentAddresses: ReturnType<typeof getRecentAddresses>;
|
||||
isValidAddress: ReturnType<typeof getIsValidAddressFn>;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
@ -29,9 +30,9 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { recentAddresses } = this.props;
|
||||
const { recentAddresses, isValidAddress } = this.props;
|
||||
const { address } = this.state;
|
||||
const isValid = isValidETHAddress(address);
|
||||
const isValid = isValidAddress(address);
|
||||
|
||||
const recentOptions = (recentAddresses.map(addr => ({
|
||||
label: (
|
||||
|
@ -89,8 +90,9 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
|||
ev.preventDefault();
|
||||
}
|
||||
|
||||
const { isValidAddress } = this.props;
|
||||
const { address } = this.state;
|
||||
if (isValidETHAddress(address)) {
|
||||
if (isValidAddress(address)) {
|
||||
const wallet = new AddressOnlyWallet(address);
|
||||
this.props.onUnlock(wallet);
|
||||
}
|
||||
|
@ -98,5 +100,6 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
export const ViewOnlyDecrypt = connect((state: AppState): StateProps => ({
|
||||
recentAddresses: getRecentAddresses(state)
|
||||
recentAddresses: getRecentAddresses(state),
|
||||
isValidAddress: getIsValidAddressFn(state)
|
||||
}))(ViewOnlyDecryptClass);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export * from './AddressField';
|
||||
export * from './DataField';
|
||||
export * from './GasLimitField';
|
||||
export * from './AmountField';
|
||||
|
@ -9,11 +8,13 @@ export * from './GenerateTransaction';
|
|||
export * from './SendButton';
|
||||
export * from './SigningStatus';
|
||||
export * from '../containers/Tabs/ScheduleTransaction/components';
|
||||
export { default as AddressField } from './AddressField';
|
||||
export { default as NonceField } from './NonceField';
|
||||
export { default as Header } from './Header';
|
||||
export { default as Footer } from './Footer';
|
||||
export { default as BalanceSidebar } from './BalanceSidebar';
|
||||
export { default as PaperWallet } from './PaperWallet';
|
||||
export { default as PrintableWallet } from './PrintableWallet';
|
||||
export { default as TXMetaDataPanel } from './TXMetaDataPanel';
|
||||
export { default as WalletDecrypt } from './WalletDecrypt';
|
||||
export { default as TogglablePassword } from './TogglablePassword';
|
||||
|
|
|
@ -1,40 +1,42 @@
|
|||
import React from 'react';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import NewTabLink from './NewTabLink';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import { BlockExplorerConfig } from 'types/network';
|
||||
import { getChecksumAddressFn } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface BaseProps {
|
||||
explorer?: BlockExplorerConfig | null;
|
||||
address?: string | null;
|
||||
wallet?: IWallet | null;
|
||||
}
|
||||
|
||||
interface AddressProps extends BaseProps {
|
||||
address: string;
|
||||
interface StateProps {
|
||||
toChecksumAddress: ReturnType<typeof getChecksumAddressFn>;
|
||||
}
|
||||
|
||||
interface WalletProps extends BaseProps {
|
||||
wallet: IWallet;
|
||||
}
|
||||
type Props = BaseProps & StateProps;
|
||||
|
||||
type Props = AddressProps | WalletProps;
|
||||
export class Address extends React.PureComponent<Props> {
|
||||
public render() {
|
||||
const { wallet, address, explorer, toChecksumAddress } = this.props;
|
||||
let renderAddress = '';
|
||||
if (address !== null && address !== undefined) {
|
||||
renderAddress = address;
|
||||
} else {
|
||||
renderAddress = wallet !== null && wallet !== undefined ? wallet.getAddressString() : '';
|
||||
}
|
||||
renderAddress = toChecksumAddress(renderAddress);
|
||||
|
||||
const isAddressProps = (props: Props): props is AddressProps =>
|
||||
typeof (props as AddressProps).address === 'string';
|
||||
|
||||
const Address: React.SFC<Props> = props => {
|
||||
let addr = '';
|
||||
if (isAddressProps(props)) {
|
||||
addr = props.address;
|
||||
} else {
|
||||
addr = props.wallet.getAddressString();
|
||||
if (explorer) {
|
||||
return <NewTabLink href={explorer.addressUrl(renderAddress)}>{renderAddress}</NewTabLink>;
|
||||
} else {
|
||||
return <React.Fragment>{renderAddress}</React.Fragment>;
|
||||
}
|
||||
}
|
||||
addr = toChecksumAddress(addr);
|
||||
}
|
||||
|
||||
if (props.explorer) {
|
||||
return <NewTabLink href={props.explorer.addressUrl(addr)}>{addr}</NewTabLink>;
|
||||
} else {
|
||||
return <React.Fragment>{addr}</React.Fragment>;
|
||||
}
|
||||
};
|
||||
|
||||
export default Address;
|
||||
export default connect((state: AppState) => ({
|
||||
toChecksumAddress: getChecksumAddressFn(state)
|
||||
}))(Address);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
.Identicon {
|
||||
position: relative;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
|
||||
&-img {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&-shadow {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 2px rgba(#000, 0.15),
|
||||
0 0 3px rgba(#000, 0.15) inset;
|
||||
}
|
||||
}
|
|
@ -1,49 +1,43 @@
|
|||
import { isValidETHAddress } from 'libs/validators';
|
||||
import React from 'react';
|
||||
import makeBlockie from 'ethereum-blockies-base64';
|
||||
import { connect } from 'react-redux';
|
||||
import { getIsValidAddressFn } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import './Identicon.scss';
|
||||
|
||||
interface Props {
|
||||
interface OwnProps {
|
||||
address: string;
|
||||
className?: string;
|
||||
size?: string;
|
||||
}
|
||||
|
||||
export default function Identicon(props: Props) {
|
||||
interface StateProps {
|
||||
isValidAddress: ReturnType<typeof getIsValidAddressFn>;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
const Identicon: React.SFC<Props> = props => {
|
||||
const size = props.size || '4rem';
|
||||
const { address, className = '' } = props;
|
||||
const identiconDataUrl = isValidETHAddress(address) ? makeBlockie(address) : '';
|
||||
const { address, isValidAddress, className = '' } = props;
|
||||
const identiconDataUrl = isValidAddress(address) ? makeBlockie(address) : '';
|
||||
|
||||
return (
|
||||
// Use inline styles for printable wallets
|
||||
<div
|
||||
className={`Identicon ${className}`}
|
||||
title="Address Identicon"
|
||||
style={{ width: size, height: size, position: 'relative' }}
|
||||
style={{ width: size, height: size }}
|
||||
aria-hidden={!identiconDataUrl}
|
||||
>
|
||||
{identiconDataUrl && (
|
||||
<img
|
||||
src={identiconDataUrl}
|
||||
alt="Unique Address Image"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
padding: '0px',
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
/>
|
||||
<img className="Identicon-img" src={identiconDataUrl} alt="Unique Address Image" />
|
||||
)}
|
||||
<div
|
||||
className="border"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
top: 0,
|
||||
boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.15), inset 0 0 3px 0 rgba(0, 0, 0, 0.15)',
|
||||
borderRadius: '50%',
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
<div className="Identicon-shadow" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default connect((state: AppState): StateProps => ({
|
||||
isValidAddress: getIsValidAddressFn(state)
|
||||
}))(Identicon);
|
||||
|
|
|
@ -1 +1,7 @@
|
|||
[]
|
||||
[
|
||||
{
|
||||
"name": "Bridge",
|
||||
"address": "0x0000000000000000000000000000000001000006",
|
||||
"abi": "[{ \"name\": \"getFederationAddress\", \"type\": \"function\", \"constant\": true, \"inputs\": [], \"outputs\": [{ \"name\": \"\", \"type\": \"string\" }] }]"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -78,6 +78,11 @@ export const ETH_SINGULAR: DPath = {
|
|||
value: "m/0'/0'/0'"
|
||||
};
|
||||
|
||||
export const RSK_TESTNET: DPath = {
|
||||
label: 'Testnet (RSK)',
|
||||
value: "m/44'/37310'/0'/0"
|
||||
};
|
||||
|
||||
export const GO_DEFAULT: DPath = {
|
||||
label: 'Default (GO)',
|
||||
value: "m/44'/6060'/0'/0"
|
||||
|
@ -99,6 +104,7 @@ export const DPaths: DPath[] = [
|
|||
ETSC_DEFAULT,
|
||||
EGEM_DEFAULT,
|
||||
CLO_DEFAULT,
|
||||
RSK_TESTNET,
|
||||
GO_DEFAULT
|
||||
];
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@ import { connect } from 'react-redux';
|
|||
import Select from 'react-select';
|
||||
import moment from 'moment';
|
||||
import translate from 'translations';
|
||||
import { isValidTxHash, isValidETHAddress } from 'libs/validators';
|
||||
import { isValidTxHash } from 'libs/validators';
|
||||
import { getRecentNetworkTransactions } from 'selectors/transactions';
|
||||
import { getIsValidAddressFn } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { Input } from 'components/ui';
|
||||
import './TxHashInput.scss';
|
||||
|
@ -15,6 +16,7 @@ interface OwnProps {
|
|||
}
|
||||
interface ReduxProps {
|
||||
recentTxs: AppState['transactions']['recent'];
|
||||
isValidAddress: ReturnType<typeof getIsValidAddressFn>;
|
||||
}
|
||||
type Props = OwnProps & ReduxProps;
|
||||
|
||||
|
@ -40,7 +42,7 @@ class TxHashInput extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { recentTxs } = this.props;
|
||||
const { recentTxs, isValidAddress } = this.props;
|
||||
const { hash } = this.state;
|
||||
|
||||
let selectOptions: Option[] = [];
|
||||
|
@ -81,7 +83,7 @@ class TxHashInput extends React.Component<Props, State> {
|
|||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
{isValidETHAddress(hash) && (
|
||||
{isValidAddress(hash) && (
|
||||
<p className="TxHashInput-message help-block is-invalid">
|
||||
{translate('SELECT_RECENT_TX_BY_TXHASH')}
|
||||
</p>
|
||||
|
@ -116,5 +118,6 @@ class TxHashInput extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
export default connect((state: AppState): ReduxProps => ({
|
||||
recentTxs: getRecentNetworkTransactions(state)
|
||||
recentTxs: getRecentNetworkTransactions(state),
|
||||
isValidAddress: getIsValidAddressFn(state)
|
||||
}))(TxHashInput);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { Component } from 'react';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { getNetworkContracts } from 'selectors/config';
|
||||
import { getNetworkContracts, getIsValidAddressFn } from 'selectors/config';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { isValidETHAddress, isValidAbiJson } from 'libs/validators';
|
||||
import { isValidAbiJson } from 'libs/validators';
|
||||
import { NetworkContract } from 'types/network';
|
||||
import { donationAddressMap } from 'config';
|
||||
import { Input, TextArea, CodeBlock, Dropdown } from 'components/ui';
|
||||
|
@ -20,6 +20,7 @@ interface ContractOption {
|
|||
interface StateProps {
|
||||
currentTo: ReturnType<typeof getCurrentTo>;
|
||||
contracts: NetworkContract[];
|
||||
isValidAddress: ReturnType<typeof getIsValidAddressFn>;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
|
@ -75,9 +76,9 @@ class InteractForm extends Component<Props, State> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { contracts, accessContract, currentTo } = this.props;
|
||||
const { contracts, accessContract, currentTo, isValidAddress } = this.props;
|
||||
const { abiJson, contract } = this.state;
|
||||
const validEthAddress = isValidETHAddress(
|
||||
const validEthAddress = isValidAddress(
|
||||
currentTo.value ? addHexPrefix(currentTo.value.toString('hex')) : ''
|
||||
);
|
||||
const validAbiJson = isValidAbiJson(abiJson);
|
||||
|
@ -203,7 +204,8 @@ class InteractForm extends Component<Props, State> {
|
|||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
contracts: getNetworkContracts(state) || [],
|
||||
currentTo: getCurrentTo(state)
|
||||
currentTo: getCurrentTo(state),
|
||||
isValidAddress: getIsValidAddressFn(state)
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { setCurrentTo })(InteractForm);
|
||||
|
|
|
@ -18,6 +18,7 @@ interface State {
|
|||
passwordValidation: ZXCVBNResult | null;
|
||||
feedback: string;
|
||||
}
|
||||
|
||||
export default class EnterPassword extends Component<Props, State> {
|
||||
public state: State = {
|
||||
password: '',
|
||||
|
|
|
@ -7,6 +7,7 @@ import DownloadWallet from './DownloadWallet';
|
|||
import EnterPassword from './EnterPassword';
|
||||
import PaperWallet from './PaperWallet';
|
||||
import FinalSteps from '../FinalSteps';
|
||||
import { N_FACTOR } from 'config';
|
||||
|
||||
export enum Steps {
|
||||
Password = 'password',
|
||||
|
@ -87,7 +88,7 @@ export default class GenerateKeystore extends Component<{}, State> {
|
|||
private generateWalletAndContinue = (password: string) => {
|
||||
this.setState({ isGenerating: true });
|
||||
|
||||
generateKeystore(password).then(res => {
|
||||
generateKeystore(password, N_FACTOR).then(res => {
|
||||
this.setState({
|
||||
password,
|
||||
activeStep: Steps.Download,
|
||||
|
|
|
@ -31,7 +31,7 @@ const PaperWallet: React.SFC<Props> = props => (
|
|||
</label>
|
||||
|
||||
{/* Download Paper Wallet */}
|
||||
<h2 className="GenPaper-title">{translate('X_PRINT')}</h2>
|
||||
<h2 className="GenPaper-title">{translate('X_SAVE_PAPER')}</h2>
|
||||
<div className="GenPaper-paper">
|
||||
<PrintableWallet address={props.keystore.address} privateKey={props.privateKey} />
|
||||
</div>
|
||||
|
|
|
@ -1,43 +1,63 @@
|
|||
import React from 'react';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import translate, { translateRaw } from 'translations';
|
||||
import { IWallet } from 'libs/wallet';
|
||||
import { print } from 'components/PrintableWallet';
|
||||
import { QRCode } from 'components/ui';
|
||||
import { GenerateKeystoreModal, TogglablePassword, AddressField } from 'components';
|
||||
import { QRCode, Modal } from 'components/ui';
|
||||
import {
|
||||
GenerateKeystoreModal,
|
||||
TogglablePassword,
|
||||
AddressField,
|
||||
PrintableWallet
|
||||
} from 'components';
|
||||
import './WalletInfo.scss';
|
||||
import { getChecksumAddressFn } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface Props {
|
||||
interface OwnProps {
|
||||
wallet: IWallet;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
toChecksumAddress: ReturnType<typeof getChecksumAddressFn>;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
interface State {
|
||||
address: string;
|
||||
privateKey: string;
|
||||
isPrivateKeyVisible: boolean;
|
||||
isKeystoreModalOpen: boolean;
|
||||
isPaperWalletModalOpen: boolean;
|
||||
}
|
||||
|
||||
export default class WalletInfo extends React.PureComponent<Props, State> {
|
||||
class WalletInfo extends React.PureComponent<Props, State> {
|
||||
public state = {
|
||||
address: '',
|
||||
privateKey: '',
|
||||
isPrivateKeyVisible: false,
|
||||
isKeystoreModalOpen: false
|
||||
isKeystoreModalOpen: false,
|
||||
isPaperWalletModalOpen: false
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.setStateFromWallet(this.props.wallet);
|
||||
this.setStateFromWallet(this.props);
|
||||
}
|
||||
|
||||
public UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.wallet !== nextProps.wallet) {
|
||||
this.setStateFromWallet(nextProps.wallet);
|
||||
this.setStateFromWallet(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { address, privateKey, isPrivateKeyVisible, isKeystoreModalOpen } = this.state;
|
||||
const {
|
||||
address,
|
||||
privateKey,
|
||||
isPrivateKeyVisible,
|
||||
isKeystoreModalOpen,
|
||||
isPaperWalletModalOpen
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div className="WalletInfo">
|
||||
|
@ -85,11 +105,11 @@ export default class WalletInfo extends React.PureComponent<Props, State> {
|
|||
<div className="col-xs-6">
|
||||
<label>{translate('WALLET_INFO_UTILITIES')}</label>
|
||||
|
||||
<button className="btn btn-info btn-block" onClick={print(address, privateKey)}>
|
||||
{translate('X_PRINT')}
|
||||
<button className="btn btn-info btn-block" onClick={this.openPaperWalletModal}>
|
||||
{translate('X_SAVE_PAPER')}
|
||||
</button>
|
||||
|
||||
<button className="btn btn-info btn-block" onClick={this.toggleKeystoreModal}>
|
||||
<button className="btn btn-info btn-block" onClick={this.openKeystoreModal}>
|
||||
{translate('GENERATE_KEYSTORE_TITLE')}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -99,15 +119,20 @@ export default class WalletInfo extends React.PureComponent<Props, State> {
|
|||
<GenerateKeystoreModal
|
||||
isOpen={isKeystoreModalOpen}
|
||||
privateKey={privateKey}
|
||||
handleClose={this.toggleKeystoreModal}
|
||||
handleClose={this.closeKeystoreModal}
|
||||
/>
|
||||
|
||||
<Modal isOpen={isPaperWalletModalOpen} handleClose={this.closePaperWalletModal}>
|
||||
<PrintableWallet address={address} privateKey={privateKey} />
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private setStateFromWallet(wallet: IWallet) {
|
||||
private setStateFromWallet(props: Props) {
|
||||
const { wallet, toChecksumAddress } = props;
|
||||
const address = toChecksumAddress(wallet.getAddressString());
|
||||
const privateKey = wallet.getPrivateKeyString ? wallet.getPrivateKeyString() : '';
|
||||
this.setState({ address, privateKey });
|
||||
|
@ -117,7 +142,13 @@ export default class WalletInfo extends React.PureComponent<Props, State> {
|
|||
this.setState({ isPrivateKeyVisible: !this.state.isPrivateKeyVisible });
|
||||
};
|
||||
|
||||
private toggleKeystoreModal = () => {
|
||||
this.setState({ isKeystoreModalOpen: !this.state.isKeystoreModalOpen });
|
||||
};
|
||||
private openKeystoreModal = () => this.setState({ isKeystoreModalOpen: true });
|
||||
private closeKeystoreModal = () => this.setState({ isKeystoreModalOpen: false });
|
||||
|
||||
private openPaperWalletModal = () => this.setState({ isPaperWalletModalOpen: true });
|
||||
private closePaperWalletModal = () => this.setState({ isPaperWalletModalOpen: false });
|
||||
}
|
||||
|
||||
export default connect((state: AppState): StateProps => ({
|
||||
toChecksumAddress: getChecksumAddressFn(state)
|
||||
}))(WalletInfo);
|
||||
|
|
|
@ -103,6 +103,9 @@ class FieldsClass extends Component<Props> {
|
|||
}
|
||||
|
||||
export const Fields = connect(
|
||||
(state: AppState) => ({ unit: getUnit(state), currentBalance: getCurrentBalance(state) }),
|
||||
(state: AppState) => ({
|
||||
unit: getUnit(state),
|
||||
currentBalance: getCurrentBalance(state)
|
||||
}),
|
||||
{ resetWallet }
|
||||
)(FieldsClass);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import abi from 'ethereumjs-abi';
|
||||
import { toChecksumAddress, addHexPrefix, stripHexPrefix } from 'ethereumjs-util';
|
||||
import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util';
|
||||
import BN from 'bn.js';
|
||||
import { toChecksumAddressByChainId } from 'utils/formatters';
|
||||
import {
|
||||
FuncParams,
|
||||
FunctionOutputMappings,
|
||||
|
@ -33,7 +34,7 @@ export default class AbiFunction {
|
|||
return encodedCall;
|
||||
};
|
||||
|
||||
public decodeInput = (argString: string) => {
|
||||
public decodeInput = (argString: string, chainId: number) => {
|
||||
// Remove method selector from data, if present
|
||||
argString = argString.replace(addHexPrefix(this.methodSelector), '');
|
||||
// Convert argdata to a hex buffer for ethereumjs-abi
|
||||
|
@ -46,12 +47,12 @@ export default class AbiFunction {
|
|||
const currType = this.inputTypes[index];
|
||||
return {
|
||||
...argObj,
|
||||
[currName]: this.parsePostDecodedValue(currType, currArg)
|
||||
[currName]: this.parsePostDecodedValue(currType, currArg, chainId)
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
||||
public decodeOutput = (argString: string) => {
|
||||
public decodeOutput = (argString: string, chainId: number) => {
|
||||
// Remove method selector from data, if present
|
||||
argString = argString.replace(addHexPrefix(this.methodSelector), '');
|
||||
|
||||
|
@ -69,7 +70,7 @@ export default class AbiFunction {
|
|||
const currType = this.outputTypes[index];
|
||||
return {
|
||||
...argObj,
|
||||
[currName]: this.parsePostDecodedValue(currType, currArg)
|
||||
[currName]: this.parsePostDecodedValue(currType, currArg, chainId)
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
@ -85,9 +86,9 @@ export default class AbiFunction {
|
|||
this.methodSelector = abi.methodID(this.name, this.inputTypes).toString('hex');
|
||||
}
|
||||
|
||||
private parsePostDecodedValue = (type: string, value: any) => {
|
||||
private parsePostDecodedValue = (type: string, value: any, chainId: number) => {
|
||||
const valueMapping: ITypeMapping = {
|
||||
address: (val: any) => toChecksumAddress(val.toString(16))
|
||||
address: (val: any) => toChecksumAddressByChainId(val.toString(16), chainId)
|
||||
};
|
||||
|
||||
const mapppedType = valueMapping[type];
|
||||
|
|
|
@ -104,6 +104,7 @@ export const NODE_CONFIGS: { [key in StaticNetworkIds]: RawNodeConfig[] } = {
|
|||
url: 'https://node.expanse.tech/'
|
||||
}
|
||||
],
|
||||
|
||||
POA: [
|
||||
{
|
||||
name: makeNodeName('POA', 'core'),
|
||||
|
@ -167,6 +168,15 @@ export const NODE_CONFIGS: { [key in StaticNetworkIds]: RawNodeConfig[] } = {
|
|||
}
|
||||
],
|
||||
|
||||
RSK_TESTNET: [
|
||||
{
|
||||
name: makeNodeName('RSK_TESTNET', 'rsk_testnet'),
|
||||
type: 'rpc',
|
||||
service: 'mycrypto.testnet.rsk.co',
|
||||
url: 'https://mycrypto.testnet.rsk.co/'
|
||||
}
|
||||
],
|
||||
|
||||
GO: [
|
||||
{
|
||||
name: makeNodeName('GO', 'go'),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Tx from 'ethereumjs-tx';
|
||||
import { bufferToHex } from 'ethereumjs-util';
|
||||
import { Wei } from 'libs/units';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import { isValidAddress } from 'libs/validators';
|
||||
import { IFullWallet } from 'libs/wallet';
|
||||
import { translateRaw } from 'translations';
|
||||
import { ITransaction, IHexStrTransaction } from '../typings';
|
||||
|
@ -69,7 +69,7 @@ const gasParamsInRange = (t: ITransaction) => {
|
|||
};
|
||||
|
||||
const validAddress = (t: ITransaction) => {
|
||||
if (!isValidETHAddress(bufferToHex(t.to))) {
|
||||
if (!isValidAddress(bufferToHex(t.to), t.chainId)) {
|
||||
throw Error(translateRaw('ERROR_5'));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { toChecksumAddress, isValidPrivate } from 'ethereumjs-util';
|
||||
import { isValidChecksumAddress as isValidChecksumRSKAddress } from 'rskjs-util';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
import WalletAddressValidator from 'wallet-address-validator';
|
||||
import { normalise } from './ens';
|
||||
|
@ -16,8 +17,18 @@ import { dPathRegex, ETC_LEDGER, ETH_SINGULAR } from 'config/dpaths';
|
|||
import { EAC_SCHEDULING_CONFIG } from './scheduling';
|
||||
import BN from 'bn.js';
|
||||
|
||||
// FIXME we probably want to do checksum checks sideways
|
||||
export function isValidETHAddress(address: string): boolean {
|
||||
export function getIsValidAddressFunction(chainId: number) {
|
||||
if (chainId === 30 || chainId === 31) {
|
||||
return (address: string) => isValidRSKAddress(address, chainId);
|
||||
}
|
||||
return isValidETHAddress;
|
||||
}
|
||||
|
||||
export function isValidAddress(address: string, chainId: number) {
|
||||
return getIsValidAddressFunction(chainId)(address);
|
||||
}
|
||||
|
||||
function isValidETHLikeAddress(address: string, extraChecks?: () => boolean): boolean {
|
||||
if (address === '0x0000000000000000000000000000000000000000') {
|
||||
return false;
|
||||
}
|
||||
|
@ -28,10 +39,18 @@ export function isValidETHAddress(address: string): boolean {
|
|||
} else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) {
|
||||
return true;
|
||||
} else {
|
||||
return isChecksumAddress(address);
|
||||
return extraChecks ? extraChecks() : false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidETHAddress(address: string): boolean {
|
||||
return isValidETHLikeAddress(address, () => isChecksumAddress(address));
|
||||
}
|
||||
|
||||
export function isValidRSKAddress(address: string, chainId: number): boolean {
|
||||
return isValidETHLikeAddress(address, () => isValidChecksumRSKAddress(address, chainId));
|
||||
}
|
||||
|
||||
export const isCreationAddress = (address: string): boolean =>
|
||||
address === '0x0' || address === '0x0000000000000000000000000000000000000000';
|
||||
|
||||
|
@ -339,15 +358,16 @@ export function isValidAddressLabel(
|
|||
address: string,
|
||||
label: string,
|
||||
addresses: { [address: string]: string },
|
||||
labels: { [label: string]: string }
|
||||
labels: { [label: string]: string },
|
||||
chainId: number
|
||||
) {
|
||||
const addressAlreadyExists = !!addresses[address];
|
||||
const addressAlreadyExists = !!addresses[address.toLowerCase()];
|
||||
const labelAlreadyExists = !!labels[label];
|
||||
const result: { isValid: boolean; addressError?: string; labelError?: string } = {
|
||||
isValid: true
|
||||
};
|
||||
|
||||
if (!isValidETHAddress(address)) {
|
||||
if (!isValidAddress(address, chainId)) {
|
||||
result.addressError = translateRaw('INVALID_ADDRESS');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getTransactionFields, makeTransaction } from 'libs/transaction';
|
||||
import { IFullWallet } from '../IWallet';
|
||||
import { bufferToHex, toChecksumAddress } from 'ethereumjs-util';
|
||||
import { bufferToHex } from 'ethereumjs-util';
|
||||
import { configuredStore } from 'store';
|
||||
import { getNodeLib, getNetworkByChainId } from 'selectors/config';
|
||||
import Web3Node from 'libs/nodes/web3';
|
||||
|
@ -16,7 +16,7 @@ export default class Web3Wallet implements IFullWallet {
|
|||
}
|
||||
|
||||
public getAddressString(): string {
|
||||
return toChecksumAddress(this.address);
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public signRawTransaction(): Promise<Buffer> {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { IV3Wallet } from 'ethereumjs-wallet';
|
||||
import { N_FACTOR } from 'config';
|
||||
import Worker from 'worker-loader!./workers/generateKeystore.worker.ts';
|
||||
|
||||
interface KeystorePayload {
|
||||
|
@ -8,7 +7,10 @@ interface KeystorePayload {
|
|||
privateKey: string;
|
||||
}
|
||||
|
||||
export default function generateKeystore(password: string): Promise<KeystorePayload> {
|
||||
export default function generateKeystore(
|
||||
password: string,
|
||||
N_FACTOR: number
|
||||
): Promise<KeystorePayload> {
|
||||
return new Promise(resolve => {
|
||||
const worker = new Worker();
|
||||
worker.postMessage({ password, N_FACTOR });
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import { TypeKeys, AddressBookAction, AddressLabelEntry } from 'actions/addressBook';
|
||||
|
||||
export interface State {
|
||||
|
@ -23,15 +22,16 @@ export function addressBook(state: State = INITIAL_STATE, action: AddressBookAct
|
|||
switch (action.type) {
|
||||
case TypeKeys.SET_ADDRESS_LABEL: {
|
||||
const { addresses, labels } = state;
|
||||
const { address, label } = action.payload;
|
||||
const checksummedAddress = toChecksumAddress(address);
|
||||
const { label } = action.payload;
|
||||
const address = action.payload.address.toLowerCase();
|
||||
|
||||
const updatedAddresses = {
|
||||
...addresses,
|
||||
[checksummedAddress]: label
|
||||
[address]: label
|
||||
};
|
||||
const updatedLabels = {
|
||||
...labels,
|
||||
[label]: checksummedAddress
|
||||
[label]: address
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -43,12 +43,12 @@ export function addressBook(state: State = INITIAL_STATE, action: AddressBookAct
|
|||
|
||||
case TypeKeys.CLEAR_ADDRESS_LABEL: {
|
||||
const { addresses, labels } = state;
|
||||
const address = action.payload;
|
||||
const address = action.payload.toLowerCase();
|
||||
const label = addresses[address];
|
||||
const updatedAddresses = { ...addresses };
|
||||
const updatedLabels = { ...labels };
|
||||
|
||||
delete updatedAddresses[toChecksumAddress(address)];
|
||||
delete updatedAddresses[address];
|
||||
delete updatedLabels[label];
|
||||
|
||||
return {
|
||||
|
@ -59,9 +59,8 @@ export function addressBook(state: State = INITIAL_STATE, action: AddressBookAct
|
|||
}
|
||||
|
||||
case TypeKeys.SET_ADDRESS_LABEL_ENTRY: {
|
||||
const { id, address } = action.payload;
|
||||
const checksummedAddress = toChecksumAddress(address);
|
||||
const isNonRowEntry = id === 'ADDRESS_BOOK_TABLE_ID' || id === 'ACCOUNT_ADDRESS_ID';
|
||||
const { id } = action.payload;
|
||||
const address = action.payload.address.toLowerCase();
|
||||
|
||||
return {
|
||||
...state,
|
||||
|
@ -69,7 +68,7 @@ export function addressBook(state: State = INITIAL_STATE, action: AddressBookAct
|
|||
...state.entries,
|
||||
[id]: {
|
||||
...action.payload,
|
||||
address: isNonRowEntry ? address : checksummedAddress
|
||||
address
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
ETSC_DEFAULT,
|
||||
EGEM_DEFAULT,
|
||||
CLO_DEFAULT,
|
||||
RSK_TESTNET,
|
||||
GO_DEFAULT
|
||||
} from 'config/dpaths';
|
||||
import { ConfigAction } from 'actions/config';
|
||||
|
@ -367,6 +368,32 @@ export const INITIAL_STATE: State = {
|
|||
}
|
||||
},
|
||||
|
||||
RSK_TESTNET: {
|
||||
id: 'RSK_TESTNET',
|
||||
name: 'RSK',
|
||||
unit: 'SBTC',
|
||||
chainId: 31,
|
||||
color: '#58A052',
|
||||
isCustom: false,
|
||||
blockExplorer: makeExplorer({
|
||||
name: 'RSK Testnet Explorer',
|
||||
origin: 'https://explorer.testnet.rsk.co'
|
||||
}),
|
||||
tokens: require('config/tokens/rsk.json'),
|
||||
contracts: require('config/contracts/rsk.json'),
|
||||
isTestnet: true,
|
||||
dPathFormats: {
|
||||
[SecureWalletName.TREZOR]: RSK_TESTNET,
|
||||
[SecureWalletName.LEDGER_NANO_S]: RSK_TESTNET,
|
||||
[InsecureWalletName.MNEMONIC_PHRASE]: RSK_TESTNET
|
||||
},
|
||||
gasPriceSettings: {
|
||||
min: 0.183,
|
||||
max: 1.5,
|
||||
initial: 0.183
|
||||
}
|
||||
},
|
||||
|
||||
GO: {
|
||||
id: 'GO',
|
||||
name: 'GO',
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
getNextAddressLabelId
|
||||
} from 'selectors/addressBook';
|
||||
import { showNotification } from 'actions/notifications';
|
||||
import { getNetworkChainId } from 'selectors/config';
|
||||
|
||||
export const ERROR_DURATION: number = 4000;
|
||||
|
||||
|
@ -32,15 +33,21 @@ export function* handleChangeAddressLabelEntry(action: ChangeAddressLabelEntry):
|
|||
isEditing,
|
||||
overrideValidation
|
||||
} = action.payload;
|
||||
const addresses = yield select(getAddressLabels);
|
||||
const labels = yield select(getLabelAddresses);
|
||||
const priorEntry = yield select(getAddressLabelEntry, id);
|
||||
const addresses: ReturnType<typeof getAddressLabels> = yield select(getAddressLabels);
|
||||
const labels: ReturnType<typeof getLabelAddresses> = yield select(getLabelAddresses);
|
||||
const priorEntry: ReturnType<typeof getAddressLabelEntry> = yield select(
|
||||
getAddressLabelEntry,
|
||||
id
|
||||
);
|
||||
const chainId: ReturnType<typeof getNetworkChainId> = yield select(getNetworkChainId);
|
||||
const { addressError, labelError } = isValidAddressLabel(
|
||||
temporaryAddress,
|
||||
temporaryLabel,
|
||||
addresses,
|
||||
labels
|
||||
labels,
|
||||
chainId
|
||||
);
|
||||
|
||||
const updatedEntry = {
|
||||
id,
|
||||
address: addressError && !isEditing ? priorEntry.address || '' : temporaryAddress,
|
||||
|
@ -56,8 +63,13 @@ export function* handleChangeAddressLabelEntry(action: ChangeAddressLabelEntry):
|
|||
|
||||
export function* handleSaveAddressLabelEntry(action: SaveAddressLabelEntry): SagaIterator {
|
||||
const id = action.payload;
|
||||
const { address, addressError, label, labelError } = yield select(getAddressLabelEntry, id);
|
||||
const nextId = yield select(getNextAddressLabelId);
|
||||
const {
|
||||
address,
|
||||
addressError,
|
||||
label,
|
||||
labelError
|
||||
}: ReturnType<typeof getAddressLabelEntry> = yield select(getAddressLabelEntry, id);
|
||||
const nextId: ReturnType<typeof getNextAddressLabelId> = yield select(getNextAddressLabelId);
|
||||
const flashError = (error: string) => put(showNotification('danger', error, ERROR_DURATION));
|
||||
|
||||
if (addressError) {
|
||||
|
@ -101,7 +113,10 @@ export function* handleSaveAddressLabelEntry(action: SaveAddressLabelEntry): Sag
|
|||
})
|
||||
);
|
||||
} else if (id === ACCOUNT_ADDRESS_ID) {
|
||||
const ownEntry = yield select(getAddressLabelEntryFromAddress, address);
|
||||
const ownEntry: ReturnType<typeof getAddressLabelEntryFromAddress> = yield select(
|
||||
getAddressLabelEntryFromAddress,
|
||||
address
|
||||
);
|
||||
|
||||
yield put(
|
||||
setAddressLabelEntry({
|
||||
|
@ -143,7 +158,10 @@ export function* handleSaveAddressLabelEntry(action: SaveAddressLabelEntry): Sag
|
|||
|
||||
export function* handleRemoveAddressLabelEntry(action: RemoveAddressLabelEntry): SagaIterator {
|
||||
const id = action.payload;
|
||||
const { id: entryId, address } = yield select(getAddressLabelEntry, id);
|
||||
const { id: entryId, address }: ReturnType<typeof getAddressLabelEntry> = yield select(
|
||||
getAddressLabelEntry,
|
||||
id
|
||||
);
|
||||
|
||||
if (typeof entryId === 'undefined') {
|
||||
return;
|
||||
|
@ -153,7 +171,10 @@ export function* handleRemoveAddressLabelEntry(action: RemoveAddressLabelEntry):
|
|||
yield put(clearAddressLabelEntry(id));
|
||||
|
||||
if (id === ACCOUNT_ADDRESS_ID) {
|
||||
const ownEntry = yield select(getAddressLabelEntryFromAddress, address);
|
||||
const ownEntry: ReturnType<typeof getAddressLabelEntryFromAddress> = yield select(
|
||||
getAddressLabelEntryFromAddress,
|
||||
address
|
||||
);
|
||||
|
||||
if (ownEntry) {
|
||||
yield put(clearAddressLabelEntry(ownEntry.id));
|
||||
|
|
|
@ -5,12 +5,12 @@ import {
|
|||
updateDeterministicWallet
|
||||
} from 'actions/deterministicWallets';
|
||||
import { showNotification } from 'actions/notifications';
|
||||
import { publicToAddress, toChecksumAddress } from 'ethereumjs-util';
|
||||
import { publicToAddress } from 'ethereumjs-util';
|
||||
import HDKey from 'hdkey';
|
||||
import { INode } from 'libs/nodes/INode';
|
||||
import { SagaIterator } from 'redux-saga';
|
||||
import { all, apply, fork, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
import { getNodeLib } from 'selectors/config';
|
||||
import { getNodeLib, getChecksumAddressFn } from 'selectors/config';
|
||||
import { getDesiredToken, getWallets } from 'selectors/deterministicWallets';
|
||||
import { getTokens } from 'selectors/wallet';
|
||||
import translate from 'translations';
|
||||
|
@ -37,6 +37,9 @@ export function* getDeterministicWallets(action: GetDeterministicWalletsAction):
|
|||
return;
|
||||
}
|
||||
const wallets: DeterministicWalletData[] = [];
|
||||
const toChecksumAddress: ReturnType<typeof getChecksumAddressFn> = yield select(
|
||||
getChecksumAddressFn
|
||||
);
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const index = i + offset;
|
||||
const dkey = hdk.derive(`${pathBase}/${index}`);
|
||||
|
|
|
@ -5,14 +5,16 @@ import { setTokenTo } from 'actions/transaction/actionCreators/meta';
|
|||
import { Address } from 'libs/units';
|
||||
import { select, call, put, takeLatest, take } from 'redux-saga/effects';
|
||||
import { SagaIterator } from 'redux-saga';
|
||||
import { isValidENSAddress, isValidETHAddress } from 'libs/validators';
|
||||
import { isValidENSAddress } from 'libs/validators';
|
||||
import { TypeKeys } from 'actions/transaction/constants';
|
||||
import { getResolvedAddress } from 'selectors/ens';
|
||||
import { resolveDomainRequested, TypeKeys as ENSTypekeys } from 'actions/ens';
|
||||
import { SetToFieldAction, SetTokenToMetaAction } from 'actions/transaction';
|
||||
import { getIsValidAddressFn } from 'selectors/config';
|
||||
|
||||
export function* setCurrentTo({ payload: raw }: SetCurrentToAction): SagaIterator {
|
||||
const validAddress: boolean = yield call(isValidETHAddress, raw);
|
||||
const isValidAddress: ReturnType<typeof getIsValidAddressFn> = yield select(getIsValidAddressFn);
|
||||
const validAddress: boolean = yield call(isValidAddress, raw);
|
||||
const validEns: boolean = yield call(isValidENSAddress, raw);
|
||||
|
||||
let value: Buffer | null = null;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { SagaIterator } from 'redux-saga';
|
||||
import { put, select, apply, call, take, takeEvery } from 'redux-saga/effects';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import {
|
||||
setTransactionData,
|
||||
FetchTransactionDataAction,
|
||||
|
@ -15,7 +14,7 @@ import {
|
|||
BroadcastTransactionSucceededAction,
|
||||
BroadcastTransactionFailedAction
|
||||
} from 'actions/transaction';
|
||||
import { getNodeLib, getNetworkConfig } from 'selectors/config';
|
||||
import { getNodeLib, getNetworkConfig, getChecksumAddressFn } from 'selectors/config';
|
||||
import { getWalletInst } from 'selectors/wallet';
|
||||
import { INode } from 'libs/nodes';
|
||||
import { hexEncodeData } from 'libs/nodes/rpc/utils';
|
||||
|
@ -99,6 +98,7 @@ export function* getSaveableTransaction(tx: EthTx, hash: string): SagaIterator {
|
|||
}
|
||||
}
|
||||
|
||||
const toChecksumAddress = yield select(getChecksumAddressFn);
|
||||
const savableTx: SavedTransaction = {
|
||||
hash,
|
||||
from,
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import { AppState } from 'reducers';
|
||||
import { ADDRESS_BOOK_TABLE_ID } from 'components/AddressBookTable';
|
||||
import { ACCOUNT_ADDRESS_ID } from 'components/BalanceSidebar/AccountAddress';
|
||||
import { AddressLabelEntry } from 'actions/addressBook';
|
||||
import { getCurrentTo } from './transaction';
|
||||
|
||||
export function getAddressLabels(state: AppState) {
|
||||
|
@ -31,10 +29,7 @@ export function getAccountAddressEntry(state: AppState) {
|
|||
|
||||
export function getAddressLabelEntryFromAddress(state: AppState, address: string) {
|
||||
const rows = getAddressLabelRows(state);
|
||||
const entry = rows.find(
|
||||
(iteratedEntry: AddressLabelEntry) => iteratedEntry.address === toChecksumAddress(address)
|
||||
);
|
||||
|
||||
const entry = rows.find(e => e.address === address.toLowerCase());
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
@ -63,5 +58,5 @@ export function getCurrentToLabel(state: AppState) {
|
|||
const addresses = getAddressLabels(state);
|
||||
const currentTo = getCurrentTo(state);
|
||||
|
||||
return addresses[currentTo.raw] || null;
|
||||
return addresses[currentTo.raw.toLowerCase()] || null;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
} from 'types/network';
|
||||
import { getNodeConfig } from 'selectors/config';
|
||||
import { stripWeb3Network } from 'libs/nodes';
|
||||
import { getIsValidAddressFunction } from 'libs/validators';
|
||||
import { getChecksumAddressFunction } from 'utils/formatters';
|
||||
const getConfig = (state: AppState) => state.config;
|
||||
|
||||
export const getNetworks = (state: AppState) => getConfig(state).networks;
|
||||
|
@ -90,3 +92,17 @@ export const getAllNetworkConfigs = (state: AppState) => ({
|
|||
export const isNetworkUnit = (state: AppState, unit: string) => {
|
||||
return unit === getNetworkUnit(state);
|
||||
};
|
||||
|
||||
export const getNetworkChainId = (state: AppState) => {
|
||||
return getNetworkConfig(state).chainId;
|
||||
};
|
||||
|
||||
export const getIsValidAddressFn = (state: AppState) => {
|
||||
const chainId = getNetworkChainId(state);
|
||||
return getIsValidAddressFunction(chainId);
|
||||
};
|
||||
|
||||
export const getChecksumAddressFn = (state: AppState) => {
|
||||
const chainId = getNetworkChainId(state);
|
||||
return getChecksumAddressFunction(chainId);
|
||||
};
|
||||
|
|
|
@ -47,6 +47,7 @@ export function isNetworkUnit(state: AppState, unit: string) {
|
|||
|
||||
export function isWalletFormatSupportedOnNetwork(state: AppState, format: WalletName): boolean {
|
||||
const network = getStaticNetworkConfig(state);
|
||||
const chainId = network ? network.chainId : 0;
|
||||
|
||||
const CHECK_FORMATS: DPathFormat[] = [
|
||||
SecureWalletName.LEDGER_NANO_S,
|
||||
|
@ -65,6 +66,11 @@ export function isWalletFormatSupportedOnNetwork(state: AppState, format: Wallet
|
|||
return !!dPath;
|
||||
}
|
||||
|
||||
// Parity signer on RSK
|
||||
if (chainId === 30 || (chainId === 31 && format === SecureWalletName.PARITY_SIGNER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All other wallet formats are supported
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -6,10 +6,9 @@ import { getDecimalFromEtherUnit } from 'libs/units';
|
|||
import { getSerializedTransaction } from 'selectors/transaction';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import { getCustomTokens } from 'selectors/customTokens';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { getNetworkConfig, getChecksumAddressFn } from 'selectors/config';
|
||||
import { Token } from '../../../shared/types/network';
|
||||
import { stripHexPrefixAndLower } from 'libs/values';
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
|
||||
const getMetaState = (state: AppState) => getTransactionState(state).meta;
|
||||
const getFrom = (state: AppState) => {
|
||||
|
@ -20,6 +19,7 @@ const getFrom = (state: AppState) => {
|
|||
try {
|
||||
const from = transactionInstance.from;
|
||||
if (from) {
|
||||
const toChecksumAddress = getChecksumAddressFn(state);
|
||||
return toChecksumAddress(from.toString('hex'));
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -52,6 +52,7 @@ const getUnit = (state: AppState) => {
|
|||
networkTokens = networkConfig.tokens;
|
||||
}
|
||||
const mergedTokens = networkTokens ? [...networkTokens, ...customTokens] : customTokens;
|
||||
const toChecksumAddress = getChecksumAddressFn(state);
|
||||
const stringTo = toChecksumAddress(stripHexPrefixAndLower(to.toString('hex')));
|
||||
const result = mergedTokens.find(t => t.address === stringTo);
|
||||
if (result) {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import { State as AddressBookState, INITIAL_STATE } from 'reducers/addressBook';
|
||||
import { ADDRESS_BOOK_TABLE_ID } from 'components/AddressBookTable';
|
||||
import { ACCOUNT_ADDRESS_ID } from 'components/BalanceSidebar/AccountAddress';
|
||||
|
||||
export default function rehydrateAddressBookState(
|
||||
ab: AddressBookState | undefined
|
||||
): AddressBookState {
|
||||
const addressBook = { ...INITIAL_STATE };
|
||||
if (!ab) {
|
||||
return INITIAL_STATE;
|
||||
}
|
||||
|
||||
// Lower case addresses
|
||||
Object.keys(ab.addresses).forEach(address => {
|
||||
addressBook.addresses[address.toLowerCase()] = ab.addresses[address];
|
||||
});
|
||||
|
||||
Object.keys(ab.labels).forEach(label => {
|
||||
addressBook.labels[label] = ab.labels[label].toLowerCase();
|
||||
});
|
||||
|
||||
Object.keys(ab.entries).forEach(id => {
|
||||
// Remove the temporary entries that address book and account store info in
|
||||
if (id === ADDRESS_BOOK_TABLE_ID || id === ACCOUNT_ADDRESS_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = {
|
||||
...ab.entries[id],
|
||||
address: ab.entries[id].address.toLowerCase()
|
||||
};
|
||||
|
||||
// Convert errorous entries into temporary ones
|
||||
if (entry.addressError) {
|
||||
entry.temporaryAddress = entry.address;
|
||||
delete entry.addressError;
|
||||
}
|
||||
if (entry.labelError) {
|
||||
entry.temporaryLabel = entry.label;
|
||||
delete entry.labelError;
|
||||
}
|
||||
|
||||
addressBook.entries[id] = entry;
|
||||
});
|
||||
|
||||
return addressBook;
|
||||
}
|
|
@ -10,10 +10,7 @@ import {
|
|||
} from 'reducers/transactions';
|
||||
import { State as SwapState, INITIAL_STATE as initialSwapState } from 'reducers/swap';
|
||||
import { State as WalletState, INITIAL_STATE as initialWalletState } from 'reducers/wallet';
|
||||
import {
|
||||
State as AddressBookState,
|
||||
INITIAL_STATE as initialAddressBookState
|
||||
} from 'reducers/addressBook';
|
||||
import { State as AddressBookState } from 'reducers/addressBook';
|
||||
import { applyMiddleware, createStore, Store } from 'redux';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import { createLogger } from 'redux-logger';
|
||||
|
@ -26,7 +23,7 @@ import {
|
|||
rehydrateConfigAndCustomTokenState,
|
||||
getConfigAndCustomTokensStateToSubscribe
|
||||
} from './configAndTokens';
|
||||
import fixAddressBookErrors from 'utils/fixAddressBookErrors';
|
||||
import rehydrateAddressBookState from './addressBook';
|
||||
|
||||
const configureStore = () => {
|
||||
const logger = createLogger({
|
||||
|
@ -78,10 +75,7 @@ const configureStore = () => {
|
|||
...initialTransactionsState,
|
||||
...savedTransactionsState
|
||||
},
|
||||
addressBook: {
|
||||
...initialAddressBookState,
|
||||
...fixAddressBookErrors(savedAddressBook)
|
||||
},
|
||||
addressBook: rehydrateAddressBookState(savedAddressBook),
|
||||
wallet: {
|
||||
...initialWalletState,
|
||||
...savedWalletState
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
"X_MIST": "Mist",
|
||||
"X_WEB3": "Web3",
|
||||
"X_MNEMONIC": "Mnemonic Phrase ",
|
||||
"X_SAVE_PAPER": "Save Paper Wallet ",
|
||||
"X_HARDWARE_WALLET": "hardware wallet ",
|
||||
"X_PRINT": "Print Paper Wallet ",
|
||||
"X_PRINTDESC": "ProTip: Click print and save this as a PDF, even if you do not own a printer! ",
|
||||
|
@ -124,7 +125,6 @@
|
|||
"GEN_SUCCESSMSG": "Success! Your wallet has been generated. ",
|
||||
"GEN_LABEL_2": "Save your `Keystore` File. ",
|
||||
"GEN_LABEL_3": "Save Your Address. ",
|
||||
"GEN_LABEL_4": "Print paper wallet or a QR code. ",
|
||||
"GEN_ARIA_1": "Enter a strong password (at least 9 characters)",
|
||||
"GEN_ARIA_2": "make password visible",
|
||||
"BULK_LABEL_1": "Number of Wallets To Generate ",
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
declare module 'rskjs-util' {
|
||||
/**
|
||||
*
|
||||
* @description Returns a checksummed address
|
||||
* @export
|
||||
* @param {string} address
|
||||
* @returns {string}
|
||||
*/
|
||||
export function toChecksumAddress(address: string, chainId: number): string;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Checks if the address is a valid checksummed address
|
||||
* @export
|
||||
* @param {string} address
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isValidChecksumAddress(address: string, chainId: number): boolean;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { State as AddressBookState } from 'reducers/addressBook';
|
||||
import { ADDRESS_BOOK_TABLE_ID } from 'components/AddressBookTable';
|
||||
import { ACCOUNT_ADDRESS_ID } from 'components/BalanceSidebar/AccountAddress';
|
||||
|
||||
export default function fixAddressBookErrors(addressBook: AddressBookState | undefined) {
|
||||
if (!addressBook) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Object.keys(addressBook.entries).forEach(entryId => {
|
||||
const entry = addressBook.entries[entryId];
|
||||
|
||||
if (entry.addressError) {
|
||||
entry.temporaryAddress = entry.address;
|
||||
delete entry.addressError;
|
||||
}
|
||||
|
||||
if (entry.labelError) {
|
||||
entry.temporaryLabel = entry.label;
|
||||
delete entry.labelError;
|
||||
}
|
||||
});
|
||||
|
||||
delete addressBook.entries[ADDRESS_BOOK_TABLE_ID];
|
||||
delete addressBook.entries[ACCOUNT_ADDRESS_ID];
|
||||
|
||||
return addressBook;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import BN from 'bn.js';
|
||||
import { toChecksumAddress as toETHChecksumAddress } from 'ethereumjs-util';
|
||||
import { toChecksumAddress as toRSKChecksumAddress } from 'rskjs-util';
|
||||
import { Wei } from 'libs/units';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
|
||||
|
@ -119,3 +121,15 @@ export function ensV3Url(name: string) {
|
|||
export function hexToNumber(hex: string) {
|
||||
return new BN(stripHexPrefix(hex)).toNumber();
|
||||
}
|
||||
|
||||
// Checksumming split into two functions so it's shared by network selector
|
||||
export function getChecksumAddressFunction(chainId: number) {
|
||||
if (chainId === 30 || chainId === 31) {
|
||||
return (addr: string) => toRSKChecksumAddress(addr, chainId);
|
||||
}
|
||||
return toETHChecksumAddress;
|
||||
}
|
||||
|
||||
export function toChecksumAddressByChainId(address: string, chainId: number) {
|
||||
return getChecksumAddressFunction(chainId)(address);
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
import React from 'react';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
|
||||
interface PrintOptions {
|
||||
styles?: string;
|
||||
printTimeout?: number;
|
||||
popupFeatures?: object;
|
||||
}
|
||||
|
||||
export default function(element: React.ReactElement<any>, opts: PrintOptions = {}) {
|
||||
const options = {
|
||||
styles: '',
|
||||
printTimeout: 500,
|
||||
popupFeatures: {},
|
||||
...opts
|
||||
};
|
||||
|
||||
// Convert popupFeatures into a key=value,key=value string. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/open#Window_features
|
||||
// for more information.
|
||||
const featuresStr = Object.entries(options.popupFeatures)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(',');
|
||||
|
||||
const popup = window.open('about:blank', 'printWindow', featuresStr);
|
||||
if (popup) {
|
||||
popup.document.open();
|
||||
popup.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<style>${options.styles}</style>
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
window.print();
|
||||
}, ${options.printTimeout});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
${renderToString(element)}
|
||||
<script>
|
||||
// FIXME consider if we REALLY want it
|
||||
var width = document.body.children[0].offsetWidth;
|
||||
var height = document.body.children[0].offsetHeight;
|
||||
window.moveTo(0, 0);
|
||||
// FIXME Chrome could be larger (i guess?)
|
||||
window.resizeTo(width + 60, height + 60);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@
|
|||
"ethereumjs-wallet": "0.6.0",
|
||||
"font-awesome": "4.7.0",
|
||||
"hdkey": "0.8.0",
|
||||
"html2canvas": "1.0.0-alpha.12",
|
||||
"idna-uts46": "1.1.0",
|
||||
"jsonschema": "1.2.4",
|
||||
"lodash": "4.17.5",
|
||||
|
@ -55,6 +56,7 @@
|
|||
"redux": "3.7.2",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-saga": "0.16.0",
|
||||
"rskjs-util": "1.0.3",
|
||||
"scryptsy": "2.0.0",
|
||||
"semver": "5.5.0",
|
||||
"uuid": "3.2.1",
|
||||
|
@ -69,6 +71,7 @@
|
|||
"@types/enzyme-adapter-react-16": "1.0.1",
|
||||
"@types/events": "1.2.0",
|
||||
"@types/history": "4.6.2",
|
||||
"@types/html2canvas": "0.0.33",
|
||||
"@types/jest": "22.2.3",
|
||||
"@types/lodash": "4.14.107",
|
||||
"@types/moment-timezone": "0.5.4",
|
||||
|
|
|
@ -15,6 +15,7 @@ type StaticNetworkIds =
|
|||
| 'ETSC'
|
||||
| 'EGEM'
|
||||
| 'CLO'
|
||||
| 'RSK_TESTNET'
|
||||
| 'GO';
|
||||
|
||||
export interface BlockExplorerConfig {
|
||||
|
|
|
@ -5,20 +5,24 @@ import {
|
|||
isValidPath,
|
||||
isValidPrivKey,
|
||||
isLabelWithoutENS,
|
||||
isValidAddressLabel
|
||||
isValidAddressLabel,
|
||||
isValidAddress
|
||||
} from '../../common/libs/validators';
|
||||
import { translateRaw } from '../../common/translations';
|
||||
import { DPaths } from 'config/dpaths';
|
||||
import { valid, invalid } from '../utils/testStrings';
|
||||
|
||||
configuredStore.getState();
|
||||
|
||||
const VALID_BTC_ADDRESS = '1MEWT2SGbqtz6mPCgFcnea8XmWV5Z4Wc6';
|
||||
const VALID_ETH_ADDRESS = '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8';
|
||||
const VALID_RSK_TESTNET_ADDRESS = '0x5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd';
|
||||
const VALID_ETH_PRIVATE_KEY = '3f4fd89ea4970cc77bfd2d07a95786575ea62e183857afe6301578e1a3c5c782';
|
||||
const INVALID_ETH_PRIVATE_KEY = '3f4fd89ea4970cc77bfd2d07a95786575ea62e183857afe6301578e1a3c5ZZZZ';
|
||||
const VALID_ETH_PRIVATE_BUFFER = Buffer.from(VALID_ETH_PRIVATE_KEY, 'hex');
|
||||
const VALID_ETH_PRIVATE_0X = '0x3f4fd89ea4970cc77bfd2d07a95786575ea62e183857afe6301578e1a3c5c782';
|
||||
const RSK_TESTNET_CHAIN_ID = 31;
|
||||
const RSK_MAINNET_CHAIN_ID = 30;
|
||||
const ETH_CHAIN_ID = 1;
|
||||
|
||||
describe('Validator', () => {
|
||||
it('should validate correct BTC address as true', () => {
|
||||
|
@ -34,6 +38,18 @@ describe('Validator', () => {
|
|||
it('should validate incorrect ETH address as false', () => {
|
||||
expect(isValidETHAddress('nonsense' + VALID_ETH_ADDRESS + 'nonsense')).toBeFalsy();
|
||||
});
|
||||
it('should validate correct ETH address in RSK network as false', () => {
|
||||
expect(isValidAddress(VALID_ETH_ADDRESS, RSK_TESTNET_CHAIN_ID)).toBeFalsy();
|
||||
});
|
||||
it('should validate correct RSK address in ETH network as false', () => {
|
||||
expect(isValidAddress(VALID_RSK_TESTNET_ADDRESS, ETH_CHAIN_ID)).toBeFalsy();
|
||||
});
|
||||
it('should validate correct RSK address in RSK testnet network as true', () => {
|
||||
expect(isValidAddress(VALID_RSK_TESTNET_ADDRESS, RSK_TESTNET_CHAIN_ID)).toBeTruthy();
|
||||
});
|
||||
it('should validate correct RSK address in RSK mainnet network as false', () => {
|
||||
expect(isValidAddress(VALID_RSK_TESTNET_ADDRESS, RSK_MAINNET_CHAIN_ID)).toBeFalsy();
|
||||
});
|
||||
it('should validate an incorrect DPath as false', () => {
|
||||
expect(isValidPath('m/44/60/0/0')).toBeFalsy();
|
||||
});
|
||||
|
@ -92,33 +108,39 @@ describe('isValidAddressLabel', () => {
|
|||
|
||||
describe('Happy path', () => {
|
||||
it('should return valid', () => {
|
||||
expect(isValidAddressLabel(validAddress, 'Foo', {}, {}).isValid).toEqual(true);
|
||||
expect(isValidAddressLabel(validAddress, 'Foo', {}, {}, 1).isValid).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('Invalid cases', () => {
|
||||
it('should return invalid when the provided address is invalid', () => {
|
||||
const { isValid, addressError } = isValidAddressLabel('derp', 'Foo', {}, {});
|
||||
const { isValid, addressError } = isValidAddressLabel('derp', 'Foo', {}, {}, 1);
|
||||
|
||||
expect(isValid).toEqual(false);
|
||||
expect(addressError).toEqual(translateRaw('INVALID_ADDRESS'));
|
||||
});
|
||||
|
||||
it('should return invalid if the address already exists', () => {
|
||||
const { isValid, addressError } = isValidAddressLabel(validAddress, 'Foo', addresses, labels);
|
||||
const { isValid, addressError } = isValidAddressLabel(
|
||||
validAddress,
|
||||
'Foo',
|
||||
addresses,
|
||||
labels,
|
||||
1
|
||||
);
|
||||
|
||||
expect(isValid).toEqual(false);
|
||||
expect(addressError).toEqual(translateRaw('ADDRESS_ALREADY_EXISTS'));
|
||||
});
|
||||
|
||||
it('should return invalid if the label is not of correct length', () => {
|
||||
const { isValid, labelError } = isValidAddressLabel(validAddress, 'X', {}, {});
|
||||
const { isValid, labelError } = isValidAddressLabel(validAddress, 'X', {}, {}, 1);
|
||||
|
||||
expect(isValid).toEqual(false);
|
||||
expect(labelError).toEqual(translateRaw('INVALID_LABEL_LENGTH'));
|
||||
});
|
||||
|
||||
it('should return invalid if the label contains an ENS TLD', () => {
|
||||
const { isValid, labelError } = isValidAddressLabel(validAddress, 'Foo.eth', {}, {});
|
||||
const { isValid, labelError } = isValidAddressLabel(validAddress, 'Foo.eth', {}, {}, 1);
|
||||
|
||||
expect(isValid).toEqual(false);
|
||||
expect(labelError).toEqual(translateRaw('LABEL_CANNOT_CONTAIN_ENS_SUFFIX'));
|
||||
|
@ -129,7 +151,8 @@ describe('isValidAddressLabel', () => {
|
|||
otherValidAddress,
|
||||
'Foo',
|
||||
addresses,
|
||||
labels
|
||||
labels,
|
||||
1
|
||||
);
|
||||
|
||||
expect(isValid).toEqual(false);
|
||||
|
|
|
@ -13,7 +13,8 @@ import {
|
|||
changeAddressLabelEntry,
|
||||
saveAddressLabelEntry,
|
||||
clearAddressLabelEntry,
|
||||
removeAddressLabelEntry
|
||||
removeAddressLabelEntry,
|
||||
AddressLabel
|
||||
} from 'actions/addressBook';
|
||||
import { getInitialState } from '../selectors/helpers';
|
||||
|
||||
|
@ -295,11 +296,11 @@ describe('addressBook: Sagas', () => {
|
|||
}
|
||||
};
|
||||
const action = removeAddressLabelEntry(id);
|
||||
const dispatched: string[] = [];
|
||||
const dispatched: AddressLabel[] = [];
|
||||
|
||||
await runSaga(
|
||||
{
|
||||
dispatch: (dispatching: string) => dispatched.push(dispatching),
|
||||
dispatch: (dispatching: AddressLabel) => dispatched.push(dispatching),
|
||||
getState: () => state
|
||||
},
|
||||
handleRemoveAddressLabelEntry,
|
||||
|
|
|
@ -5,7 +5,8 @@ import { all, apply, fork, put, select } from 'redux-saga/effects';
|
|||
import RpcNode from 'libs/nodes/rpc';
|
||||
import { getDesiredToken, getWallets } from 'selectors/deterministicWallets';
|
||||
import { getTokens } from 'selectors/wallet';
|
||||
import { getNodeLib } from 'selectors/config';
|
||||
import { getNodeLib, getChecksumAddressFn } from 'selectors/config';
|
||||
import { getChecksumAddressFunction } from 'utils/formatters';
|
||||
import * as dWalletActions from 'actions/deterministicWallets';
|
||||
import {
|
||||
getDeterministicWallets,
|
||||
|
@ -45,6 +46,8 @@ const genWalletData2 = () => ({
|
|||
const genBalances = () => [Wei('100'), Wei('200')];
|
||||
|
||||
describe('getDeterministicWallets*', () => {
|
||||
const toChecksumAddress = getChecksumAddressFunction(1);
|
||||
|
||||
describe('starting from seed', () => {
|
||||
const dWallet = {
|
||||
seed:
|
||||
|
@ -54,8 +57,12 @@ describe('getDeterministicWallets*', () => {
|
|||
const action = dWalletActions.getDeterministicWallets(dWallet);
|
||||
const gen = getDeterministicWallets(action);
|
||||
|
||||
it('should select getChecksumAddressFn', () => {
|
||||
expect(gen.next().value).toEqual(select(getChecksumAddressFn));
|
||||
});
|
||||
|
||||
it('should match put snapshot', () => {
|
||||
expect(gen.next().value).toMatchSnapshot();
|
||||
expect(gen.next(toChecksumAddress).value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should fork updateWalletValues', () => {
|
||||
|
@ -79,8 +86,12 @@ describe('getDeterministicWallets*', () => {
|
|||
const action = dWalletActions.getDeterministicWallets(dWallet);
|
||||
const gen = getDeterministicWallets(action);
|
||||
|
||||
it('should select getChecksumAddressFn', () => {
|
||||
expect(gen.next().value).toEqual(select(getChecksumAddressFn));
|
||||
});
|
||||
|
||||
it('should match put snapshot', () => {
|
||||
expect(gen.next().value).toMatchSnapshot();
|
||||
expect(gen.next(toChecksumAddress).value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should fork updateWalletValues', () => {
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { getResolvedAddress } from 'selectors/ens';
|
||||
import { Address } from 'libs/units';
|
||||
import { call, select, put, take } from 'redux-saga/effects';
|
||||
import { isValidETHAddress, isValidENSAddress } from 'libs/validators';
|
||||
import { isValidENSAddress, getIsValidAddressFunction } from 'libs/validators';
|
||||
import { setCurrentTo, setField } from 'sagas/transaction/current/currentTo';
|
||||
import { isEtherTransaction } from 'selectors/transaction';
|
||||
import { cloneableGenerator } from 'redux-saga/utils';
|
||||
import { setToField, setTokenTo } from 'actions/transaction';
|
||||
import { resolveDomainRequested, TypeKeys as ENSTypekeys } from 'actions/ens';
|
||||
import { getIsValidAddressFn } from 'selectors/config';
|
||||
|
||||
const isValidAddress = getIsValidAddressFunction(1);
|
||||
|
||||
describe('setCurrentTo*', () => {
|
||||
const data = {} as any;
|
||||
|
@ -22,8 +25,13 @@ describe('setCurrentTo*', () => {
|
|||
};
|
||||
|
||||
data.validEthGen = setCurrentTo(ethAddrAction);
|
||||
it('should call isValidETHAddress', () => {
|
||||
expect(data.validEthGen.next().value).toEqual(call(isValidETHAddress, raw));
|
||||
|
||||
it('should select getIsValidAddressFn', () => {
|
||||
expect(data.validEthGen.next().value).toEqual(select(getIsValidAddressFn));
|
||||
});
|
||||
|
||||
it('should call isValidAddress', () => {
|
||||
expect(data.validEthGen.next(isValidAddress).value).toEqual(call(isValidAddress, raw));
|
||||
});
|
||||
|
||||
it('should call isValidENSAddress', () => {
|
||||
|
@ -48,8 +56,12 @@ describe('setCurrentTo*', () => {
|
|||
};
|
||||
data.validEnsGen = setCurrentTo(ensAddrAction);
|
||||
|
||||
it('should call isValidETHAddress', () => {
|
||||
expect(data.validEnsGen.next().value).toEqual(call(isValidETHAddress, raw));
|
||||
it('should select getIsValidAddressFn', () => {
|
||||
expect(data.validEnsGen.next().value).toEqual(select(getIsValidAddressFn));
|
||||
});
|
||||
|
||||
it('should call isValidAddress', () => {
|
||||
expect(data.validEnsGen.next(isValidAddress).value).toEqual(call(isValidAddress, raw));
|
||||
});
|
||||
|
||||
it('should call isValidENSAddress', () => {
|
||||
|
|
32
yarn.lock
32
yarn.lock
|
@ -126,10 +126,20 @@
|
|||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0"
|
||||
|
||||
"@types/html2canvas@0.0.33":
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/@types/html2canvas/-/html2canvas-0.0.33.tgz#e4076f0ea664d3cd6e1460c82e48b177774052fe"
|
||||
dependencies:
|
||||
"@types/jquery" "*"
|
||||
|
||||
"@types/jest@22.2.3", "@types/jest@^22.2.2":
|
||||
version "22.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d"
|
||||
|
||||
"@types/jquery@*":
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.2.tgz#8700226bdde24b6f98e3a60126dbaab3b2a3ab41"
|
||||
|
||||
"@types/lodash@4.14.107":
|
||||
version "4.14.107"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.107.tgz#b2d2ae3958bfb8ff828495cbe12214af9e4d035e"
|
||||
|
@ -1360,6 +1370,10 @@ base62@^1.1.0:
|
|||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428"
|
||||
|
||||
base64-arraybuffer@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
|
||||
|
||||
base64-js@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||
|
@ -2788,6 +2802,12 @@ css-color-names@0.0.4:
|
|||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
|
||||
|
||||
css-line-break@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.0.1.tgz#19f2063a33e95fb2831b86446c0b80c188af450a"
|
||||
dependencies:
|
||||
base64-arraybuffer "^0.1.5"
|
||||
|
||||
css-loader@0.28.11:
|
||||
version "0.28.11"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7"
|
||||
|
@ -5351,6 +5371,12 @@ html-webpack-plugin@3.0.6:
|
|||
toposort "^1.0.0"
|
||||
util.promisify "1.0.0"
|
||||
|
||||
html2canvas@1.0.0-alpha.12:
|
||||
version "1.0.0-alpha.12"
|
||||
resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.0.0-alpha.12.tgz#3b1992e3c9b3f56063c35fd620494f37eba88513"
|
||||
dependencies:
|
||||
css-line-break "1.0.1"
|
||||
|
||||
htmlparser2@^3.9.1:
|
||||
version "3.9.2"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
|
||||
|
@ -10013,6 +10039,12 @@ rn-host-detect@^1.0.1:
|
|||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.1.3.tgz#242d76e2fa485c48d751416e65b7cce596969e91"
|
||||
|
||||
rskjs-util@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/rskjs-util/-/rskjs-util-1.0.3.tgz#c0242d9308d7dc1eb653ffbf0dd1af1309bf55ca"
|
||||
dependencies:
|
||||
keccak "^1.0.2"
|
||||
|
||||
rst-selector-parser@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
|
||||
|
|
Loading…
Reference in New Issue