diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 7109922c6..52dbee6e0 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_drafts.h"
 #include "data/data_photo.h"
 #include "data/data_web_page.h"
+#include "data/data_feed.h"
 #include "core/tl_help.h"
 #include "base/overload.h"
 #include "observer_peer.h"
@@ -147,27 +148,22 @@ void ApiWrap::applyUpdates(
 	App::main()->feedUpdates(updates, sentMessageRandomId);
 }
 
-void ApiWrap::applyDialogsPinned(const QVector<MTPDialog> &list) {
-	for (auto i = list.size(); i != 0;) {
-		const auto &dialog = list[--i];
-		switch (dialog.type()) {
-		case mtpc_dialog: {
-			const auto &dialogData = dialog.c_dialog();
-			if (const auto peer = peerFromMTP(dialogData.vpeer)) {
-				const auto history = App::history(peer);
-				history->setPinnedDialog(dialogData.is_pinned());
-			}
-		} break;
-
-		case mtpc_dialogFeed: {
-			const auto &feedData = dialog.c_dialogFeed();
-			const auto feedId = feedData.vfeed_id.v;
-			// #TODO feeds
-		} break;
-
-		default: Unexpected("Type in ApiWrap::applyDialogsPinned.");
+void ApiWrap::savePinnedOrder() {
+	const auto &order = _session->data().pinnedDialogsOrder();
+	auto peers = QVector<MTPInputDialogPeer>();
+	peers.reserve(order.size());
+	for (const auto pinned : base::reversed(order)) {
+		if (const auto history = pinned.history()) {
+			peers.push_back(MTP_inputDialogPeer(history->peer->input));
+		} else if (const auto feed = pinned.feed()) {
+			peers.push_back(MTP_inputDialogPeerFeed(MTP_int(feed->id())));
 		}
 	}
+	auto flags = MTPmessages_ReorderPinnedDialogs::Flag::f_force;
+	request(MTPmessages_ReorderPinnedDialogs(
+		MTP_flags(flags),
+		MTP_vector(peers)
+	)).send();
 }
 
 void ApiWrap::sendMessageFail(const RPCError &error) {
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 9c708f984..035cce204 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -45,7 +45,7 @@ public:
 
 	void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0);
 
-	void applyDialogsPinned(const QVector<MTPDialog> &list);
+	void savePinnedOrder();
 
 	using RequestMessageDataCallback = base::lambda<void(ChannelData*, MsgId)>;
 	void requestMessageData(
diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
index 2e04f81dc..dd8ec4932 100644
--- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
@@ -208,9 +208,10 @@ void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) {
 
 	auto it = _inlineCache.find(_inlineQuery);
 	auto adding = (it != _inlineCache.cend());
-	// #TODO layer 72 feed users
 	if (result.type() == mtpc_messages_botResults) {
 		auto &d = result.c_messages_botResults();
+		App::feedUsers(d.vusers);
+
 		auto &v = d.vresults.v;
 		auto queryId = d.vquery_id.v;
 
diff --git a/Telegram/SourceFiles/data/data_feed.cpp b/Telegram/SourceFiles/data/data_feed.cpp
index 6ac8b3e01..e623bb33e 100644
--- a/Telegram/SourceFiles/data/data_feed.cpp
+++ b/Telegram/SourceFiles/data/data_feed.cpp
@@ -25,4 +25,14 @@ void Feed::setUnreadCounts(int unreadCount, int unreadMutedCount) {
 	_unreadMutedCount = unreadMutedCount;
 }
 
+void Feed::cachePinnedIndex(int index) {
+	_pinnedIndex = index;
+}
+
+uint64 Feed::sortKeyInChatList() const {
+	return 0ULL;/* isPinnedDialog()
+		? pinnedDialogPos(_pinnedIndex)
+		: dialogPosFromDate(chatListDate());*/
+}
+
 } // namespace Data
diff --git a/Telegram/SourceFiles/data/data_feed.h b/Telegram/SourceFiles/data/data_feed.h
index 78722c88d..f01a2e6f0 100644
--- a/Telegram/SourceFiles/data/data_feed.h
+++ b/Telegram/SourceFiles/data/data_feed.h
@@ -15,16 +15,26 @@ class Feed {
 public:
 	Feed(FeedId id);
 
+	FeedId id() const {
+		return _id;
+	}
 	void registerOne(not_null<ChannelData*> channel);
 	void unregisterOne(not_null<ChannelData*> channel);
 
 	void setUnreadCounts(int unreadCount, int unreadMutedCount);
 
+	bool isPinnedDialog() const {
+		return _pinnedIndex > 0;
+	}
+	void cachePinnedIndex(int index);
+	uint64 sortKeyInChatList() const;
+
 private:
 	FeedId _id = 0;
 	base::flat_set<not_null<ChannelData*>> _channels;
 	int _unreadCount = 0;
 	int _unreadMutedCount = 0;
+	int _pinnedIndex = 0;
 	bool _complete = false;
 
 };
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index cedeb541f..533280bbc 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -164,6 +164,114 @@ MessageIdsList Session::groupToIds(
 	return result;
 }
 
+void Session::setPinnedDialog(const Dialogs::Key &key, bool pinned) {
+	setIsPinned(key, pinned);
+}
+
+void Session::applyPinnedDialogs(const QVector<MTPDialog> &list) {
+	clearPinnedDialogs();
+	for (auto i = list.size(); i != 0;) {
+		const auto &dialog = list[--i];
+		switch (dialog.type()) {
+		case mtpc_dialog: {
+			const auto &dialogData = dialog.c_dialog();
+			if (const auto peer = peerFromMTP(dialogData.vpeer)) {
+				setPinnedDialog(App::history(peer), true);
+			}
+		} break;
+
+		case mtpc_dialogFeed: {
+			const auto &feedData = dialog.c_dialogFeed();
+			const auto feedId = feedData.vfeed_id.v;
+			setPinnedDialog(feed(feedId), true);
+		} break;
+
+		default: Unexpected("Type in ApiWrap::applyDialogsPinned.");
+		}
+	}
+}
+
+void Session::applyPinnedDialogs(const QVector<MTPDialogPeer> &list) {
+	clearPinnedDialogs();
+	for (auto i = list.size(); i != 0;) {
+		const auto &dialogPeer = list[--i];
+		switch (dialogPeer.type()) {
+		case mtpc_dialogPeer: {
+			const auto &peerData = dialogPeer.c_dialogPeer();
+			if (const auto peerId = peerFromMTP(peerData.vpeer)) {
+				setPinnedDialog(App::history(peerId), true);
+			}
+		} break;
+		case mtpc_dialogPeerFeed: {
+			const auto &feedData = dialogPeer.c_dialogPeerFeed();
+			const auto feedId = feedData.vfeed_id.v;
+			setPinnedDialog(feed(feedId), true);
+		} break;
+		}
+	}
+}
+
+int Session::pinnedDialogsCount() const {
+	return _pinnedDialogs.size();
+}
+
+const std::deque<Dialogs::Key> &Session::pinnedDialogsOrder() const {
+	return _pinnedDialogs;
+}
+
+void Session::clearPinnedDialogs() {
+	while (!_pinnedDialogs.empty()) {
+		setPinnedDialog(_pinnedDialogs.back(), false);
+	}
+}
+
+void Session::reorderTwoPinnedDialogs(
+		const Dialogs::Key &key1,
+		const Dialogs::Key &key2) {
+	const auto &order = pinnedDialogsOrder();
+	const auto index1 = ranges::find(order, key1) - begin(order);
+	const auto index2 = ranges::find(order, key2) - begin(order);
+	Assert(index1 >= 0 && index1 < order.size());
+	Assert(index2 >= 0 && index2 < order.size());
+	Assert(index1 != index2);
+	std::swap(_pinnedDialogs[index1], _pinnedDialogs[index2]);
+	key1.cachePinnedIndex(index2 + 1);
+	key2.cachePinnedIndex(index1 + 1);
+}
+
+void Session::setIsPinned(const Dialogs::Key &key, bool pinned) {
+	const auto already = ranges::find(_pinnedDialogs, key);
+	if (pinned) {
+		if (already != end(_pinnedDialogs)) {
+			auto saved = std::move(*already);
+			const auto alreadyIndex = already - end(_pinnedDialogs);
+			const auto count = int(size(_pinnedDialogs));
+			Assert(alreadyIndex < count);
+			for (auto index = alreadyIndex + 1; index != count; ++index) {
+				_pinnedDialogs[index - 1] = std::move(_pinnedDialogs[index]);
+				_pinnedDialogs[index - 1].cachePinnedIndex(index);
+			}
+			_pinnedDialogs.back() = std::move(saved);
+			_pinnedDialogs.back().cachePinnedIndex(count);
+		} else {
+			_pinnedDialogs.push_back(key);
+			if (_pinnedDialogs.size() > Global::PinnedDialogsCountMax()) {
+				_pinnedDialogs.front().cachePinnedIndex(0);
+				_pinnedDialogs.pop_front();
+
+				auto index = 0;
+				for (const auto &pinned : _pinnedDialogs) {
+					pinned.cachePinnedIndex(++index);
+				}
+			} else {
+				key.cachePinnedIndex(_pinnedDialogs.size());
+			}
+		}
+	} else if (!pinned && already != _pinnedDialogs.end()) {
+		_pinnedDialogs.erase(already);
+	}
+}
+
 not_null<Data::Feed*> Session::feed(FeedId id) {
 	if (const auto result = feedLoaded(id)) {
 		return result;
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 80ace497a..3d27d4cc0 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "chat_helpers/stickers.h"
+#include "dialogs/dialogs_key.h"
 
 namespace Data {
 
@@ -142,6 +143,15 @@ public:
 	MessageIdsList itemsToIds(const HistoryItemsList &items) const;
 	MessageIdsList groupToIds(not_null<HistoryMessageGroup*> group) const;
 
+	int pinnedDialogsCount() const;
+	const std::deque<Dialogs::Key> &pinnedDialogsOrder() const;
+	void setPinnedDialog(const Dialogs::Key &key, bool pinned);
+	void applyPinnedDialogs(const QVector<MTPDialog> &list);
+	void applyPinnedDialogs(const QVector<MTPDialogPeer> &list);
+	void reorderTwoPinnedDialogs(
+		const Dialogs::Key &key1,
+		const Dialogs::Key &key2);
+
 	not_null<Data::Feed*> feed(FeedId id);
 	Data::Feed *feedLoaded(FeedId id);
 
@@ -153,6 +163,9 @@ private:
 	}
 	void userIsContactUpdated(not_null<UserData*> user);
 
+	void clearPinnedDialogs();
+	void setIsPinned(const Dialogs::Key &key, bool pinned);
+
 	base::Variable<bool> _contactsLoaded = { false };
 	base::Variable<bool> _allChatsLoaded = { false };
 	base::Observable<void> _moreChatsLoaded;
@@ -180,6 +193,7 @@ private:
 	Stickers::Order _archivedStickerSetsOrder;
 	Stickers::SavedGifs _savedGifs;
 
+	std::deque<Dialogs::Key> _pinnedDialogs;
 	base::flat_map<FeedId, std::unique_ptr<Data::Feed>> _feeds;
 
 	rpl::lifetime _lifetime;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp
index 7fef3c0eb..9420ab4b6 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp
@@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "dialogs/dialogs_indexed_list.h"
 
+#include "auth_session.h"
+#include "data/data_session.h"
+
 namespace Dialogs {
 
 IndexedList::IndexedList(SortMode sortMode)
@@ -81,15 +84,9 @@ void IndexedList::movePinned(Row *row, int deltaSign) {
 		Assert(swapPinnedIndexWith != cbegin());
 		--swapPinnedIndexWith;
 	}
-	// #TODO feeds pinned
-	auto history1 = row->history();
-	auto history2 = (*swapPinnedIndexWith)->history();
-	Assert(history1->isPinnedDialog());
-	Assert(history2->isPinnedDialog());
-	auto index1 = history1->getPinnedIndex();
-	auto index2 = history2->getPinnedIndex();
-	history1->setPinnedIndex(index2);
-	history2->setPinnedIndex(index1);
+	Auth().data().reorderTwoPinnedDialogs(
+		row->key(),
+		(*swapPinnedIndexWith)->key());
 }
 
 void IndexedList::peerNameChanged(
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index aca7c2928..a1bfd7a1d 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -736,7 +736,7 @@ void DialogsInner::checkReorderPinnedStart(QPoint localPosition) {
 			if (updateReorderIndexGetCount() < 2) {
 				_dragging = nullptr;
 			} else {
-				_pinnedOrder = App::histories().getPinnedOrder();
+				_pinnedOrder = Auth().data().pinnedDialogsOrder();
 				_pinnedRows[_draggingIndex].yadd = anim::value(0, localPosition.y() - _dragStart.y());
 				_pinnedRows[_draggingIndex].animStartTime = getms();
 				_a_pinnedShifting.start();
@@ -748,8 +748,7 @@ void DialogsInner::checkReorderPinnedStart(QPoint localPosition) {
 int DialogsInner::shownPinnedCount() const {
 	auto result = 0;
 	for_const (auto row, *shownDialogs()) {
-		// #TODO feeds pinned
-		if (!row->history()->isPinnedDialog()) {
+		if (!row->entry()->isPinnedDialog()) {
 			break;
 		}
 		++result;
@@ -758,13 +757,12 @@ int DialogsInner::shownPinnedCount() const {
 }
 
 int DialogsInner::countPinnedIndex(Dialogs::Row *ofRow) {
-	// #TODO feeds pinned
-	if (!ofRow || !ofRow->history()->isPinnedDialog()) {
+	if (!ofRow || !ofRow->entry()->isPinnedDialog()) {
 		return -1;
 	}
 	auto result = 0;
 	for_const (auto row, *shownDialogs()) {
-		if (!row->history()->isPinnedDialog()) {
+		if (!row->entry()->isPinnedDialog()) {
 			break;
 		} else if (row == ofRow) {
 			return result;
@@ -775,16 +773,16 @@ int DialogsInner::countPinnedIndex(Dialogs::Row *ofRow) {
 }
 
 void DialogsInner::savePinnedOrder() {
-	auto newOrder = App::histories().getPinnedOrder();
+	const auto &newOrder = Auth().data().pinnedDialogsOrder();
 	if (newOrder.size() != _pinnedOrder.size()) {
 		return; // Something has changed in the set of pinned chats.
 	}
-	for_const (auto history, newOrder) {
-		if (_pinnedOrder.indexOf(history) < 0) {
+	for (const auto &pinned : newOrder) {
+		if (!base::contains(_pinnedOrder, pinned)) {
 			return; // Something has changed in the set of pinned chats.
 		}
 	}
-	App::histories().savePinnedToServer();
+	Auth().api().savePinnedOrder();
 }
 
 void DialogsInner::finishReorderPinned() {
@@ -2246,7 +2244,7 @@ void DialogsInner::destroyData() {
 
 Dialogs::RowDescriptor DialogsInner::chatListEntryBefore(
 		const Dialogs::RowDescriptor &which) const {
-	if (!which.key.value) {
+	if (!which.key) {
 		return Dialogs::RowDescriptor();
 	}
 	if (_state == DefaultState) {
@@ -2323,7 +2321,7 @@ Dialogs::RowDescriptor DialogsInner::chatListEntryBefore(
 
 Dialogs::RowDescriptor DialogsInner::chatListEntryAfter(
 		const Dialogs::RowDescriptor &which) const {
-	if (!which.key.value) {
+	if (!which.key) {
 		return Dialogs::RowDescriptor();
 	}
 	if (_state == DefaultState) {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index 0f1488bc9..1aec0554b 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "dialogs/dialogs_widget.h"
+#include "dialogs/dialogs_key.h"
 #include "base/flags.h"
 
 namespace Dialogs {
@@ -276,7 +277,7 @@ private:
 	};
 	std::vector<PinnedRow> _pinnedRows;
 	BasicAnimation _a_pinnedShifting;
-	QList<History*> _pinnedOrder;
+	std::deque<Dialogs::Key> _pinnedOrder;
 
 	// Remember the last currently dragged row top shift for updating area.
 	int _aboveTopShift = -1;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.cpp b/Telegram/SourceFiles/dialogs/dialogs_key.cpp
new file mode 100644
index 000000000..260e1641b
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_key.cpp
@@ -0,0 +1,66 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "dialogs/dialogs_key.h"
+
+#include "data/data_feed.h"
+
+namespace Dialogs {
+
+const QString &Key::name() const {
+	if (const auto h = history()) {
+		return h->peer->name;
+	}
+	// #TODO feeds name
+	static const auto empty = QString();
+	return empty;
+}
+
+const PeerData::NameFirstChars &Key::nameFirstChars() const {
+	if (const auto h = history()) {
+		return h->peer->nameFirstChars();
+	}
+	// #TODO feeds name
+	static const auto empty = PeerData::NameFirstChars();
+	return empty;
+}
+
+uint64 Key::sortKey() const {
+	if (const auto h = history()) {
+		return h->sortKeyInChatList();
+	} else if (const auto f = feed()) {
+		return f->sortKeyInChatList();
+	} else {
+		Unexpected("Key value in Key::sortKey");
+	}
+}
+
+void Key::cachePinnedIndex(int index) const {
+	if (const auto h = history()) {
+		h->cachePinnedIndex(index);
+	} else if (const auto f = feed()) {
+		f->cachePinnedIndex(index);
+	} else {
+		Unexpected("Key value in Key::setPinnedIndex");
+	}
+}
+
+History *Key::history() const {
+	if (const auto p = base::get_if<not_null<History*>>(&_value)) {
+		return *p;
+	}
+	return nullptr;
+}
+
+Data::Feed *Key::feed() const {
+	if (const auto p = base::get_if<not_null<Data::Feed*>>(&_value)) {
+		return *p;
+	}
+	return nullptr;
+}
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h
new file mode 100644
index 000000000..389ec7a13
--- /dev/null
+++ b/Telegram/SourceFiles/dialogs/dialogs_key.h
@@ -0,0 +1,85 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "base/value_ordering.h"
+
+namespace Data {
+class Feed;
+} // namespace Data
+
+namespace Dialogs {
+
+class Key {
+public:
+	Key() = default;
+	Key(History *history) : _value(history) {
+	}
+	Key(not_null<History*> history) : _value(history) {
+	}
+	Key(Data::Feed *feed) : _value(feed) {
+	}
+	Key(not_null<Data::Feed*> feed) : _value(feed) {
+	}
+
+	explicit operator bool() const {
+		return !!_value;
+	}
+	History *history() const;
+	Data::Feed *feed() const;
+
+	const QString &name() const;
+	const PeerData::NameFirstChars &nameFirstChars() const;
+	uint64 sortKey() const;
+	void cachePinnedIndex(int index) const;
+
+	inline bool operator<(const Key &other) const {
+		return _value < other._value;
+	}
+	inline bool operator>(const Key &other) const {
+		return (other < *this);
+	}
+	inline bool operator<=(const Key &other) const {
+		return !(other < *this);
+	}
+	inline bool operator>=(const Key &other) const {
+		return !(*this < other);
+	}
+	inline bool operator==(const Key &other) const {
+		return _value == other._value;
+	}
+	inline bool operator!=(const Key &other) const {
+		return !(*this == other);
+	}
+
+	base::optional_variant<
+		not_null<History*>,
+		not_null<Data::Feed*>> raw() const {
+		return _value;
+	}
+
+	// Not working :(
+	//friend inline auto value_ordering_helper(const Key &key) {
+	//	return key.value;
+	//}
+
+private:
+	base::optional_variant<not_null<History*>, not_null<Data::Feed*>> _value;
+
+};
+
+struct RowDescriptor {
+	RowDescriptor() = default;
+	RowDescriptor(Key key, MsgId msgId) : key(key), msgId(msgId) {
+	}
+
+	Key key;
+	MsgId msgId = 0;
+};
+
+} // namespace Dialogs
diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h
index f3dff3669..7831b3887 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_row.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_row.h
@@ -8,15 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #pragma once
 
 #include "ui/text/text.h"
-#include "base/value_ordering.h"
+#include "dialogs/dialogs_key.h"
 
 class History;
 class HistoryItem;
 
-namespace Data {
-class Feed;
-} // namespace Data
-
 namespace Ui {
 class RippleAnimation;
 } // namespace Ui
@@ -26,79 +22,6 @@ namespace Layout {
 class RowPainter;
 } // namespace Layout
 
-struct Key {
-	Key() = default;
-	Key(History *history) : value(history) {
-	}
-	Key(not_null<History*> history) : value(history) {
-	}
-	Key(Data::Feed *feed) : value(feed) {
-	}
-	Key(not_null<Data::Feed*> feed) : value(feed) {
-	}
-	const QString &name() const {
-		if (const auto p = base::get_if<not_null<History*>>(&value)) {
-			return (*p)->peer->name;
-		}
-		// #TODO feeds name
-		static const auto empty = QString();
-		return empty;
-	}
-	const PeerData::NameFirstChars &nameFirstChars() const {
-		if (const auto p = base::get_if<not_null<History*>>(&value)) {
-			return (*p)->peer->nameFirstChars();
-		}
-		// #TODO feeds name
-		static const auto empty = PeerData::NameFirstChars();
-		return empty;
-	}
-	uint64 sortKey() const {
-		if (const auto p = base::get_if<not_null<History*>>(&value)) {
-			return (*p)->sortKeyInChatList();
-		}
-		// #TODO feeds sort in chats list
-		return 0ULL;
-	}
-	History *history() const {
-		if (const auto p = base::get_if<not_null<History*>>(&value)) {
-			return *p;
-		}
-		return nullptr;
-	}
-	Data::Feed *feed() const {
-		if (const auto p = base::get_if<not_null<Data::Feed*>>(&value)) {
-			return *p;
-		}
-		return nullptr;
-	}
-
-	inline bool operator<(const Key &other) const {
-		return value < other.value;
-	}
-	inline bool operator==(const Key &other) const {
-		return value == other.value;
-	}
-
-	// Not working :(
-	//friend inline auto value_ordering_helper(const Key &key) {
-	//	return key.value;
-	//}
-
-	base::optional_variant<
-		not_null<History*>,
-		not_null<Data::Feed*>> value;
-
-};
-
-struct RowDescriptor {
-	RowDescriptor() = default;
-	RowDescriptor(Key key, MsgId msgId) : key(key), msgId(msgId) {
-	}
-
-	Key key;
-	MsgId msgId = 0;
-};
-
 class RippleRow {
 public:
 	RippleRow();
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index 9857e7082..1dcee945d 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "dialogs/dialogs_inner_widget.h"
 #include "dialogs/dialogs_search_from_controllers.h"
-#include "dialogs/dialogs_row.h"
+#include "dialogs/dialogs_key.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/input_fields.h"
 #include "ui/wrap/fade_wrap.h"
@@ -400,12 +400,11 @@ void DialogsWidget::pinnedDialogsReceived(
 
 	if (_pinnedDialogsRequestId != requestId) return;
 
-	App::histories().clearPinned();
-
 	auto &data = result.c_messages_peerDialogs();
 	App::feedUsers(data.vusers);
 	App::feedChats(data.vchats);
 
+	Auth().data().applyPinnedDialogs(data.vdialogs.v);
 	applyReceivedDialogs(data.vdialogs.v, data.vmessages.v);
 
 	_pinnedDialogsRequestId = 0;
@@ -420,7 +419,6 @@ void DialogsWidget::pinnedDialogsReceived(
 void DialogsWidget::applyReceivedDialogs(
 		const QVector<MTPDialog> &dialogs,
 		const QVector<MTPMessage> &messages) {
-	Auth().api().applyDialogsPinned(dialogs);
 	App::feedMsgs(messages, NewMessageLast);
 	_inner->dialogsReceived(dialogs);
 	onListScroll();
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index d25dd2424..23aa030a5 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -47,8 +47,6 @@ constexpr auto kStatusShowClientsidePlayGame = 10000;
 constexpr auto kSetMyActionForMs = 10000;
 constexpr auto kNewBlockEachMessage = 50;
 
-auto GlobalPinnedIndex = 0;
-
 HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) {
 	auto text = TextWithEntities { lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")) };
 	TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
@@ -516,7 +514,8 @@ void ChannelHistory::checkMaxReadMessageDate() {
 			if (!item->unread()) {
 				_maxReadMessageDate = item->date;
 				if (item->isGroupMigrate() && isMegagroup() && peer->migrateFrom()) {
-					_maxReadMessageDate = date(MTP_int(peer->asChannel()->date + 1)); // no report spam panel
+					// No report spam panel.
+					_maxReadMessageDate = date(MTP_int(peer->asChannel()->date + 1));
 				}
 				return;
 			}
@@ -583,7 +582,6 @@ not_null<History*> Histories::findOrInsert(const PeerId &peerId, int32 unreadCou
 void Histories::clear() {
 	App::historyClearMsgs();
 
-	_pinnedDialogs.clear();
 	auto temp = base::take(map);
 	for_const (auto history, temp) {
 		delete history;
@@ -677,63 +675,6 @@ bool Histories::unreadOnlyMuted() const {
 	return Global::IncludeMuted() ? (_unreadMuted >= _unreadFull) : false;
 }
 
-void Histories::setIsPinned(History *history, bool isPinned) {
-	if (isPinned) {
-		_pinnedDialogs.insert(history);
-		if (_pinnedDialogs.size() > Global::PinnedDialogsCountMax()) {
-			auto minIndex = GlobalPinnedIndex + 1;
-			auto minIndexHistory = (History*)nullptr;
-			for_const (auto pinned, _pinnedDialogs) {
-				if (pinned->getPinnedIndex() < minIndex) {
-					minIndex = pinned->getPinnedIndex();
-					minIndexHistory = pinned;
-				}
-			}
-			Assert(minIndexHistory != nullptr);
-			minIndexHistory->setPinnedDialog(false);
-		}
-	} else {
-		_pinnedDialogs.remove(history);
-	}
-}
-
-void Histories::clearPinned() {
-	for (auto pinned : base::take(_pinnedDialogs)) {
-		pinned->setPinnedDialog(false);
-	}
-}
-
-int Histories::pinnedCount() const {
-	return _pinnedDialogs.size();
-}
-
-QList<History*> Histories::getPinnedOrder() const {
-	QMap<int, History*> sorter;
-	for_const (auto pinned, _pinnedDialogs) {
-		sorter.insert(pinned->getPinnedIndex(), pinned);
-	}
-	QList<History*> result;
-	for (auto i = sorter.cend(), e = sorter.cbegin(); i != e;) {
-		--i;
-		result.push_back(i.value());
-	}
-	return result;
-}
-
-void Histories::savePinnedToServer() const {
-	const auto order = getPinnedOrder();
-	auto peers = QVector<MTPInputDialogPeer>();
-	peers.reserve(order.size());
-	for (const auto history : order) {
-		peers.push_back(MTP_inputDialogPeer(history->peer->input));
-	}
-	auto flags = MTPmessages_ReorderPinnedDialogs::Flag::f_force;
-	MTP::send(
-		MTPmessages_ReorderPinnedDialogs(
-			MTP_flags(flags),
-			MTP_vector(peers)));
-}
-
 void Histories::selfDestructIn(not_null<HistoryItem*> item, TimeMs delay) {
 	_selfDestructItems.push_back(item->fullId());
 	if (!_selfDestructTimer.isActive() || _selfDestructTimer.remainingTime() > delay) {
@@ -2262,8 +2203,10 @@ void History::setNotLoadedAtBottom() {
 }
 
 namespace {
-	uint32 _dialogsPosToTopShift = 0x80000000UL;
-}
+
+uint32 _dialogsPosToTopShift = 0x80000000UL;
+
+} // namespace
 
 inline uint64 dialogPosFromDate(const QDateTime &date) {
 	if (date.isNull()) return 0;
@@ -2316,7 +2259,9 @@ void History::updateChatListSortPosition() {
 		return lastMsgDate;
 	};
 
-	_sortKeyInChatList = isPinnedDialog() ? pinnedDialogPos(_pinnedIndex) : dialogPosFromDate(chatListDate());
+	_sortKeyInChatList = isPinnedDialog()
+		? pinnedDialogPos(_pinnedIndex)
+		: dialogPosFromDate(chatListDate());
 	if (auto m = App::main()) {
 		if (needUpdateInChatList()) {
 			if (_sortKeyInChatList) {
@@ -2602,20 +2547,17 @@ void History::updateChatListEntry() const {
 	}
 }
 
-void History::setPinnedDialog(bool isPinned) {
-	setPinnedIndex(isPinned ? (++GlobalPinnedIndex) : 0);
-}
-
-void History::setPinnedIndex(int pinnedIndex) {
+void History::cachePinnedIndex(int pinnedIndex) {
 	if (_pinnedIndex != pinnedIndex) {
 		auto wasPinned = isPinnedDialog();
 		_pinnedIndex = pinnedIndex;
 		updateChatListSortPosition();
 		updateChatListEntry();
 		if (wasPinned != isPinnedDialog()) {
-			Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::PinnedChanged);
+			Notify::peerUpdatedDelayed(
+				peer,
+				Notify::PeerUpdate::Flag::PinnedChanged);
 		}
-		App::histories().setIsPinned(this, isPinnedDialog());
 	}
 }
 
diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h
index 6878f643c..ac20eae05 100644
--- a/Telegram/SourceFiles/history/history.h
+++ b/Telegram/SourceFiles/history/history.h
@@ -75,12 +75,6 @@ public:
 		}
 	}
 
-	void setIsPinned(History *history, bool isPinned);
-	void clearPinned();
-	int pinnedCount() const;
-	QList<History*> getPinnedOrder() const;
-	void savePinnedToServer() const;
-
 	struct SendActionAnimationUpdate {
 		History *history;
 		int width;
@@ -98,7 +92,6 @@ private:
 	int _unreadFull = 0;
 	int _unreadMuted = 0;
 	base::Observable<SendActionAnimationUpdate> _sendActionAnimationUpdated;
-	OrderedSet<History*> _pinnedDialogs;
 
 	base::Timer _selfDestructTimer;
 	std::vector<FullMsgId> _selfDestructItems;
@@ -275,11 +268,7 @@ public:
 	bool isPinnedDialog() const {
 		return (_pinnedIndex > 0);
 	}
-	void setPinnedDialog(bool isPinned);
-	void setPinnedIndex(int newPinnedIndex);
-	int getPinnedIndex() const {
-		return _pinnedIndex;
-	}
+	void cachePinnedIndex(int newPinnedIndex);
 
 	MsgId minMsgId() const;
 	MsgId maxMsgId() const;
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 46bea9634..58bb947f8 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -65,7 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "inline_bots/inline_results_widget.h"
 #include "chat_helpers/emoji_suggestions_widget.h"
 #include "core/crash_reports.h"
-#include "dialogs/dialogs_row.h"
+#include "dialogs/dialogs_key.h"
 #include "styles/style_history.h"
 #include "styles/style_dialogs.h"
 #include "styles/style_window.h"
diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
index 4a2f2e0d1..41eeebe00 100644
--- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
+++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp
@@ -1008,9 +1008,10 @@ void Widget::inlineResultsDone(const MTPmessages_BotResults &result) {
 
 	auto it = _inlineCache.find(_inlineQuery);
 	auto adding = (it != _inlineCache.cend());
-	// #TODO layer 72 feed users
 	if (result.type() == mtpc_messages_botResults) {
 		auto &d = result.c_messages_botResults();
+		App::feedUsers(d.vusers);
+
 		auto &v = d.vresults.v;
 		auto queryId = d.vquery_id.v;
 
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 75fc425e1..092afec8c 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -34,7 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "observer_peer.h"
 #include "apiwrap.h"
 #include "dialogs/dialogs_widget.h"
-#include "dialogs/dialogs_row.h"
+#include "dialogs/dialogs_key.h"
 #include "history/history_widget.h"
 #include "history/history_message.h"
 #include "history/history_media.h"
@@ -1100,7 +1100,7 @@ void MainWidget::deleteConversation(PeerData *peer, bool deleteHistory) {
 		Ui::showChatsList();
 	}
 	if (auto history = App::historyLoaded(peer->id)) {
-		history->setPinnedDialog(false);
+		Auth().data().setPinnedDialog(history, false);
 		removeDialog(history);
 		if (peer->isMegagroup() && peer->asChannel()->mgInfo->migrateFromPtr) {
 			if (auto migrated = App::historyLoaded(peer->asChannel()->mgInfo->migrateFromPtr->id)) {
@@ -5330,31 +5330,20 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 					case mtpc_dialogPeerFeed: {
 						const auto &feed = dialogPeer.c_dialogPeerFeed();
 						const auto feedId = feed.vfeed_id.v;
-						// #TODO feeds
+						if (!Auth().data().feedLoaded(feedId)) {
+							DEBUG_LOG(("API Error: "
+								"pinned feed not loaded for feedId %1"
+								).arg(feedId
+								));
+							return false;
+						}
 					} break;
 					}
 				}
 				return true;
 			}();
 			if (allLoaded) {
-				App::histories().clearPinned();
-				for (auto i = order.size(); i != 0;) {
-					const auto &dialogPeer = order[--i];
-					switch (dialogPeer.type()) {
-					case mtpc_dialogPeer: {
-						const auto &peer = dialogPeer.c_dialogPeer();
-						const auto peerId = peerFromMTP(peer.vpeer);
-						const auto history = App::historyLoaded(peerId);
-						Assert(history != nullptr);
-						history->setPinnedDialog(true);
-					} break;
-					case mtpc_dialogPeerFeed: {
-						const auto &feed = dialogPeer.c_dialogPeerFeed();
-						const auto feedId = feed.vfeed_id.v;
-						// #TODO feeds
-					} break;
-					}
-				}
+				Auth().data().applyPinnedDialogs(order);
 			} else {
 				_dialogs->loadPinnedDialogs();
 			}
@@ -5369,7 +5358,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 		case mtpc_dialogPeer: {
 			const auto peerId = peerFromMTP(d.vpeer.c_dialogPeer().vpeer);
 			if (const auto history = App::historyLoaded(peerId)) {
-				history->setPinnedDialog(d.is_pinned());
+				Auth().data().setPinnedDialog(history, d.is_pinned());
 			} else {
 				DEBUG_LOG(("API Error: "
 					"pinned chat not loaded for peer %1"
@@ -5380,7 +5369,15 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
 		} break;
 		case mtpc_dialogPeerFeed: {
 			const auto feedId = d.vpeer.c_dialogPeerFeed().vfeed_id.v;
-			// #TODO feeds
+			if (const auto feed = Auth().data().feedLoaded(feedId)) {
+				Auth().data().setPinnedDialog(feed, d.is_pinned());
+			} else {
+				DEBUG_LOG(("API Error: "
+					"pinned feed not loaded for feedId %1"
+					).arg(feedId
+					));
+				_dialogs->loadPinnedDialogs();
+			}
 		} break;
 		}
 	} break;
diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp
index 7512ab06f..6da36d5da 100644
--- a/Telegram/SourceFiles/mainwindow.cpp
+++ b/Telegram/SourceFiles/mainwindow.cpp
@@ -222,7 +222,9 @@ void MainWindow::setupPasscode() {
 }
 
 void MainWindow::setupIntro() {
-	if (_intro && !_intro->isHidden() && !_main) return;
+	if (_intro && !_intro->isHidden() && !_main) {
+		return;
+	}
 
 	Ui::hideSettingsAndLayer(anim::type::instant);
 
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index c78cede73..347de8a76 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -65,12 +65,14 @@ private:
 };
 
 History *FindWastedPin() {
-	auto order = App::histories().getPinnedOrder();
-	for_const (auto pinned, order) {
-		if (pinned->peer->isChat()
-			&& pinned->peer->asChat()->isDeactivated()
-			&& !pinned->inChatList(Dialogs::Mode::All)) {
-			return pinned;
+	const auto &order = Auth().data().pinnedDialogsOrder();
+	for (const auto pinned : order) {
+		if (const auto history = pinned.history()) {
+			if (history->peer->isChat()
+				&& history->peer->asChat()->isDeactivated()
+				&& !history->inChatList(Dialogs::Mode::All)) {
+				return history;
+			}
 		}
 	}
 	return nullptr;
@@ -116,14 +118,14 @@ void Filler::addPinToggle() {
 	auto pinToggle = [peer] {
 		auto history = App::history(peer);
 		auto isPinned = !history->isPinnedDialog();
-		auto pinnedCount = App::histories().pinnedCount();
-		auto pinnedMax = Global::PinnedDialogsCountMax();
+		const auto pinnedCount = Auth().data().pinnedDialogsCount();
+		const auto pinnedMax = Global::PinnedDialogsCountMax();
 		if (isPinned && pinnedCount >= pinnedMax) {
 			// Some old chat, that was converted to supergroup, maybe is still pinned.
 			if (auto wasted = FindWastedPin()) {
-				wasted->setPinnedDialog(false);
-				history->setPinnedDialog(isPinned);
-				App::histories().savePinnedToServer();
+				Auth().data().setPinnedDialog(wasted, false);
+				Auth().data().setPinnedDialog(history, true);
+				Auth().api().savePinnedOrder();
 			} else {
 				auto errorText = lng_error_pinned_max(
 					lt_count,
@@ -133,7 +135,7 @@ void Filler::addPinToggle() {
 			return;
 		}
 
-		history->setPinnedDialog(isPinned);
+		Auth().data().setPinnedDialog(history, isPinned);
 		auto flags = MTPmessages_ToggleDialogPin::Flags(0);
 		if (isPinned) {
 			flags |= MTPmessages_ToggleDialogPin::Flag::f_pinned;
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index ff87a9e78..410429230 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -198,6 +198,8 @@
 <(src_loc)/dialogs/dialogs_indexed_list.h
 <(src_loc)/dialogs/dialogs_inner_widget.cpp
 <(src_loc)/dialogs/dialogs_inner_widget.h
+<(src_loc)/dialogs/dialogs_key.cpp
+<(src_loc)/dialogs/dialogs_key.h
 <(src_loc)/dialogs/dialogs_layout.cpp
 <(src_loc)/dialogs/dialogs_layout.h
 <(src_loc)/dialogs/dialogs_list.cpp