ENS Resolving (#942)

* Refactor BaseNode to be an interface INode

* Initial contract commit

* Remove redundant fallback ABI function

* First working iteration of Contract generator to be used in ENS branch

* Hide abi to clean up logging output

* Strip 0x prefix from output decode

* Handle unnamed output params

* Implement ability to supply output mappings to ABI functions

* Fix null case in outputMapping

* Add flow typing

* Add .call method to functions

* Partial commit for type refactor

* Temp contract type fix -- waiting for NPM modularization

* Remove empty files

* Cleanup contract

* Add call request to node interface

* Fix output mapping types

* Revert destructuring overboard

* Add sendCallRequest to rpcNode class and add typing

* Use enum for selecting ABI methods

* Add transaction capability to contracts

* Cleanup privaite/public members

* Remove broadcasting step from a contract transaction

* Cleanup uneeded types

* Refactor ens-base to typescript and add typings for ENS smart contracts

* Migrate ens-name-search to TS

* Add IResolveDomainRequest

* Fix rest of TSC errors

* Add definition file for bn.js

* Remove types-bn

* Fix some typings

* make isBN a static property

* progress commit -- swap out bignumber.js for bn.js

* Swap out bignumber for bn in vendor

* Change modn to number return

* Start to strip out units lib for a string manipulation based lib

* Convert codebase to only base units

* Get rid of useless component

* Handle only wei in values

* Use unit conversion in sidebar

* Automatically strip hex prefix, and  handle decimal edge case

* Handle base 16 wei in transactions

* Make a render callback component for dealing with unit conversion

* Switch contracts to use bn.js, and get transaction values from signedTx instead of state

* Get send transaction  working with bn.js

* Remove redundant hex stripping,  return base value of tokens

* Cleanup unit file

* Re-implement toFixed for strings

* Use formatNumber in codebase

* Cleanup code

* Undo package test changes

* Update snapshot and remove console logs

* Use TokenValue / Wei more consistently where applicable

* Add typing to deterministicWallets, fix confirmation modal, make UnitDisplay more flexible

* Split different ENS modes into their own components

* Fix Abi typedef

* Remove redundant moment type package

* Add Aux helper component

* Split out resolve components

* Make 'to' parameter optional

* Change import type

* Change typing to be base domain request

* Split handling of resolving into object handler

* Fix countdown component

* Adjust element spacing

* Implement reveal search functionality

* Add unit display for highest bidder

* Fill out forbidden/NYA modes

* ENS wallet component skeleton

* Clean up prop handling in UnitDisplay

* Change instanceof to typeof check, change boolean of displayBalance

* Add ENS wallet component

* Cleanup spacing

* Convert ConfModal for bidding in ENS

* Make ui component for placing bids

* Fix destructure in placeBid

* Pass through entire wallet

* Remove text center

* Display inline notification ENS isValid & add some ENS tests

* Add export of Aux

* Reformat with prettier

* progress...

* Add ENSUnlockLayout

* Add RevealBid component

* organize NameResolve components

* Merge ENS with transaction-refactor changes

* Fix address resolution

* Update styles

* convert ens name to lowercase before checking

* Add overflow-y:scroll to table

* update ens snapshots & tests

* cast 'undefined' state argument as any for testing

* clean up components

* Connect unitconverter to redux state

* remove unnecessary type assertion

* fix spinner size

* remove old bidmodal

* validate bidmask before opening modal

* progress...

* Update styles

* Add saga / actions for placing a bid

* Update types & clean up dead code

* Delete old test

* Dispatch PlaceBidRequested acitons

* Progress commit -- get ENS bidding ready for tx generation via sagas

* Seperate ENS action creators and types

* Add reducer & actions for ENS fields

* Add preliminary sagas for bid mask and bid value

* Fix ts errors

* Get bidding fields connected with some validation

* Clean up generate bid

* Hook up generate bid to redux state

* Get bid data generation working

* Add support for bidding on already open auctions

* Move bid generation states to redux, improve default field values

* Remove generate bid component

* Throttle bid generation

* Progress commit -- Bid Modal

* Hook bidmodal component up to bidding component

* Update template modal to handle custom confirm behavior

* Remove old redux bidding actions, add new one for downloaded bids

* Save downloaded bids to local storage

* Finish bidding modal

* Fix gas estimation bug

* Fix typing

* Remove bidding related functionality

* Get passing unit tests

* Make previous test more comprehensive

* Fix ts errors

* Remove commented code

* Fix invalid return

* Remove implementation of revealing bid

* Update snapshot

* Fix tests

* Delegate bidding to V3
This commit is contained in:
HenryNguyen5 2018-02-05 14:40:33 -05:00 committed by Daniel Ternyak
parent 8f3669cbc6
commit 2a4ad180d3
26 changed files with 501 additions and 126 deletions

View File

@ -1,5 +1,4 @@
import { ResolveDomainAction } from './resolveDomain';
export * from './resolveDomain';
export type EnsAction = ResolveDomainAction;

View File

@ -1,6 +1,6 @@
export enum TypeKeys {
ENS_RESOLVE_DOMAIN_REQUESTED = 'ENS_RESOLVE_DOMAIN_REQUESTED',
ENS_RESOLVE_DOMAIN_SUCCEEDED = 'ENS_RESOLVE_DOMAIN_SUCCEEDED',
ENS_RESOLVE_DOMAIN_CACHED = 'ENS_RESOLVE_DOMAIN_CACHED',
ENS_RESOLVE_DOMAIN_FAILED = 'ENS_RESOLVE_DOMAIN_FAILED'
ENS_RESOLVE_DOMAIN_FAILED = 'ENS_RESOLVE_DOMAIN_FAILED',
ENS_RESOLVE_DOMAIN_CACHED = 'ENS_RESOLVE_DOMAIN_CACHED'
}

View File

@ -69,12 +69,12 @@ class DeployClass extends Component<DispatchProps> {
</div>
<SigningStatus />
<SendButtonFactory
Modal={ConfirmationModal}
withProps={({ onClick }) => (
<button className="Deploy-submit btn btn-primary" onClick={onClick}>
{translate('NAV_DeployContract')}
</button>
)}
Modal={ConfirmationModal}
/>
</main>
);

View File

@ -0,0 +1,39 @@
import React from 'react';
import NameInputHoc from './NameInputHOC';
interface Props {
isValidDomain: boolean;
domainToCheck: string;
onClick(ev: React.FormEvent<HTMLButtonElement>): void;
onChange(ev: React.FormEvent<HTMLInputElement>): void;
}
class ENSNameInput extends React.Component<Props, {}> {
public render() {
const { onChange, onClick, isValidDomain, domainToCheck } = this.props;
return (
<article className="row">
<section className="col-xs-12 col-sm-6 col-sm-offset-3 text-center">
<div className="input-group">
<input
className={`form-control ${
domainToCheck === '' ? '' : isValidDomain ? 'is-valid' : 'is-invalid'
}`}
type="text"
placeholder="myetherwallet"
onChange={onChange}
/>
<div className="input-group-btn">
<a className="btn btn-default">.eth</a>
</div>
</div>
{isValidDomain ? null : <p>Use at least 7 characters</p>}
<button className="btn btn-primary " onClick={onClick}>
Check ENS Name
</button>
</section>
</article>
);
}
}
export default NameInputHoc(ENSNameInput);

View File

@ -0,0 +1,42 @@
import React, { Component } from 'react';
import { isValidENSName } from 'libs/validators';
interface State {
domainToCheck: string;
isValidDomain: boolean;
}
interface Props {
resolveDomainRequested(domain: string): void;
}
const NameInputHoc = PassedComponent =>
class HOC extends Component<Props, State> {
public state = {
isValidDomain: false,
domainToCheck: ''
};
//add delay to namehash computation / getting the availability
public onChange = (event: React.FormEvent<HTMLButtonElement>) => {
const domainToCheck: string = event.currentTarget.value.toLowerCase();
this.setState({ domainToCheck });
const isValidName: boolean = isValidENSName(domainToCheck);
this.setState({ isValidDomain: isValidName });
};
public onClick = () => {
const { isValidDomain, domainToCheck } = this.state;
const { resolveDomainRequested } = this.props;
return isValidDomain && resolveDomainRequested(domainToCheck);
};
public render() {
const { onChange, onClick } = this;
const { isValidDomain, domainToCheck } = this.state;
const props = {
onChange,
onClick,
isValidDomain,
domainToCheck
};
return <PassedComponent {...props} />;
}
};
export default NameInputHoc;

View File

@ -0,0 +1 @@
export { default as NameInput } from './components/NameInput';

View File

@ -0,0 +1,40 @@
.auction-info {
margin-bottom: 32px;
}
.ens-title {
margin: 2rem 0;
h2 {
margin: 0;
}
}
.ens-panel-wrapper {
display: flex;
flex-wrap: wrap;
.ens-panel {
flex-grow: 1;
color: white;
padding: 1rem;
background-color: #185475;
p,
h4 {
margin: 0;
}
&-light {
background-color: #1e92ba;
}
}
}
.table-wrapper {
overflow-y: scroll;
}
@media (max-width: 820px) {
.ens-panel {
width: 100%;
}
}

View File

@ -0,0 +1,43 @@
import React from 'react';
import { IBaseDomainRequest } from 'libs/ens';
import ENSTime from './components/ENSTime';
import moment from 'moment';
import { NewTabLink } from 'components/ui';
const getDeadlines = (registrationDate: string) => {
// Get the time to reveal bids, and the time when the action closes
const time = moment(+registrationDate * 1000);
const auctionCloseTime = +time;
const revealBidTime = +time.subtract(2, 'days');
return { auctionCloseTime, revealBidTime };
};
export const NameAuction: React.SFC<IBaseDomainRequest> = props => {
const { registrationDate, name } = props;
const { auctionCloseTime, revealBidTime } = getDeadlines(registrationDate);
return (
<section className="row">
<div className="auction-info text-center">
<div className="ens-title">
<h1>
An auction has started for <strong>{name}.eth</strong>
</h1>
</div>
<div className="ens-panel-wrapper">
<section className="ens-panel">
<ENSTime text="Reveal Bids On" time={revealBidTime} />
</section>
<section className="ens-panel ens-panel-light">
<ENSTime text="Auction Closes On" time={auctionCloseTime} />
</section>
</div>
<NewTabLink
content={`Do you want to place a bid on ${name}.eth? You'll need to bid on MyCrypto V3 by clicking here: `}
href="https://mycrypto.com/#ens"
/>
</div>
</section>
);
};

View File

@ -0,0 +1,6 @@
import React from 'react';
import { IBaseDomainRequest } from 'libs/ens';
export const NameForbidden: React.SFC<IBaseDomainRequest> = props => (
<h1>{props.name}.eth is forbidden</h1>
);

View File

@ -0,0 +1,6 @@
import React from 'react';
import { IBaseDomainRequest } from 'libs/ens';
export const NameNotYetAvailable: React.SFC<IBaseDomainRequest> = props => (
<h1>{props.name}.eth is not yet available</h1>
);

View File

@ -0,0 +1,23 @@
import React from 'react';
import { IBaseDomainRequest } from 'libs/ens';
import { NewTabLink } from 'components/ui';
export const NameOpen: React.SFC<IBaseDomainRequest> = props => (
<section className="row">
<section className="auction-info text-center">
<div className="ens-title">
<h1>
<strong>{props.name}.eth</strong> is available!
</h1>
</div>
<NewTabLink
className="text-center"
content={`Do you want ${
props.name
}.eth? You'll need open an auction on MyCrypto V3 by clicking here`}
href="https://mycrypto.com/#ens"
/>
</section>
</section>
);

View File

@ -0,0 +1,59 @@
import React from 'react';
import { IOwnedDomainRequest } from 'libs/ens';
import { NewTabLink } from 'components/ui';
const lookupLink = name => `https://etherscan.io/enslookup?q=${name}`;
type ChildrenProps = any;
const MonoTd = ({ children }: ChildrenProps) => <td className="mono">{children}</td>;
export const NameOwned: React.SFC<IOwnedDomainRequest> = ({
highestBid,
labelHash,
nameHash,
resolvedAddress,
ownerAddress,
name
}) => (
<section>
<div className="ens-title">
<h1 className="text-center">
<strong>{name}.eth</strong> is already owned
</h1>
</div>
<div className="table-wrapper">
<table className="table table-striped">
<tbody>
<tr>
<td>Name: </td>
<MonoTd>
<NewTabLink content={`${name}.eth`} href={lookupLink(`${name}.eth`)} />
</MonoTd>
</tr>
<tr>
<td>Labelhash ({name}): </td>
<MonoTd>{labelHash}</MonoTd>
</tr>
<tr>
<td>Namehash ({name}.eth): </td>
<MonoTd>{nameHash}</MonoTd>
</tr>
<tr>
<td>Owner:</td>
<MonoTd>{ownerAddress}</MonoTd>
</tr>
<tr>
<td>Highest Bidder (Deed Owner): </td>
<MonoTd>
<span>{highestBid}</span>
</MonoTd>
</tr>
<tr>
<td>Resolved Address: </td>
<MonoTd>{resolvedAddress}</MonoTd>
</tr>
</tbody>
</table>
</div>
</section>
);

View File

@ -0,0 +1,43 @@
import React from 'react';
import { IRevealDomainRequest } from 'libs/ens';
import ENSTime from './components/ENSTime';
import { UnitDisplay, NewTabLink } from 'components/ui';
import { Wei } from 'libs/units';
export const NameReveal: React.SFC<IRevealDomainRequest> = props => (
<section className="row text-center">
<div className="auction-info text-center">
<div className="ens-title">
<h2>
<p>
It's time to reveal the bids for <strong>{props.name}.eth.</strong>{' '}
</p>
<p>
Current Highest bid is{' '}
<strong>
<UnitDisplay
value={Wei(props.highestBid)}
unit="ether"
symbol="ETH"
displayShortBalance={false}
checkOffline={false}
/>
</strong>
</p>
</h2>
</div>
<div className="ens-panel-wrapper">
<section className="ens-panel">
<ENSTime text="Auction closes on" time={+props.registrationDate * 1000} />
</section>
</div>
</div>
<NewTabLink
content={`Did you you bid on ${
props.name
}.eth? You must reveal your bid now. You'll need reveal your bid on MyCrypto V3 by clicking here`}
href="https://mycrypto.com/#ens"
/>
</section>
);

View File

@ -0,0 +1,72 @@
import React, { Component } from 'react';
import moment from 'moment';
interface Props {
initialTime: number;
}
interface State {
currentTime: number;
}
class CountDown extends Component<Props, State> {
public state = { currentTime: 0 };
constructor(props: Props) {
super(props);
this.startCountDown();
this.state = { currentTime: 0 };
}
public render() {
const { currentTime } = this.state;
return <p>{this.humanizeTime(currentTime)}</p>;
}
private humanizeTime = (time: number) => {
let timeRemaining = time;
const floorTime = unit => Math.floor(timeRemaining / unit);
const pad = (num: number) => num.toString().padStart(2, '0');
const second = 1000;
const minute = second * 60;
const hour = minute * 60;
const day = hour * 24;
const days = floorTime(day);
timeRemaining -= days * day;
const hours = floorTime(hour);
timeRemaining -= hours * hour;
const minutes = floorTime(minute);
timeRemaining -= minutes * minute;
const seconds = floorTime(second);
return `${pad(days)} Days ${pad(hours)} Hours ${pad(minutes)} Minutes ${pad(seconds)} Seconds `;
};
private startCountDown = () => {
const intervalId = window.setInterval(() => {
const nextTime = +moment(this.props.initialTime).diff(+moment(), 'ms');
if (nextTime < 0) {
return clearInterval(intervalId);
}
this.setState({ currentTime: nextTime });
}, 1000);
};
}
interface ITime {
text: string;
time: number;
}
const ENSTime: React.SFC<ITime> = ({ text, time }) => (
<section className="sm-6 col-xs-12 order-info">
<p>{text}</p>
<h4>{moment(time).toString()}</h4>
<CountDown initialTime={time} />
</section>
);
export default ENSTime;

View File

@ -0,0 +1,6 @@
export { NameAuction } from './NameAuction';
export { NameForbidden } from './NameForbidden';
export { NameNotYetAvailable } from './NameNotYetAvailable';
export { NameOpen } from './NameOpen';
export { NameOwned } from './NameOwned';
export { NameReveal } from './NameReveal';

View File

@ -0,0 +1,38 @@
import React from 'react';
import { AppState } from 'reducers';
import { NameState } from 'libs/ens';
import {
NameOwned,
NameAuction,
NameForbidden,
NameNotYetAvailable,
NameOpen,
NameReveal
} from './components';
import './NameResolve.scss';
import { Spinner } from 'components/ui';
type Props = AppState['ens'];
const modeResult = {
[NameState.Auction]: NameAuction,
[NameState.Forbidden]: NameForbidden,
[NameState.NotYetAvailable]: NameNotYetAvailable,
[NameState.Open]: NameOpen,
[NameState.Owned]: NameOwned,
[NameState.Reveal]: NameReveal
};
export const NameResolve: React.SFC<Props> = props => {
const { domainRequests, domainSelector } = props;
const { currentDomain } = domainSelector;
if (!currentDomain || !domainRequests[currentDomain] || domainRequests[currentDomain].error) {
return null;
}
const domainData = domainRequests[currentDomain].data! || false;
const Component = domainData ? modeResult[domainData.mode] : Spinner;
return <Component {...domainData} />;
};

View File

@ -1,47 +0,0 @@
@import "common/sass/variables";
.UnfinishedBanner {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: $brand-warning;
overflow: auto;
z-index: 10000;
&-content {
text-align: center;
margin-top: 20%;
@media screen and (min-width: 2150px) {
margin-top: 15%;
}
color: #fff;
text-shadow: 1px 1px 1px rgba(#000, 0.12);
overflow: auto;
h2 {
font-size: 52px;
margin-bottom: 20px;
}
p {
font-size: 30px;
margin-bottom: 15px;
}
}
// Fade out
&.is-fading {
pointer-events: none;
opacity: 0;
background: #fff;
transition: all 500ms ease 400ms;
.UnfinishedBanner-content {
opacity: 0;
transform: translateY(15px);
transition: all 500ms ease;
}
}
}

View File

@ -1,40 +0,0 @@
import React from 'react';
import './index.scss';
interface State {
isFading: boolean;
hasAcknowledged: boolean;
}
export default class UnfinishedBanner extends React.Component<{}, State> {
public state = {
isFading: false,
hasAcknowledged: false
};
public render() {
if (this.state.hasAcknowledged) {
return null;
}
const isFading = this.state.isFading ? 'is-fading' : '';
return (
<div className={`UnfinishedBanner ${isFading}`} onClick={this.continue}>
<div className="UnfinishedBanner-content">
<h2>Under Contruction</h2>
<p>The ENS section is still under contruction</p>
<p>Expect unfinished components</p>
<h3>Click to continue</h3>
</div>
</div>
);
}
private continue = () => {
this.setState({ isFading: true });
setTimeout(() => {
this.setState({ hasAcknowledged: true });
}, 1000);
};
}

View File

@ -0,0 +1,3 @@
export { GeneralInfoPanel } from './GeneralInfoPanel';
export { NameInput } from './NameInput';
export { NameResolve } from './NameResolve';

View File

@ -1,11 +1,13 @@
import React from 'react';
import { GeneralInfoPanel } from './components/GeneralInfoPanel';
import UnfinishedBanner from './components/UnfinishedBanner';
import { GeneralInfoPanel, NameInput, NameResolve } from './components';
import TabSection from 'containers/TabSection';
import { Route, Switch, RouteComponentProps } from 'react-router';
import { RouteNotFound } from 'components/RouteNotFound';
import { NewTabLink } from 'components/ui';
import translate from 'translations';
import { connect } from 'react-redux';
import { resolveDomainRequested, TResolveDomainRequested } from 'actions/ens';
import { AppState } from 'reducers';
const ENSDocsLink = () => (
<NewTabLink
@ -26,7 +28,17 @@ const ENSTitle = () => (
</article>
);
class ENSClass extends React.Component<RouteComponentProps<{}>> {
interface StateProps {
ens: AppState['ens'];
}
interface DispatchProps {
resolveDomainRequested: TResolveDomainRequested;
}
type Props = StateProps & DispatchProps;
class ENSClass extends React.Component<RouteComponentProps<any> & Props> {
public render() {
const { match } = this.props;
const currentPath = match.url;
@ -39,8 +51,9 @@ class ENSClass extends React.Component<RouteComponentProps<{}>> {
path={currentPath}
render={() => (
<section role="main" className="row">
<UnfinishedBanner />
<ENSTitle />
<NameInput resolveDomainRequested={this.props.resolveDomainRequested} />
<NameResolve {...this.props.ens} />
<GeneralInfoPanel />
</section>
)}
@ -53,4 +66,7 @@ class ENSClass extends React.Component<RouteComponentProps<{}>> {
}
}
export default ENSClass;
const mapStateToProps = (state: AppState): StateProps => ({ ens: state.ens });
const mapDispatchToProps: DispatchProps = { resolveDomainRequested };
export default connect(mapStateToProps, mapDispatchToProps)(ENSClass);

View File

@ -1,9 +1,9 @@
import {
EnsAction,
ResolveDomainRequested,
ResolveDomainFailed,
ResolveDomainSucceeded,
ResolveDomainCached
ResolveDomainCached,
ResolveDomainAction
} from 'actions/ens';
import { DomainRequest } from 'libs/ens';
import { TypeKeys } from 'actions/ens/constants';
@ -66,7 +66,7 @@ const resolveDomainFailed = (state: State, action: ResolveDomainFailed): State =
return { ...state, [domain]: nextDomain };
};
export default (state: State = INITIAL_STATE, action: EnsAction): State => {
export default (state: State = INITIAL_STATE, action: ResolveDomainAction): State => {
switch (action.type) {
case TypeKeys.ENS_RESOLVE_DOMAIN_REQUESTED:
return resolveDomainRequested(state, action);

View File

@ -8,4 +8,4 @@ export interface State {
domainRequests: DRState;
}
export const ens = combineReducers({ domainSelector, domainRequests });
export const ens = combineReducers<State>({ domainSelector, domainRequests });

View File

@ -12,6 +12,7 @@ import { call, put, select, all, actionChannel, take, fork, race } from 'redux-s
import { showNotification } from 'actions/notifications';
import { resolveDomainRequest } from './modeMap';
import { getCurrentDomainName, getCurrentDomainData } from 'selectors/ens';
import { IBaseDomainRequest } from 'libs/ens';
function* shouldResolveDomain(domain: string) {
const currentDomainName = yield select(getCurrentDomainName);
@ -43,18 +44,19 @@ function* resolveDomain(): SagaIterator {
}
const node: INode = yield select(getNodeLib);
const result = yield race({
const result: { domainData: IBaseDomainRequest; error } = yield race({
domainData: call(resolveDomainRequest, domain, node),
err: call(delay, 4000)
});
const { domainData } = result;
if (!domainData) {
throw Error();
}
const domainSuccessAction = resolveDomainSucceeded(domain, domainData);
yield put(domainSuccessAction);
yield;
} catch (e) {
const domainFailAction = resolveDomainFailed(domain, e);
yield put(domainFailAction);

View File

@ -1,4 +1,4 @@
import { IDomainData, NameState, getNameHash } from 'libs/ens';
import { IDomainData, NameState, getNameHash, IBaseDomainRequest } from 'libs/ens';
import ENS from 'libs/ens/contracts';
import { SagaIterator } from 'redux-saga';
import { call } from 'redux-saga/effects';
@ -48,7 +48,7 @@ function* nameStateReveal({ deedAddress }: IDomainData<NameState.Reveal>): SagaI
data: ENS.deed.owner.encodeInput(),
decoder: ENS.deed.owner.decodeOutput
});
return ownerAddress;
return { ownerAddress };
}
interface IModeMap {
@ -83,11 +83,12 @@ export function* resolveDomainRequest(name: string): SagaIterator {
const nameStateHandler = modeMap[domainData.mode];
const result = yield call(nameStateHandler, domainData, nameHash);
return {
const returnValue: IBaseDomainRequest = {
name,
...domainData,
...result,
labelHash: hash.toString('hex'),
nameHash
};
return returnValue;
}

View File

@ -15,7 +15,7 @@ import { composeWithDevTools } from 'redux-devtools-extension';
import { createLogger } from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import { loadStatePropertyOrEmptyObject, saveState } from 'utils/localStorage';
import RootReducer from './reducers';
import RootReducer, { AppState } from './reducers';
import promiseMiddleware from 'redux-promise-middleware';
import { getNodeConfigFromId } from 'utils/node';
import { getNetworkConfigFromId } from 'utils/network';
@ -111,7 +111,6 @@ const configureStore = () => {
// ONLY LOAD SWAP STATE FROM LOCAL STORAGE IF STEP WAS 3
swap: swapState
};
// if 'web3' has persisted as node selection, reset to app default
// necessary because web3 is only initialized as a node upon MetaMask / Mist unlock
if (persistedInitialState.config.nodeSelection === 'web3') {
@ -127,14 +126,13 @@ const configureStore = () => {
store.subscribe(
throttle(() => {
const state = store.getState();
const state: AppState = store.getState();
saveState({
config: {
nodeSelection: state.config.nodeSelection,
languageSelection: state.config.languageSelection,
customNodes: state.config.customNodes,
customNetworks: state.config.customNetworks,
setGasLimit: state.config.setGasLimit
customNetworks: state.config.customNetworks
},
transaction: {
fields: {

View File

@ -1,20 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`snapshot test ENS component 1`] = `
<Connect(TabSection)
isUnavailableOffline={true}
>
<section
className="container"
>
<Switch>
<Route
exact={true}
path="/ens"
render={[Function]}
/>
<Component />
</Switch>
</section>
</Connect(TabSection)>
<ENSClass
history={
Object {
"action": "PUSH",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 2,
"listen": [Function],
"location": Object {
"hash": "",
"key": "e08jz7",
"pathname": "/ens",
"search": "",
"state": Object {},
},
"push": [Function],
"replace": [Function],
}
}
location={
Object {
"hash": "",
"key": "e08jz7",
"pathname": "/ens",
"search": "",
"state": Object {},
}
}
match={
Object {
"isExact": false,
"params": Object {},
"path": "/ens",
"url": "/ens",
}
}
resolveDomainRequested={[Function]}
/>
`;