diff --git a/package-lock.json b/package-lock.json
index 81cc5c6..0cc3e74 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5640,8 +5640,8 @@
             }
         },
         "lbry-redux": {
-            "version": "github:lbryio/lbry-redux#123efacf4d45289ebda9dc291976d475de227a55",
-            "from": "github:lbryio/lbry-redux#123efacf4d45289ebda9dc291976d475de227a55",
+            "version": "github:lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c",
+            "from": "github:lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c",
             "requires": {
                 "proxy-polyfill": "0.1.6",
                 "reselect": "^3.0.0",
@@ -5649,8 +5649,8 @@
             }
         },
         "lbryinc": {
-            "version": "github:lbryio/lbryinc#b9f354ae50bd57691765a7d042c5054167878bf4",
-            "from": "github:lbryio/lbryinc#b9f354ae50bd57691765a7d042c5054167878bf4",
+            "version": "github:lbryio/lbryinc#d250096a6fc5df16be4f82812ecce28d6e558b6e",
+            "from": "github:lbryio/lbryinc#d250096a6fc5df16be4f82812ecce28d6e558b6e",
             "requires": {
                 "reselect": "^3.0.0"
             }
diff --git a/package.json b/package.json
index 57d482b..9a434c5 100644
--- a/package.json
+++ b/package.json
@@ -12,8 +12,8 @@
         "base-64": "^0.1.0",
         "@expo/vector-icons": "^8.1.0",
         "gfycat-style-urls": "^1.0.3",
-        "lbry-redux": "lbryio/lbry-redux#123efacf4d45289ebda9dc291976d475de227a55",
-        "lbryinc": "lbryio/lbryinc#b9f354ae50bd57691765a7d042c5054167878bf4",
+        "lbry-redux": "lbryio/lbry-redux#d44cd9ca56dee784dba42c0cc13061ae75cbd46c",
+        "lbryinc": "lbryio/lbryinc#d250096a6fc5df16be4f82812ecce28d6e558b6e",
         "lodash": ">=4.17.11",
         "merge": ">=1.2.1",
         "moment": "^2.22.1",
diff --git a/src/assets/gerbil-happy.png b/src/assets/gerbil-happy.png
new file mode 100644
index 0000000..4247f83
Binary files /dev/null and b/src/assets/gerbil-happy.png differ
diff --git a/src/assets/gerbil-sad.png b/src/assets/gerbil-sad.png
new file mode 100644
index 0000000..153d4ad
Binary files /dev/null and b/src/assets/gerbil-sad.png differ
diff --git a/src/component/AppNavigator.js b/src/component/AppNavigator.js
index db24edb..a5fb642 100644
--- a/src/component/AppNavigator.js
+++ b/src/component/AppNavigator.js
@@ -29,8 +29,9 @@ import {
 import { connect } from 'react-redux';
 import { AppState, BackHandler, Linking, NativeModules, TextInput, ToastAndroid } from 'react-native';
 import { selectDrawerStack } from 'redux/selectors/drawer';
-import { SETTINGS, doDismissToast, doToast, selectToast } from 'lbry-redux';
+import { SETTINGS, doDismissToast, doPopulateSharedUserState, doToast, selectToast } from 'lbry-redux';
 import {
+  Lbryio,
   doGetSync,
   doUserCheckEmailVerified,
   doUserEmailVerify,
@@ -305,6 +306,13 @@ class AppWithNavigationState extends React.Component {
     });
   };
 
+  getUserSettings = () => {
+    const { dispatch } = this.props;
+    Lbryio.call('user_settings', 'get').then(settings => {
+      dispatch(doPopulateSharedUserState(settings));
+    });
+  };
+
   componentWillUnmount() {
     AppState.removeEventListener('change', this._handleAppStateChange);
     BackHandler.removeEventListener('hardwareBackPress');
@@ -321,12 +329,8 @@ class AppWithNavigationState extends React.Component {
       NativeModules.Firebase.track('email_verified', { email: user.primary_email });
       ToastAndroid.show('Your email address was successfully verified.', ToastAndroid.LONG);
 
-      // upon successful email verification, do wallet sync (if password has been set)
-      NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(walletPassword => {
-        if (walletPassword && walletPassword.trim().length > 0) {
-          dispatch(doGetSync(walletPassword));
-        }
-      });
+      // get user settings after email verification
+      this.getUserSettings();
     }
   }
 
diff --git a/src/component/channelSelector/view.js b/src/component/channelSelector/view.js
index e5e8c29..b8d81a8 100644
--- a/src/component/channelSelector/view.js
+++ b/src/component/channelSelector/view.js
@@ -26,8 +26,8 @@ export default class ChannelSelector extends React.PureComponent {
   }
 
   componentDidMount() {
-    const { channels, channelName, fetchChannelListMine, fetchingChannels } = this.props;
-    if (!channels.length && !fetchingChannels) {
+    const { channels = [], channelName, fetchChannelListMine, fetchingChannels } = this.props;
+    if ((!channels || channels.length === 0) && !fetchingChannels) {
       fetchChannelListMine();
     }
     this.setState({ currentSelectedValue: channelName });
@@ -37,7 +37,7 @@ export default class ChannelSelector extends React.PureComponent {
     const { channels: prevChannels = [], channelName } = this.props;
     const { channels = [] } = nextProps;
 
-    if (channels.length !== prevChannels.length && channelName !== this.state.currentSelectedValue) {
+    if (channels && channels.length !== prevChannels.length && channelName !== this.state.currentSelectedValue) {
       this.setState({ currentSelectedValue: channelName });
     }
   }
@@ -182,7 +182,9 @@ export default class ChannelSelector extends React.PureComponent {
   render() {
     const channel = this.state.addingChannel ? 'new' : this.props.channel;
     const { enabled, fetchingChannels, channels = [] } = this.props;
-    const pickerItems = [Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL].concat(channels.map(ch => ch.name));
+    const pickerItems = [Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL].concat(
+      channels ? channels.map(ch => ch.name) : []
+    );
 
     const {
       newChannelName,
diff --git a/src/component/emptyStateView/index.js b/src/component/emptyStateView/index.js
new file mode 100644
index 0000000..53e1c84
--- /dev/null
+++ b/src/component/emptyStateView/index.js
@@ -0,0 +1,4 @@
+import { connect } from 'react-redux';
+import EmptyStateView from './view';
+
+export default connect()(EmptyStateView);
diff --git a/src/component/emptyStateView/view.js b/src/component/emptyStateView/view.js
new file mode 100644
index 0000000..e69ceda
--- /dev/null
+++ b/src/component/emptyStateView/view.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { NativeModules, Text, View, Image, TouchableOpacity } from 'react-native';
+import Button from '../button';
+import emptyStateStyle from 'styles/emptyState';
+
+class EmptyStateView extends React.PureComponent {
+  render() {
+    const { message, buttonText, inner, onButtonPress } = this.props;
+
+    return (
+      <View
+        style={[emptyStateStyle.container, inner ? emptyStateStyle.innerContainer : emptyStateStyle.outerContainer]}
+      >
+        <Image style={emptyStateStyle.image} resizeMode={'stretch'} source={require('../../assets/gerbil-happy.png')} />
+        <Text style={emptyStateStyle.message}>{message}</Text>
+        {buttonText && (
+          <View style={emptyStateStyle.buttonContainer}>
+            <Button style={emptyStateStyle.button} text={buttonText} onPress={onButtonPress} />
+          </View>
+        )}
+      </View>
+    );
+  }
+}
+
+export default EmptyStateView;
diff --git a/src/component/rewardCard/view.js b/src/component/rewardCard/view.js
index e5faaca..8db4147 100644
--- a/src/component/rewardCard/view.js
+++ b/src/component/rewardCard/view.js
@@ -59,7 +59,8 @@ class RewardCard extends React.PureComponent<Props> {
   getDisplayAmount = () => {
     const { reward } = this.props;
     if (reward) {
-      if (reward.reward_range && reward.reward_range.includes('-')) {
+      const claimed = !!reward.transaction_id;
+      if (!claimed && reward.reward_range && reward.reward_range.includes('-')) {
         return reward.reward_range.split('-')[0] + '+'; // ex: 5+
       } else if (reward.reward_amount > 0) {
         return reward.reward_amount;
diff --git a/src/index.js b/src/index.js
index 9b92508..4013fe2 100644
--- a/src/index.js
+++ b/src/index.js
@@ -15,6 +15,7 @@ import {
   walletReducer,
 } from 'lbry-redux';
 import {
+  Lbryio,
   authReducer,
   blacklistReducer,
   costInfoReducer,
@@ -41,6 +42,7 @@ import formReducer from 'redux/reducers/form';
 import drawerReducer from 'redux/reducers/drawer';
 import settingsReducer from 'redux/reducers/settings';
 import thunk from 'redux-thunk';
+import isEqual from 'utils/deep-equal';
 
 const globalExceptionHandler = (error, isFatal) => {
   if (error && NativeModules.Firebase) {
@@ -145,22 +147,27 @@ const persistor = persistStore(store, persistOptions, err => {
 });
 window.persistor = persistor;
 
-/*
-const persistFilter = {
-  'auth': ['authToken'],
-  'claims': ['byId', 'claimsByUri'],
-  'content': ['positions'],
-  'subscriptions': ['enabledChannelNotifications', 'subscriptions'],
-  'settings': ['clientSettings'],
-  'tags': ['followedTags'],
-  'wallet': ['receiveAddress']
-};
-
+let currentPayload;
 store.subscribe(() => {
-  const state = (({ auth, claims, content, subscriptions, settings, tags, wallet }) =>
-    ({ auth, claims, content, subscriptions, settings, tags, wallet }))(store.getState());
-  NativeModules.StatePersistor.update(state, persistFilter);
-}); */
+  const state = store.getState();
+  const subscriptions = state.subscriptions.subscriptions.map(({ uri }) => uri);
+  const tags = state.tags.followedTags;
+
+  const newPayload = {
+    version: '0.1',
+    shared: {
+      subscriptions,
+      tags,
+    },
+  };
+
+  if (!isEqual(newPayload, currentPayload)) {
+    currentPayload = newPayload;
+    if (Lbryio.authToken) {
+      Lbryio.call('user_settings', 'set', { settings: JSON.stringify(newPayload) });
+    }
+  }
+});
 
 // TODO: Find i18n module that is compatible with react-native
 global.__ = str => str;
diff --git a/src/page/channel/index.js b/src/page/channel/index.js
index fae3125..42c42cf 100644
--- a/src/page/channel/index.js
+++ b/src/page/channel/index.js
@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import { makeSelectClaimForUri, selectMyChannelClaims } from 'lbry-redux';
+import { doAbandonClaim, doFetchChannelListMine, makeSelectClaimForUri, selectMyChannelClaims } from 'lbry-redux';
 import { doPopDrawerStack } from 'redux/actions/drawer';
 import { doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
 import { selectDrawerStack } from 'redux/selectors/drawer';
@@ -15,6 +15,8 @@ const select = (state, props) => ({
 });
 
 const perform = dispatch => ({
+  abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
+  fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
   popDrawerStack: () => dispatch(doPopDrawerStack()),
   setSortByItem: item => dispatch(doSetSortByItem(item)),
   setTimeItem: item => dispatch(doSetTimeItem(item)),
diff --git a/src/page/channel/view.js b/src/page/channel/view.js
index d20ffd5..2a787be 100644
--- a/src/page/channel/view.js
+++ b/src/page/channel/view.js
@@ -19,6 +19,7 @@ import ClaimList from 'component/claimList';
 import Colors from 'styles/colors';
 import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
 import Button from 'component/button';
+import EmptyStateView from 'component/emptyStateView';
 import Icon from 'react-native-vector-icons/FontAwesome5';
 import Link from 'component/link';
 import ModalPicker from 'component/modalPicker';
@@ -47,7 +48,9 @@ class ChannelPage extends React.PureComponent {
   }
 
   componentDidMount() {
+    const { fetchChannelListMine } = this.props;
     NativeModules.Firebase.setCurrentScreen('Channel');
+    fetchChannelListMine();
   }
 
   handleSortByItemSelected = item => {
@@ -128,9 +131,7 @@ class ChannelPage extends React.PureComponent {
     return (
       <View style={channelPageStyle.aboutTab}>
         {!websiteUrl && !email && !description && (
-          <View style={channelPageStyle.busyContainer}>
-            <Text style={channelPageStyle.infoText}>Nothing here yet. Please check back later.</Text>
-          </View>
+          <EmptyStateView message={"There's nothing here yet.\nPlease check back later."} />
         )}
 
         {(websiteUrl || email || description) && (
@@ -266,7 +267,7 @@ class ChannelPage extends React.PureComponent {
               )}
               {ownedChannel && (
                 <Button
-                  style={channelPageStyle.deleteButton}
+                  style={[channelPageStyle.actionButton, channelPageStyle.deleteButton]}
                   theme={'light'}
                   icon={'trash-alt'}
                   text={'Delete'}
diff --git a/src/page/channelCreator/view.js b/src/page/channelCreator/view.js
index e41e0dc..76cc322 100644
--- a/src/page/channelCreator/view.js
+++ b/src/page/channelCreator/view.js
@@ -19,6 +19,7 @@ import Button from 'component/button';
 import ChannelIconItem from 'component/channelIconItem';
 import Colors from 'styles/colors';
 import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
+import EmptyStateView from 'component/emptyStateView';
 import FloatingWalletBalance from 'component/floatingWalletBalance';
 import Icon from 'react-native-vector-icons/FontAwesome5';
 import Link from 'component/link';
@@ -175,10 +176,8 @@ export default class ChannelCreator extends React.PureComponent {
 
       if (!isEditMode && hasFormState) {
         this.loadPendingFormState();
-        this.setState({ currentPhase: Constants.PHASE_CREATE });
-      } else {
-        this.setState({ currentPhase: Constants.PHASE_LIST });
       }
+      this.setState({ currentPhase: isEditMode || hasFormState ? Constants.PHASE_CREATE : Constants.PHASE_LIST });
     });
   };
 
@@ -240,7 +239,7 @@ export default class ChannelCreator extends React.PureComponent {
   componentDidUpdate() {
     const { channels = [] } = this.props;
     const { editChannelUrl } = this.state;
-    if (channels.length > 0) {
+    if (channels && channels.length > 0) {
       if (this.state.autoStyles.length !== channels.length) {
         this.setState({
           autoStyles: this.generateAutoStyles(channels.length),
@@ -534,10 +533,12 @@ export default class ChannelCreator extends React.PureComponent {
   };
 
   showChannelList = () => {
-    const { popDrawerStack } = this.props;
-    popDrawerStack();
-    this.resetChannelCreator();
+    const { drawerStack, popDrawerStack } = this.props;
+    if (drawerStack[drawerStack.length - 1].route === Constants.DRAWER_ROUTE_CHANNEL_CREATOR_FORM) {
+      popDrawerStack();
+    }
     this.setState({ currentPhase: Constants.PHASE_LIST });
+    this.resetChannelCreator();
   };
 
   resetChannelCreator = () => {
@@ -606,6 +607,7 @@ export default class ChannelCreator extends React.PureComponent {
     this.setState({
       claimId: channel.claim_id,
       currentPhase: Constants.PHASE_CREATE,
+      displayName: value && value.title ? value.title : channel.name.substring(1),
       editMode: true,
       coverImageUrl: value && value.cover ? value.cover.url : null,
       currentChannelName: channel.name.substring(1),
@@ -744,6 +746,8 @@ export default class ChannelCreator extends React.PureComponent {
       uploadingImage,
     } = this.state;
 
+    const hasChannels = channels && channels.length > 0;
+
     return (
       <View style={channelCreatorStyle.container}>
         <UriBar
@@ -756,34 +760,34 @@ export default class ChannelCreator extends React.PureComponent {
           onExitSelectionMode={this.onExitSelectionMode}
         />
 
+        {fetchingChannels && (
+          <View style={channelCreatorStyle.loading}>
+            <ActivityIndicator size={'large'} color={Colors.NextLbryGreen} />
+          </View>
+        )}
+
+        {currentPhase === Constants.PHASE_LIST && !fetchingChannels && !hasChannels && (
+          <EmptyStateView
+            message={'You have not created a channel.\nStart now by creating a new channel!'}
+            buttonText={'Create a channel'}
+            onButtonPress={this.handleNewChannelPress}
+          />
+        )}
+
         {currentPhase === Constants.PHASE_LIST && (
           <FlatList
             extraData={this.state}
-            ListHeaderComponent={
-              fetchingChannels ? (
-                <View style={channelCreatorStyle.listHeader}>
-                  <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
-                </View>
-              ) : null
-            }
-            ListEmptyComponent={
-              fetchingChannels ? null : (
-                <View style={channelCreatorStyle.listEmpty}>
-                  <Text style={channelCreatorStyle.listEmptyText}>
-                    You have not created a channel. Start now by creating a new channel!
-                  </Text>
+            ListFooterComponent={
+              !channels || channels.length === 0 ? null : (
+                <View style={channelCreatorStyle.listFooter}>
+                  <Button
+                    style={channelCreatorStyle.createChannelButton}
+                    text={'Create a channel'}
+                    onPress={this.handleNewChannelPress}
+                  />
                 </View>
               )
             }
-            ListFooterComponent={
-              <View style={channelCreatorStyle.listFooter}>
-                <Button
-                  style={channelCreatorStyle.createChannelButton}
-                  text={'Create a channel'}
-                  onPress={this.handleNewChannelPress}
-                />
-              </View>
-            }
             style={channelCreatorStyle.scrollContainer}
             contentContainerStyle={channelCreatorStyle.scrollPadding}
             initialNumToRender={10}
@@ -825,7 +829,7 @@ export default class ChannelCreator extends React.PureComponent {
                 </TouchableOpacity>
               );
             }}
-            data={channels.filter(channel => !abandoningClaimIds.includes(channel.claim_id))}
+            data={channels ? channels.filter(channel => !abandoningClaimIds.includes(channel.claim_id)) : []}
             keyExtractor={(item, index) => item.claim_id}
           />
         )}
@@ -864,7 +868,7 @@ export default class ChannelCreator extends React.PureComponent {
                       source={{ uri: thumbnailUrl }}
                     />
                   )}
-                  {thumbnailUrl !== null && thumbnailUrl.trim().length === 0 && newChannelName.length > 1 && (
+                  {(thumbnailUrl === null || thumbnailUrl.trim().length === 0) && newChannelName.length > 1 && (
                     <Text style={channelIconStyle.autothumbCharacter}>
                       {newChannelName.substring(0, 1).toUpperCase()}
                     </Text>
diff --git a/src/page/downloads/view.js b/src/page/downloads/view.js
index ddc5206..aa349d6 100644
--- a/src/page/downloads/view.js
+++ b/src/page/downloads/view.js
@@ -15,6 +15,7 @@ import { __, navigateToUri, uriFromFileInfo } from 'utils/helper';
 import Colors from 'styles/colors';
 import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
 import PageHeader from 'component/pageHeader';
+import EmptyStateView from 'component/emptyStateView';
 import FileListItem from 'component/fileListItem';
 import FloatingWalletBalance from 'component/floatingWalletBalance';
 import StorageStatsCard from 'component/storageStatsCard';
@@ -154,6 +155,10 @@ class DownloadsPage extends React.PureComponent {
           onDeleteActionPressed={this.onDeleteActionPressed}
         />
 
+        {!fetching && !hasDownloads && (
+          <EmptyStateView message={'You do not have any\ndownloaded content on this device.'} />
+        )}
+
         <View style={downloadsStyle.subContainer}>
           {hasDownloads && <StorageStatsCard fileInfos={this.getFilteredFileInfos()} />}
           {fetching && (
@@ -161,11 +166,7 @@ class DownloadsPage extends React.PureComponent {
               <ActivityIndicator size="large" color={Colors.NextLbryGreen} style={downloadsStyle.loading} />
             </View>
           )}
-          {!fetching && !hasDownloads && (
-            <View style={downloadsStyle.busyContainer}>
-              <Text style={downloadsStyle.noDownloadsText}>You do not have any downloaded content on this device.</Text>
-            </View>
-          )}
+
           {!fetching && hasDownloads && (
             <FlatList
               extraData={this.state}
diff --git a/src/page/file/view.js b/src/page/file/view.js
index 950511c..36a0d63 100644
--- a/src/page/file/view.js
+++ b/src/page/file/view.js
@@ -22,6 +22,7 @@ import { navigateBack, navigateToUri } from 'utils/helper';
 import Icon from 'react-native-vector-icons/FontAwesome5';
 import ImageViewer from 'react-native-image-zoom-viewer';
 import Button from 'component/button';
+import EmptyStateView from 'component/emptyStateView';
 import Tag from 'component/tag';
 import ChannelPage from 'page/channel';
 import Colors from 'styles/colors';
@@ -100,7 +101,7 @@ class FilePage extends React.PureComponent {
     DeviceEventEmitter.addListener('onDownloadUpdated', this.handleDownloadUpdated);
     DeviceEventEmitter.addListener('onDownloadCompleted', this.handleDownloadCompleted);
 
-    const { fetchChannelListMine, fileInfo, isResolvingUri, resolveUri, navigation } = this.props;
+    const { fetchMyClaims, fileInfo, isResolvingUri, resolveUri, navigation } = this.props;
     const { uri, uriVars } = navigation.state.params;
     this.setState({ uri, uriVars });
 
@@ -108,7 +109,7 @@ class FilePage extends React.PureComponent {
 
     this.fetchFileInfo(this.props);
     this.fetchCostInfo(this.props);
-    fetchChannelListMine();
+    fetchMyClaims();
 
     if (NativeModules.Firebase) {
       NativeModules.Firebase.track('open_file_page', { uri: uri });
@@ -590,14 +591,15 @@ class FilePage extends React.PureComponent {
     } = this.props;
     const { uri, autoplay } = navigation.state.params;
 
+    const { isChannel } = parseURI(uri);
     const myChannelUris = channels ? channels.map(channel => channel.permanent_url) : [];
     const ownedClaim = myClaimUris.includes(uri) || myChannelUris.includes(uri);
-    const { isChannel } = parseURI(uri);
 
     let innerContent = null;
     if ((isResolvingUri && !claim) || !claim) {
       return (
         <View style={filePageStyle.container}>
+          <UriBar value={uri} navigation={navigation} />
           {isResolvingUri && (
             <View style={filePageStyle.busyContainer}>
               <ActivityIndicator size="large" color={Colors.NextLbryGreen} />
@@ -607,16 +609,29 @@ class FilePage extends React.PureComponent {
           {claim === null && !isResolvingUri && (
             <View style={filePageStyle.container}>
               {ownedClaim && (
-                <Text style={filePageStyle.emptyClaimText}>
-                  {isChannel
-                    ? 'It looks like you just created this channel. It will appear in a few minutes.'
-                    : 'It looks you just published this content. It will appear in a few minutes.'}
-                </Text>
+                <EmptyStateView
+                  message={
+                    isChannel
+                      ? 'It looks like you just created this channel. It will appear in a few minutes.'
+                      : 'It looks you just published this content. It will appear in a few minutes.'
+                  }
+                />
+              )}
+              {!ownedClaim && (
+                <EmptyStateView
+                  message={"There's nothing at this location."}
+                  buttonText={'Publish something here'}
+                  onButtonPress={() =>
+                    navigation.navigate({
+                      routeName: Constants.DRAWER_ROUTE_PUBLISH,
+                      params: { vanityUrl: uri.trim() },
+                    })
+                  }
+                />
               )}
-              {!ownedClaim && <Text style={filePageStyle.emptyClaimText}>There's nothing at this location.</Text>}
             </View>
           )}
-          <UriBar value={uri} navigation={navigation} />
+          <FloatingWalletBalance navigation={navigation} />
         </View>
       );
     }
diff --git a/src/page/publish/index.js b/src/page/publish/index.js
index ffbadf8..009d602 100644
--- a/src/page/publish/index.js
+++ b/src/page/publish/index.js
@@ -4,7 +4,6 @@ import {
   doResolveUri,
   doToast,
   doUpdatePublishForm,
-  doUploadThumbnail,
   selectBalance,
   selectPublishFormValues,
 } from 'lbry-redux';
@@ -28,7 +27,6 @@ const perform = dispatch => ({
   clearPublishFormState: () => dispatch(doClearPublishFormState()),
   updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
   updatePublishFormState: data => dispatch(doUpdatePublishFormState(data)),
-  uploadThumbnail: (filePath, fsAdapter) => dispatch(doUploadThumbnail(filePath, null, fsAdapter)),
   publish: (success, fail) => dispatch(doPublish(success, fail)),
   resolveUri: uri => dispatch(doResolveUri(uri)),
   pushDrawerStack: (routeName, params) => dispatch(doPushDrawerStack(routeName, params)),
diff --git a/src/page/publish/view.js b/src/page/publish/view.js
index 5d5d2f2..0d6a4c7 100644
--- a/src/page/publish/view.js
+++ b/src/page/publish/view.js
@@ -43,7 +43,7 @@ import Tag from 'component/tag';
 import TagSearch from 'component/tagSearch';
 import UriBar from 'component/uriBar';
 import publishStyle from 'styles/publish';
-import { __, navigateToUri } from 'utils/helper';
+import { __, navigateToUri, uploadImageAsset } from 'utils/helper';
 
 const languages = {
   en: 'English',
@@ -129,6 +129,8 @@ class PublishPage extends React.PureComponent {
     uploadedThumbnailUri: null,
     vanityUrlSet: false,
 
+    thumbnailImagePickerOpen: false,
+
     // other
     publishStarted: false,
   };
@@ -218,10 +220,8 @@ class PublishPage extends React.PureComponent {
           // replace name with the specified vanity URL if there was one in the pending state
           this.setState({ name: this.state.vanityUrl });
         }
-        this.setState({ currentPhase: Constants.PHASE_DETAILS });
-      } else {
-        this.setState({ currentPhase: Constants.PHASE_SELECTOR });
       }
+      this.setState({ currentPhase: isEditMode || hasFormState ? Constants.PHASE_DETAILS : Constants.PHASE_SELECTOR });
     });
   };
 
@@ -256,6 +256,7 @@ class PublishPage extends React.PureComponent {
     this.setState(
       {
         editMode: true,
+        publishStarted: false,
         currentPhase: Constants.PHASE_DETAILS,
 
         hasEditedContentAddress: true,
@@ -382,10 +383,12 @@ class PublishPage extends React.PureComponent {
   };
 
   handlePublishSuccess = data => {
-    const { navigation, notify } = this.props;
+    const { clearPublishFormState, navigation, notify } = this.props;
     notify({
       message: `Your content was successfully published to ${this.state.uri}. It will be available in a few mintues.`,
     });
+    clearPublishFormState();
+    this.setState({ publishStarted: false });
     navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISHES, params: { publishSuccess: true } });
   };
 
@@ -407,14 +410,6 @@ class PublishPage extends React.PureComponent {
       this.onComponentFocused();
     }
 
-    if (publishFormValues) {
-      if (publishFormValues.thumbnail && !this.state.uploadedThumbnailUri) {
-        const { thumbnail } = publishFormValues;
-        updatePublishFormState({ currentThumbnailUri: thumbnail, uploadedThumbnailUri: thumbnail });
-        this.setState({ currentThumbnailUri: thumbnail, uploadedThumbnailUri: thumbnail });
-      }
-    }
-
     if (
       this.state.currentPhase === Constants.PHASE_DETAILS &&
       prevDrawerStack[prevDrawerStack.length - 1].route === Constants.DRAWER_ROUTE_PUBLISH_FORM &&
@@ -432,6 +427,7 @@ class PublishPage extends React.PureComponent {
     updatePublishFormState({ currentMedia: media, name: newName });
     this.setState(
       {
+        publishStarted: false,
         currentMedia: media,
         title: null, // no title autogeneration (user will fill this in)
         name: newName,
@@ -449,7 +445,7 @@ class PublishPage extends React.PureComponent {
   };
 
   showSelector() {
-    const { updatePublishForm } = this.props;
+    const { clearPublishFormState, updatePublishForm } = this.props;
 
     this.setState(
       {
@@ -486,9 +482,12 @@ class PublishPage extends React.PureComponent {
         selectedChannel: null,
         uploadedThumbnailUri: null,
 
+        thumbnailImagePickerOpen: false,
+
         vanityUrlSet: false,
       },
       () => {
+        clearPublishFormState();
         // reset thumbnail
         updatePublishForm({ thumbnail: null });
       }
@@ -522,19 +521,59 @@ class PublishPage extends React.PureComponent {
     );
   };
 
-  onFilePicked = evt => {
-    this.setState({ documentPickerOpen: false }, () => {
-      const currentMedia = {
-        id: -1,
-        filePath: `file://${evt.path}`,
-        duration: 0,
-      };
-      this.setCurrentMedia(currentMedia);
+  handleThumbnailUploadSuccess = ({ url }) => {
+    const { updatePublishFormState } = this.props;
+
+    this.setState({
+      uploadThumbnailStarted: false,
+      currentThumbnailUri: url,
+      uploadedThumbnailUri: url,
     });
+    updatePublishFormState({ currentThumbnailUri: url, uploadedThumbnailUri: url });
+  };
+
+  handleThumbnailUploadFailure = err => {
+    const { notify } = this.props;
+    this.setState({ uploadThumbnailStarted: false });
+    notify({ message: 'The thumbnail could not be uploaded. Please try again.' });
+  };
+
+  onFilePicked = evt => {
+    const { notify } = this.props;
+    if (evt.path && evt.path.length > 0) {
+      const fileUrl = `file://${evt.path}`;
+
+      if (this.state.documentPickerOpen) {
+        this.setState({ documentPickerOpen: false, thumbnailImagePickerOpen: false }, () => {
+          const currentMedia = {
+            id: -1,
+            filePath: fileUrl,
+            duration: 0,
+          };
+          this.setCurrentMedia(currentMedia);
+        });
+      } else if (this.state.thumbnailImagePickerOpen) {
+        this.setState(
+          {
+            documentPickerOpen: false,
+            thumbnailImagePickerOpen: false,
+            uploadThumbnailStarted: true,
+            currentThumbnailUri: fileUrl,
+          },
+          () => {
+            // upload a new thumbnail
+            uploadImageAsset(fileUrl, this.handleThumbnailUploadSuccess, this.handleThumbnailUploadFailure);
+          }
+        );
+      }
+    } else {
+      // could not determine the file path
+      notify({ message: 'The path could not be determined. Please try a different file.' });
+    }
   };
 
   onPickerCanceled = () => {
-    this.setState({ documentPickerOpen: false });
+    this.setState({ documentPickerOpen: false, thumbnailImagePickerOpen: false });
   };
 
   handleCloseCameraPressed = () => {
@@ -569,6 +608,7 @@ class PublishPage extends React.PureComponent {
             {
               currentThumbnailUri: null,
               updatingThumbnailUri: false,
+              publishStarted: false,
               currentPhase: Constants.PHASE_DETAILS,
               showCameraOverlay: false,
               videoRecordingMode: false,
@@ -593,6 +633,7 @@ class PublishPage extends React.PureComponent {
           {
             currentPhase: Constants.PHASE_DETAILS,
             currentThumbnailUri: null,
+            publishStarted: false,
             updatingThumbnailUri: false,
             showCameraOverlay: false,
             videoRecordingMode: false,
@@ -611,8 +652,6 @@ class PublishPage extends React.PureComponent {
     });
   };
 
-  handleUploadPressed = () => {};
-
   getRandomFileId = () => {
     // generate a random id for a photo or recorded video between 1 and 20 (for creating thumbnails)
     const id = Math.floor(Math.random() * (20 - 2)) + 1;
@@ -699,7 +738,7 @@ class PublishPage extends React.PureComponent {
       return;
     }
 
-    const { notify, uploadThumbnail } = this.props;
+    const { notify } = this.props;
     const { thumbnailPath } = this.state;
 
     this.setState({ updatingThumbnailUri: true });
@@ -714,7 +753,13 @@ class PublishPage extends React.PureComponent {
 
         // upload the thumbnail
         if (!this.state.uploadedThumbnailUri) {
-          this.setState({ uploadThumbnailStarted: true }, () => uploadThumbnail(this.getFilePathFromUri(uri), RNFS));
+          this.setState({ uploadThumbnailStarted: true }, () =>
+            uploadImageAsset(
+              this.getFilePathFromUri(uri),
+              this.handleThumbnailUploadSuccess,
+              this.handleThumbnailUploadFailure
+            )
+          );
         }
       } else if (mediaType === 'image' || mediaType === 'video') {
         const create =
@@ -725,7 +770,9 @@ class PublishPage extends React.PureComponent {
           .then(path => {
             this.setState({ currentThumbnailUri: `file://${path}`, updatingThumbnailUri: false });
             if (!this.state.uploadedThumbnailUri) {
-              this.setState({ uploadThumbnailStarted: true }, () => uploadThumbnail(path, RNFS));
+              this.setState({ uploadThumbnailStarted: true }, () =>
+                uploadImageAsset(path, this.handleThumbnailUploadSuccess, this.handleThumbnailUploadFailure)
+              );
             }
           })
           .catch(err => {
@@ -781,7 +828,7 @@ class PublishPage extends React.PureComponent {
       : '';
     const licenseUrl = LICENSES.CC_LICENSES.reduce((value, item) => {
       if (typeof value === 'object') {
-        value = '';
+        value = license === value.value ? item.url : '';
       }
       if (license === item.value) {
         value = item.url;
@@ -799,6 +846,25 @@ class PublishPage extends React.PureComponent {
     this.setState({ otherLicenseDescription });
   };
 
+  handleThumbnailPressed = () => {
+    const { notify } = this.props;
+    if (this.state.thumbnailImagePickerOpen || this.state.uploadThumbnailStarted) {
+      if (this.state.uploadThumbnailStarted) {
+        notify({ message: 'A thumbnail is already being uploaded. Please wait for the upload to finish.' });
+      }
+      return;
+    }
+
+    this.setState(
+      {
+        thumbnailImagePickerOpen: true,
+      },
+      () => {
+        NativeModules.UtilityModule.openDocumentPicker('image/*');
+      }
+    );
+  };
+
   render() {
     const { balance, navigation, notify, publishFormValues } = this.props;
     const {
@@ -893,22 +959,24 @@ class PublishPage extends React.PureComponent {
       }
       content = (
         <ScrollView style={publishStyle.publishDetails}>
-          {currentThumbnailUri && currentThumbnailUri.trim().length > 0 && (
-            <View style={publishStyle.mainThumbnailContainer}>
-              <FastImage
-                style={publishStyle.mainThumbnail}
-                resizeMode={FastImage.resizeMode.contain}
-                source={{ uri: currentThumbnailUri }}
-              />
+          <TouchableOpacity style={publishStyle.mainThumbnailContainer} onPress={this.handleThumbnailPressed}>
+            <FastImage
+              style={publishStyle.mainThumbnail}
+              resizeMode={FastImage.resizeMode.contain}
+              source={{ uri: currentThumbnailUri }}
+            />
 
-              {this.state.uploadThumbnailStarted && !this.state.uploadedThumbnailUri && (
-                <View style={publishStyle.thumbnailUploadContainer}>
-                  <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
-                  <Text style={publishStyle.thumbnailUploadText}>Uploading thumbnail...</Text>
-                </View>
-              )}
+            <View style={publishStyle.thumbnailEditOverlay}>
+              <Icon name={'edit'} style={publishStyle.editIcon} />
             </View>
-          )}
+
+            {this.state.uploadThumbnailStarted && (
+              <View style={publishStyle.thumbnailUploadContainer}>
+                <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
+                <Text style={publishStyle.thumbnailUploadText}>Uploading thumbnail...</Text>
+              </View>
+            )}
+          </TouchableOpacity>
           {!this.state.canPublish && <PublishRewardsDriver navigation={navigation} />}
 
           <View style={publishStyle.card}>
@@ -1105,21 +1173,21 @@ class PublishPage extends React.PureComponent {
           </View>
 
           <View style={publishStyle.actionButtons}>
-            {(this.state.publishStarted || publishFormValues.publishing) && (
+            {this.state.publishStarted && (
               <View style={publishStyle.progress}>
                 <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
               </View>
             )}
 
-            {!publishFormValues.publishing && !this.state.publishStarted && (
+            {!this.state.publishStarted && (
               <Link style={publishStyle.cancelLink} text="Cancel" onPress={() => this.showSelector()} />
             )}
 
-            {!publishFormValues.publishing && !this.state.publishStarted && (
+            {!this.state.publishStarted && (
               <View style={publishStyle.rightActionButtons}>
                 <Button
                   style={publishStyle.publishButton}
-                  disabled={!this.state.canPublish || !this.state.uploadedThumbnailUri}
+                  disabled={!this.state.canPublish || this.state.uploadThumbnailStarted}
                   text={this.state.editMode ? 'Save changes' : 'Publish'}
                   onPress={this.handlePublishPressed}
                 />
diff --git a/src/page/publishes/view.js b/src/page/publishes/view.js
index 68a3a85..61a3bb5 100644
--- a/src/page/publishes/view.js
+++ b/src/page/publishes/view.js
@@ -3,6 +3,7 @@ import { ActivityIndicator, Alert, FlatList, NativeModules, Text, TouchableOpaci
 import Button from 'component/button';
 import Colors from 'styles/colors';
 import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
+import EmptyStateView from 'component/emptyStateView';
 import FileListItem from 'component/fileListItem';
 import FloatingWalletBalance from 'component/floatingWalletBalance';
 import UriBar from 'component/uriBar';
@@ -137,16 +138,11 @@ class PublishesPage extends React.PureComponent {
         )}
 
         {!fetching && (!uris || uris.length === 0) && (
-          <View style={publishStyle.noPublishes}>
-            <Text style={publishStyle.noPublishText}>
-              {__('It looks like you have not published anything to LBRY yet.')}
-            </Text>
-            <Button
-              style={publishStyle.publishNowButton}
-              text={__('Publish something new')}
-              onPress={() => navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH })}
-            />
-          </View>
+          <EmptyStateView
+            message={__('It looks like you have not\npublished any content to LBRY yet.')}
+            buttonText={__('Publish something new')}
+            onButtonPress={() => navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH })}
+          />
         )}
 
         {uris && uris.length > 0 && (
diff --git a/src/page/splash/index.js b/src/page/splash/index.js
index d3b644e..031aaf8 100644
--- a/src/page/splash/index.js
+++ b/src/page/splash/index.js
@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import { doBalanceSubscribe, doUpdateBlockHeight, doToast } from 'lbry-redux';
+import { doBalanceSubscribe, doUpdateBlockHeight, doPopulateSharedUserState, doToast } from 'lbry-redux';
 import {
   doAuthenticate,
   doBlackListedOutpointsSubscribe,
@@ -34,6 +34,7 @@ const perform = dispatch => ({
   notify: data => dispatch(doToast(data)),
   setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
   setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
+  populateSharedUserState: settings => dispatch(doPopulateSharedUserState(settings)),
   updateBlockHeight: () => dispatch(doUpdateBlockHeight()),
   verifyUserEmail: (token, recaptcha) => dispatch(doUserEmailVerify(token, recaptcha)),
   verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)),
diff --git a/src/page/splash/view.js b/src/page/splash/view.js
index 567cfec..0355a83 100644
--- a/src/page/splash/view.js
+++ b/src/page/splash/view.js
@@ -1,5 +1,6 @@
 import React from 'react';
 import { Lbry } from 'lbry-redux';
+import { Lbryio } from 'lbryinc';
 import { ActivityIndicator, Linking, NativeModules, Platform, Text, View } from 'react-native';
 import { NavigationActions, StackActions } from 'react-navigation';
 import { decode as atob } from 'base-64';
@@ -15,6 +16,8 @@ import splashStyle from 'styles/splash';
 
 const BLOCK_HEIGHT_INTERVAL = 1000 * 60 * 2.5; // every 2.5 minutes
 
+const SETTINGS_GET_INTERVAL = 1000 * 60 * 5; // every 5 minutes
+
 const testingNetwork = 'Testing network';
 const waitingForResolution = 'Waiting for name resolution';
 
@@ -108,6 +111,13 @@ class SplashScreen extends React.PureComponent {
     }
   }
 
+  getUserSettings = () => {
+    const { populateSharedUserState } = this.props;
+    Lbryio.call('user_settings', 'get').then(settings => {
+      populateSharedUserState(settings);
+    });
+  };
+
   finishSplashScreen = () => {
     const {
       authenticate,
@@ -127,6 +137,10 @@ class SplashScreen extends React.PureComponent {
       filteredOutpointsSubscribe();
       checkSubscriptionsInit();
 
+      // get user settings interval
+      this.getUserSettings();
+      setInterval(() => this.getUserSettings(), SETTINGS_GET_INTERVAL);
+
       if (user && user.id && user.has_verified_email) {
         // user already authenticated
         NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(walletPassword => {
diff --git a/src/page/transactionHistory/view.js b/src/page/transactionHistory/view.js
index 0fe6839..47d5a93 100644
--- a/src/page/transactionHistory/view.js
+++ b/src/page/transactionHistory/view.js
@@ -2,6 +2,7 @@ import React from 'react';
 import { ActivityIndicator, NativeModules, View, ScrollView, Text } from 'react-native';
 import Colors from 'styles/colors';
 import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
+import EmptyStateView from 'component/emptyStateView';
 import TransactionList from 'component/transactionList';
 import UriBar from 'component/uriBar';
 import walletStyle from 'styles/wallet';
@@ -53,11 +54,9 @@ class TransactionHistoryPage extends React.PureComponent {
             <Text style={walletStyle.loadingText}>Loading transactions...</Text>
           </View>
         )}
+        {!fetchingTransactions && transactions.length === 0 && <EmptyStateView message={'No transactions to list.'} />}
         <ScrollView style={walletStyle.transactionHistoryScroll}>
           <View style={walletStyle.historyList}>
-            {!fetchingTransactions && transactions.length === 0 && (
-              <Text style={walletStyle.infoText}>No transactions to list.</Text>
-            )}
             {!fetchingTransactions && transactions && transactions.length > 0 && (
               <TransactionList navigation={navigation} transactions={transactions} />
             )}
diff --git a/src/page/verification/internal/sync-verify-page.js b/src/page/verification/internal/sync-verify-page.js
index 6862733..8bccb37 100644
--- a/src/page/verification/internal/sync-verify-page.js
+++ b/src/page/verification/internal/sync-verify-page.js
@@ -43,7 +43,7 @@ class SyncVerifyPage extends React.PureComponent {
           navigation.goBack();
         });
       } else {
-        syncApply(syncHash, syncData, this.state.password);
+        syncApply(syncHash, syncData, this.state.password ? this.state.password : '');
       }
     });
   };
diff --git a/src/styles/channelCreator.js b/src/styles/channelCreator.js
index 39eda77..8f0de60 100644
--- a/src/styles/channelCreator.js
+++ b/src/styles/channelCreator.js
@@ -121,7 +121,7 @@ const channelCreatorStyle = StyleSheet.create({
     height: '100%',
   },
   listFooter: {
-    marginTop: 24,
+    marginTop: 8,
   },
   createChannelButton: {
     backgroundColor: Colors.LbryGreen,
@@ -254,6 +254,15 @@ const channelCreatorStyle = StyleSheet.create({
     fontSize: 12,
     marginLeft: 4,
   },
+  loading: {
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    top: 0,
+    bottom: 0,
+    alignItems: 'center',
+    justifyContent: 'center',
+  },
 });
 
 export default channelCreatorStyle;
diff --git a/src/styles/channelPage.js b/src/styles/channelPage.js
index e76f8a2..d9963da 100644
--- a/src/styles/channelPage.js
+++ b/src/styles/channelPage.js
@@ -169,6 +169,9 @@ const channelPageStyle = StyleSheet.create({
   actionButton: {
     backgroundColor: Colors.White,
   },
+  deleteButton: {
+    marginLeft: 8,
+  },
 });
 
 export default channelPageStyle;
diff --git a/src/styles/emptyState.js b/src/styles/emptyState.js
new file mode 100644
index 0000000..4d30c8d
--- /dev/null
+++ b/src/styles/emptyState.js
@@ -0,0 +1,40 @@
+import { StyleSheet } from 'react-native';
+import Colors from './colors';
+
+const emptyStateStyle = StyleSheet.create({
+  container: {
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    bottom: 0,
+    paddingLeft: 24,
+    paddingRight: 24,
+    alignItems: 'center',
+    justifyContent: 'center',
+    zIndex: 99,
+  },
+  outerContainer: {
+    top: 60,
+  },
+  innerContainer: {
+    top: 0,
+  },
+  button: {
+    backgroundColor: Colors.LbryGreen,
+    fontSize: 18,
+  },
+  image: {
+    width: 128,
+    height: 170,
+  },
+  message: {
+    marginTop: 24,
+    textAlign: 'center',
+    fontFamily: 'Inter-UI-Regular',
+    fontSize: 18,
+    lineHeight: 28,
+    marginBottom: 24,
+  },
+});
+
+export default emptyStateStyle;
diff --git a/src/styles/publish.js b/src/styles/publish.js
index cfaa6f5..0967c55 100644
--- a/src/styles/publish.js
+++ b/src/styles/publish.js
@@ -1,6 +1,9 @@
-import { StyleSheet } from 'react-native';
+import { Dimensions, StyleSheet } from 'react-native';
 import Colors from './colors';
 
+const screenDimension = Dimensions.get('window');
+const screenWidth = screenDimension.width;
+
 const publishStyle = StyleSheet.create({
   container: {
     flex: 1,
@@ -434,7 +437,7 @@ const publishStyle = StyleSheet.create({
     color: Colors.DescriptionGrey,
   },
   publishesFooter: {
-    marginTop: 16,
+    marginTop: 2,
     marginLeft: 16,
     marginRight: 16,
   },
@@ -443,6 +446,21 @@ const publishStyle = StyleSheet.create({
     backgroundColor: Colors.LbryGreen,
     marginTop: 16,
   },
+  thumbnailEditOverlay: {
+    alignItems: 'center',
+    justifyContent: 'center',
+    borderRadius: 24,
+    position: 'absolute',
+    padding: 8,
+    left: screenWidth / 2 - 32 / 2,
+    bottom: 8,
+    backgroundColor: '#00000077',
+  },
+  editIcon: {
+    color: Colors.White,
+    fontFamily: 'Inter-UI-SemiBold',
+    fontSize: 12,
+  },
 });
 
 export default publishStyle;
diff --git a/src/utils/deep-equal.js b/src/utils/deep-equal.js
new file mode 100644
index 0000000..1925763
--- /dev/null
+++ b/src/utils/deep-equal.js
@@ -0,0 +1,117 @@
+/* eslint-disable */
+// underscore's deep equal function
+// https://github.com/jashkenas/underscore/blob/master/underscore.js#L1189
+
+export default function isEqual(a, b, aStack, bStack) {
+  // Identical objects are equal. `0 === -0`, but they aren't identical.
+  // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+  if (a === b) return a !== 0 || 1 / a === 1 / b;
+  // `null` or `undefined` only equal to itself (strict comparison).
+  if (a == null || b == null) return false;
+  // `NaN`s are equivalent, but non-reflexive.
+  if (a !== a) return b !== b;
+  // Exhaust primitive checks
+  var type = typeof a;
+  if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
+  return deepEq(a, b, aStack, bStack);
+}
+
+function deepEq(a, b, aStack, bStack) {
+  // Compare `[[Class]]` names.
+  var className = toString.call(a);
+  if (className !== toString.call(b)) return false;
+  switch (className) {
+    // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+    case '[object RegExp]':
+    // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+    case '[object String]':
+      // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+      // equivalent to `new String("5")`.
+      return '' + a === '' + b;
+    case '[object Number]':
+      // `NaN`s are equivalent, but non-reflexive.
+      // Object(NaN) is equivalent to NaN.
+      if (+a !== +a) return +b !== +b;
+      // An `egal` comparison is performed for other numeric values.
+      return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+    case '[object Date]':
+    case '[object Boolean]':
+      // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+      // millisecond representations. Note that invalid dates with millisecond representations
+      // of `NaN` are not equivalent.
+      return +a === +b;
+    case '[object Symbol]':
+      return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
+  }
+
+  var areArrays = className === '[object Array]';
+  if (!areArrays) {
+    if (typeof a != 'object' || typeof b != 'object') return false;
+
+    // Objects with different constructors are not equivalent, but `Object`s or `Array`s
+    // from different frames are.
+    var aCtor = a.constructor,
+      bCtor = b.constructor;
+    if (
+      aCtor !== bCtor &&
+      !(
+        typeof aCtor === 'function' &&
+        aCtor instanceof aCtor &&
+        typeof bCtor === 'function' &&
+        bCtor instanceof bCtor
+      ) &&
+      ('constructor' in a && 'constructor' in b)
+    ) {
+      return false;
+    }
+  }
+  // Assume equality for cyclic structures. The algorithm for detecting cyclic
+  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+
+  // Initializing stack of traversed objects.
+  // It's done here since we only need them for objects and arrays comparison.
+  aStack = aStack || [];
+  bStack = bStack || [];
+  var length = aStack.length;
+  while (length--) {
+    // Linear search. Performance is inversely proportional to the number of
+    // unique nested structures.
+    if (aStack[length] === a) return bStack[length] === b;
+  }
+
+  // Add the first object to the stack of traversed objects.
+  aStack.push(a);
+  bStack.push(b);
+
+  // Recursively compare objects and arrays.
+  if (areArrays) {
+    // Compare array lengths to determine if a deep comparison is necessary.
+    length = a.length;
+    if (length !== b.length) return false;
+    // Deep compare the contents, ignoring non-numeric properties.
+    while (length--) {
+      if (!isEqual(a[length], b[length], aStack, bStack)) return false;
+    }
+  } else {
+    // Deep compare objects.
+    var keys = Object.keys(a),
+      key;
+    length = keys.length;
+    // Ensure that both objects contain the same number of properties before comparing deep equality.
+    if (Object.keys(b).length !== length) return false;
+    while (length--) {
+      // Deep compare each member
+      key = keys[length];
+      if (!(has(b, key) && isEqual(a[key], b[key], aStack, bStack))) return false;
+    }
+  }
+  // Remove the first object from the stack of traversed objects.
+  aStack.pop();
+  bStack.pop();
+  return true;
+}
+
+function has(obj, path) {
+  return obj != null && hasOwnProperty.call(obj, path);
+}
+/* eslint-enable */