diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index e73f5fecb..71d5d27cb 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -205,6 +205,16 @@ void UserData::setBusinessDetails(Data::BusinessDetails details) {
 	session().changes().peerUpdated(this, UpdateFlag::BusinessDetails);
 }
 
+void UserData::setStarRefProgram(StarRefProgram program) {
+	const auto info = botInfo.get();
+	if (info && info->starRefProgram != program) {
+		info->starRefProgram = program;
+		session().changes().peerUpdated(
+			this,
+			Data::PeerUpdate::Flag::StarRefProgram);
+	}
+}
+
 ChannelId UserData::personalChannelId() const {
 	return _personalChannelId;
 }
@@ -600,20 +610,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
 	}
 	if (const auto info = user->botInfo.get()) {
 		info->canManageEmojiStatus = update.is_bot_can_manage_emoji_status();
-		auto starRefProgram = StarRefProgram();
-		if (const auto program = update.vstarref_program()) {
-			const auto &data = program->data();
-			starRefProgram.commission = data.vcommission_permille().v;
-			starRefProgram.durationMonths
-				= data.vduration_months().value_or_empty();
-			starRefProgram.endDate = data.vend_date().value_or_empty();
-		}
-		if (info->starRefProgram != starRefProgram) {
-			info->starRefProgram = starRefProgram;
-			user->session().changes().peerUpdated(
-				user,
-				Data::PeerUpdate::Flag::StarRefProgram);
-		}
+		user->setStarRefProgram(
+			Data::ParseStarRefProgram(update.vstarref_program()));
 	}
 	if (const auto pinned = update.vpinned_msg_id()) {
 		SetTopPinnedMessageId(user, pinned->v);
@@ -733,4 +731,16 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
 	user->fullUpdated();
 }
 
+StarRefProgram ParseStarRefProgram(const MTPStarRefProgram *program) {
+	if (!program) {
+		return {};
+	}
+	auto result = StarRefProgram();
+	const auto &data = program->data();
+	result.commission = data.vcommission_permille().v;
+	result.durationMonths = data.vduration_months().value_or_empty();
+	result.endDate = data.vend_date().value_or_empty();
+	return result;
+}
+
 } // namespace Data
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index f8d7ba48a..26256dedc 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -218,6 +218,8 @@ public:
 	[[nodiscard]] const Data::BusinessDetails &businessDetails() const;
 	void setBusinessDetails(Data::BusinessDetails details);
 
+	void setStarRefProgram(StarRefProgram program);
+
 	[[nodiscard]] ChannelId personalChannelId() const;
 	[[nodiscard]] MsgId personalChannelMessageId() const;
 	void setPersonalChannel(ChannelId channelId, MsgId messageId);
@@ -265,4 +267,7 @@ namespace Data {
 
 void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update);
 
+[[nodiscard]] StarRefProgram ParseStarRefProgram(
+	const MTPStarRefProgram *program);
+
 } // namespace Data
diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp
index 2254b48b5..e8ccc9b11 100644
--- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp
+++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp
@@ -7,17 +7,70 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "info/bot/starref/info_bot_starref_common.h"
 
+#include "apiwrap.h"
+#include "boxes/peers/replace_boost_box.h" // CreateUserpicsTransfer.
+#include "data/data_session.h"
+#include "lang/lang_keys.h"
+#include "main/main_session.h"
 #include "settings/settings_common.h"
+#include "ui/controls/userpic_button.h"
+#include "ui/layers/generic_box.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/labels.h"
 #include "ui/wrap/vertical_layout.h"
 #include "ui/text/text_utilities.h"
-//#include "styles/style_info.h"
+#include "ui/painter.h"
+#include "ui/vertical_list.h"
+#include "styles/style_chat.h"
 #include "styles/style_layers.h"
+#include "styles/style_premium.h"
 #include "styles/style_settings.h"
-//#include "styles/style_premium.h"
+
+#include <QtWidgets/QApplication>
 
 namespace Info::BotStarRef {
+namespace {
+
+void ConnectStarRef(
+		not_null<UserData*> bot,
+		not_null<PeerData*> peer,
+		Fn<void(ConnectedBot)> done,
+		Fn<void(const QString &)> fail) {
+	bot->session().api().request(MTPpayments_ConnectStarRefBot(
+		peer->input,
+		bot->inputUser
+	)).done([=](const MTPpayments_ConnectedStarRefBots &result) {
+		const auto parsed = Parse(&bot->session(), result);
+		if (parsed.empty()) {
+			fail(u"EMPTY"_q);
+		} else {
+			done(parsed.front());
+		}
+	}).fail([=](const MTP::Error &error) {
+		fail(error.type());
+	}).send();
+}
+
+} // namespace
+
+QString FormatStarRefCommission(ushort commission) {
+	return QString::number(commission / 10.) + '%';
+}
+
+rpl::producer<TextWithEntities> FormatProgramDuration(
+		StarRefProgram program) {
+	return !program.durationMonths
+		? tr::lng_star_ref_one_about_for_forever(Ui::Text::RichLangValue)
+		: (program.durationMonths < 12)
+		? tr::lng_star_ref_one_about_for_months(
+			lt_count,
+			rpl::single(program.durationMonths * 1.),
+			Ui::Text::RichLangValue)
+		: tr::lng_star_ref_one_about_for_years(
+			lt_count,
+			rpl::single((program.durationMonths / 12) * 1.),
+			Ui::Text::RichLangValue);
+}
 
 not_null<Ui::AbstractButton*> AddViewListButton(
 		not_null<Ui::VerticalLayout*> parent,
@@ -108,8 +161,346 @@ not_null<Ui::AbstractButton*> AddViewListButton(
 	return button;
 }
 
-QString FormatStarRefCommission(ushort commission) {
-	return QString::number(commission / 10.) + '%';
+not_null<Ui::RoundButton*> AddFullWidthButton(
+		not_null<Ui::BoxContent*> box,
+		rpl::producer<QString> text,
+		Fn<void()> callback,
+		const style::RoundButton *stOverride) {
+	const auto &boxSt = box->getDelegate()->style();
+	const auto result = box->addButton(
+		std::move(text),
+		std::move(callback),
+		stOverride ? *stOverride : boxSt.button);
+	rpl::combine(
+		box->widthValue(),
+		result->widthValue()
+	) | rpl::start_with_next([=](int width, int buttonWidth) {
+		const auto correct = width
+			- boxSt.buttonPadding.left()
+			- boxSt.buttonPadding.right();
+		if (correct > 0 && buttonWidth != correct) {
+			result->resizeToWidth(correct);
+			result->moveToLeft(
+				boxSt.buttonPadding.left(),
+				boxSt.buttonPadding.top(),
+				width);
+		}
+	}, result->lifetime());
+	return result;
+}
+
+void AddFullWidthButtonFooter(
+		not_null<Ui::BoxContent*> box,
+		not_null<Ui::RpWidget*> button,
+		rpl::producer<TextWithEntities> text) {
+	const auto footer = Ui::CreateChild<Ui::FlatLabel>(
+		button->parentWidget(),
+		std::move(text),
+		st::starrefJoinFooter);
+	button->geometryValue() | rpl::start_with_next([=](QRect geometry) {
+		footer->resizeToWidth(geometry.width());
+		const auto &st = box->getDelegate()->style();
+		const auto top = geometry.y() + geometry.height();
+		const auto available = st.buttonPadding.bottom();
+		footer->moveToLeft(
+			geometry.left(),
+			top + (available - footer->height()) / 2);
+	}, footer->lifetime());
+}
+
+object_ptr<Ui::BoxContent> StarRefLinkBox(
+		ConnectedBot row,
+		not_null<PeerData*> peer) {
+	return Box([=](not_null<Ui::GenericBox*> box) {
+		const auto show = box->uiShow();
+
+		const auto bot = row.bot;
+		const auto program = row.state.program;
+
+		box->setStyle(st::starrefFooterBox);
+		box->setNoContentMargin(true);
+		box->addTopButton(st::boxTitleClose, [=] {
+			box->closeBox();
+		});
+
+		box->addRow(
+			CreateUserpicsTransfer(
+				box,
+				rpl::single(std::vector{ not_null<PeerData*>(bot) }),
+				peer,
+				UserpicsTransferType::StarRefJoin),
+			st::boxRowPadding + st::starrefJoinUserpicsPadding);
+		box->addRow(
+			object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
+				box,
+				object_ptr<Ui::FlatLabel>(
+					box,
+					tr::lng_star_ref_link_title(),
+					st::boxTitle)),
+			st::boxRowPadding + st::starrefJoinTitlePadding);
+		box->addRow(
+			object_ptr<Ui::FlatLabel>(
+				box,
+				(peer->isSelf()
+					? tr::lng_star_ref_link_about_user
+					: peer->isUser()
+					? tr::lng_star_ref_link_about_user
+					: tr::lng_star_ref_link_about_channel)(
+						lt_amount,
+						rpl::single(Ui::Text::Bold(
+							FormatStarRefCommission(program.commission))),
+						lt_app,
+						rpl::single(Ui::Text::Bold(bot->name())),
+						lt_duration,
+						FormatProgramDuration(program),
+						Ui::Text::WithEntities),
+				st::starrefCenteredText),
+			st::boxRowPadding);
+
+		Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 4);
+
+		box->addRow(
+			object_ptr<Ui::FlatLabel>(
+				box,
+				tr::lng_star_ref_link_recipient(),
+				st::starrefCenteredText));
+		Ui::AddSkip(box->verticalLayout());
+		box->addRow(object_ptr<Ui::AbstractButton>::fromRaw(
+			MakePeerBubbleButton(box, peer).release()
+		))->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+		Ui::AddSkip(box->verticalLayout());
+		row.state.link;
+
+		const auto copy = [=] {
+			QApplication::clipboard()->setText(row.state.link);
+			box->uiShow()->showToast(tr::lng_username_copied(tr::now));
+			box->closeBox();
+		};
+		const auto button = AddFullWidthButton(
+			box,
+			tr::lng_star_ref_link_copy(),
+			copy,
+			&st::starrefCopyButton);
+		const auto name = TextWithEntities{ bot->name() };
+		AddFullWidthButtonFooter(
+			box,
+			button,
+			(row.state.users > 0
+				? tr::lng_star_ref_link_copy_users(
+					lt_count,
+					rpl::single(row.state.users * 1.),
+					lt_app,
+					rpl::single(name),
+					Ui::Text::WithEntities)
+				: tr::lng_star_ref_link_copy_none(
+					lt_app,
+					rpl::single(name),
+					Ui::Text::WithEntities)));
+	});
+}
+
+[[nodiscard]] object_ptr<Ui::BoxContent> JoinStarRefBox(
+		ConnectedBot row,
+		not_null<PeerData*> peer) {
+	Expects(row.bot->isUser());
+
+	return Box([=](not_null<Ui::GenericBox*> box) {
+		const auto show = box->uiShow();
+
+		const auto bot = row.bot;
+		const auto program = row.state.program;
+
+		box->setStyle(st::starrefFooterBox);
+		box->setNoContentMargin(true);
+		box->addTopButton(st::boxTitleClose, [=] {
+			box->closeBox();
+		});
+
+		box->addRow(
+			CreateUserpicsTransfer(
+				box,
+				rpl::single(std::vector{ not_null<PeerData*>(bot) }),
+				peer,
+				UserpicsTransferType::StarRefJoin),
+			st::boxRowPadding + st::starrefJoinUserpicsPadding);
+		box->addRow(
+			object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
+				box,
+				object_ptr<Ui::FlatLabel>(
+					box,
+					tr::lng_star_ref_title(),
+					st::boxTitle)),
+			st::boxRowPadding + st::starrefJoinTitlePadding);
+		box->addRow(
+			object_ptr<Ui::FlatLabel>(
+				box,
+				tr::lng_star_ref_one_about(
+					lt_app,
+					rpl::single(Ui::Text::Bold(bot->name())),
+					lt_amount,
+					rpl::single(Ui::Text::Bold(
+						FormatStarRefCommission(program.commission))),
+					lt_duration,
+					FormatProgramDuration(program),
+					Ui::Text::WithEntities),
+				st::starrefCenteredText),
+			st::boxRowPadding);
+
+		Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 4);
+
+		box->addRow(
+			object_ptr<Ui::FlatLabel>(
+				box,
+				tr::lng_star_ref_link_recipient(),
+				st::starrefCenteredText));
+		Ui::AddSkip(box->verticalLayout());
+		box->addRow(object_ptr<Ui::AbstractButton>::fromRaw(
+			MakePeerBubbleButton(box, peer).release()
+		))->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+		struct State {
+			QPointer<Ui::GenericBox> weak;
+			bool sent = false;
+		};
+		const auto state = std::make_shared<State>();
+		state->weak = box;
+
+		const auto send = [=] {
+			if (state->sent) {
+				return;
+			}
+			state->sent = true;
+			ConnectStarRef(bot->asUser(), peer, [=](ConnectedBot info) {
+				show->show(StarRefLinkBox(info, peer));
+				if (const auto strong = state->weak.data()) {
+					strong->closeBox();
+				}
+			}, [=](const QString &error) {
+				state->sent = false;
+				show->showToast(u"Failed: "_q + error);
+			});
+		};
+		const auto button = AddFullWidthButton(
+			box,
+			tr::lng_star_ref_one_join(),
+			send);
+		AddFullWidthButtonFooter(
+			box,
+			button,
+			tr::lng_star_ref_one_join_text(
+				lt_terms,
+				tr::lng_star_ref_button_link(
+				) | Ui::Text::ToLink(tr::lng_star_ref_tos_url(tr::now)),
+				Ui::Text::WithEntities));
+	});
+}
+
+std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton(
+		not_null<QWidget*> parent,
+		not_null<PeerData*> peer,
+		Ui::RpWidget *right) {
+	auto result = std::make_unique<Ui::AbstractButton>(parent);
+
+	const auto size = st::chatGiveawayPeerSize;
+	const auto padding = st::chatGiveawayPeerPadding;
+
+	const auto raw = result.get();
+
+	const auto width = raw->lifetime().make_state<int>();
+	const auto name = raw->lifetime().make_state<Ui::FlatLabel>(
+		raw,
+		rpl::single(peer->name()),
+		st::botEmojiStatusName);
+	const auto userpic = raw->lifetime().make_state<Ui::UserpicButton>(
+		raw,
+		peer,
+		st::botEmojiStatusUserpic);
+	name->setAttribute(Qt::WA_TransparentForMouseEvents);
+	userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+	if (right) {
+		right->setParent(result.get());
+		right->show();
+		right->setAttribute(Qt::WA_TransparentForMouseEvents);
+	}
+
+	auto rightWidth = right ? right->widthValue() : rpl::single(0);
+
+	raw->resize(size, size);
+	rpl::combine(
+		raw->sizeValue(),
+		std::move(rightWidth)
+	) | rpl::start_with_next([=](QSize outer, int rwidth) {
+		const auto full = outer.width();
+		const auto decorations = size
+			+ padding.left()
+			+ padding.right()
+			+ rwidth;
+		const auto inner = full - decorations;
+		const auto use = std::min(inner, name->textMaxWidth());
+		*width = use + decorations;
+		const auto left = (full - *width) / 2;
+		if (inner > 0) {
+			userpic->moveToLeft(left, 0, outer.width());
+			if (right) {
+				right->moveToLeft(
+					left + *width - padding.right() - right->width(),
+					padding.top(),
+					outer.width());
+			}
+			name->resizeToWidth(use);
+			name->moveToLeft(
+				left + size + padding.left(),
+				padding.top(),
+				outer.width());
+		}
+	}, raw->lifetime());
+	raw->paintRequest() | rpl::start_with_next([=] {
+		auto p = QPainter(raw);
+		const auto left = (raw->width() - *width) / 2;
+		const auto skip = size / 2;
+		p.setClipRect(left + skip, 0, *width - skip, size);
+		auto hq = PainterHighQualityEnabler(p);
+		p.setPen(Qt::NoPen);
+		p.setBrush(st::windowBgOver);
+		p.drawRoundedRect(left, 0, *width, size, skip, skip);
+	}, raw->lifetime());
+
+	return result;
+}
+ConnectedBots Parse(
+		not_null<Main::Session*> session,
+		const MTPpayments_ConnectedStarRefBots &bots) {
+	const auto &data = bots.data();
+	session->data().processUsers(data.vusers());
+	const auto &list = data.vconnected_bots().v;
+	auto result = ConnectedBots();
+	for (const auto &bot : list) {
+		const auto &data = bot.data();
+		const auto botId = UserId(data.vbot_id());
+		const auto link = qs(data.vurl());
+		const auto date = data.vdate().v;
+		const auto commission = data.vcommission_permille().v;
+		const auto durationMonths
+			= data.vduration_months().value_or_empty();
+		const auto users = int(data.vparticipants().v);
+		const auto revoked = data.is_revoked();
+		result.push_back({
+			.bot = session->data().user(botId),
+			.state = {
+				.program = {
+					.commission = ushort(commission),
+					.durationMonths = uchar(durationMonths),
+				},
+				.link = link,
+				.date = date,
+				.users = users,
+				.revoked = revoked,
+			},
+		});
+	}
+	return result;
 }
 
 } // namespace Info::BotStarRef
\ No newline at end of file
diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h
index 10e31e872..6e6b33a5e 100644
--- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h
+++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h
@@ -7,18 +7,70 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #pragma once
 
+#include "base/object_ptr.h"
+#include "data/data_user.h"
+
 namespace Ui {
 class AbstractButton;
+class RoundButton;
 class VerticalLayout;
+class BoxContent;
+class RpWidget;
 } // namespace Ui
 
+namespace style {
+struct RoundButton;
+} // namespace style
+
 namespace Info::BotStarRef {
 
+struct ConnectedBotState {
+	StarRefProgram program;
+	QString link;
+	TimeId date = 0;
+	int users = 0;
+	bool revoked = false;
+};
+struct ConnectedBot {
+	not_null<UserData*> bot;
+	ConnectedBotState state;
+};
+using ConnectedBots = std::vector<ConnectedBot>;
+
+[[nodiscard]] QString FormatStarRefCommission(ushort commission);
+[[nodiscard]] rpl::producer<TextWithEntities> FormatProgramDuration(
+	StarRefProgram program);
+
 [[nodiscard]] not_null<Ui::AbstractButton*> AddViewListButton(
 	not_null<Ui::VerticalLayout*> parent,
 	rpl::producer<QString> title,
 	rpl::producer<QString> subtitle);
 
-[[nodiscard]] QString FormatStarRefCommission(ushort commission);
+[[nodiscard]] not_null<Ui::RoundButton*> AddFullWidthButton(
+	not_null<Ui::BoxContent*> box,
+	rpl::producer<QString> text,
+	Fn<void()> callback = nullptr,
+	const style::RoundButton *stOverride = nullptr);
+
+void AddFullWidthButtonFooter(
+	not_null<Ui::BoxContent*> box,
+	not_null<Ui::RpWidget*> button,
+	rpl::producer<TextWithEntities> text);
+
+[[nodiscard]] object_ptr<Ui::BoxContent> StarRefLinkBox(
+	ConnectedBot row,
+	not_null<PeerData*> peer);
+[[nodiscard]] object_ptr<Ui::BoxContent> JoinStarRefBox(
+	ConnectedBot row,
+	not_null<PeerData*> peer);
+
+std::unique_ptr<Ui::AbstractButton> MakePeerBubbleButton(
+	not_null<QWidget*> parent,
+	not_null<PeerData*> peer,
+	Ui::RpWidget *right = nullptr);
+
+[[nodiscard]] ConnectedBots Parse(
+	not_null<Main::Session*> session,
+	const MTPpayments_ConnectedStarRefBots &bots);
 
 } // namespace Info::BotStarRef
diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_join_widget.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_join_widget.cpp
index 10899173e..984e94f09 100644
--- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_join_widget.cpp
+++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_join_widget.cpp
@@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "apiwrap.h"
 #include "base/timer_rpl.h"
 #include "base/unixtime.h"
-#include "boxes/peers/replace_boost_box.h" // CreateUserpicsTransfer.
 #include "boxes/peer_list_box.h"
 #include "core/click_handler_types.h"
 #include "data/data_session.h"
@@ -56,19 +55,6 @@ enum class JoinType {
 	Suggested,
 };
 
-struct ConnectedBotState {
-	StarRefProgram program;
-	QString link;
-	TimeId date = 0;
-	int users = 0;
-	bool revoked = false;
-};
-struct ConnectedBot {
-	not_null<UserData*> bot;
-	ConnectedBotState state;
-};
-using ConnectedBots = std::vector<ConnectedBot>;
-
 class ListController final : public PeerListController {
 public:
 	ListController(
@@ -108,246 +94,6 @@ private:
 
 };
 
-[[nodiscard]] ConnectedBots Parse(
-		not_null<Main::Session*> session,
-		const MTPpayments_ConnectedStarRefBots &bots) {
-	const auto &data = bots.data();
-	session->data().processUsers(data.vusers());
-	const auto &list = data.vconnected_bots().v;
-	auto result = ConnectedBots();
-	for (const auto &bot : list) {
-		const auto &data = bot.data();
-		const auto botId = UserId(data.vbot_id());
-		const auto link = qs(data.vurl());
-		const auto date = data.vdate().v;
-		const auto commission = data.vcommission_permille().v;
-		const auto durationMonths
-			= data.vduration_months().value_or_empty();
-		const auto users = int(data.vparticipants().v);
-		const auto revoked = data.is_revoked();
-		result.push_back({
-			.bot = session->data().user(botId),
-			.state = {
-				.program = {
-					.commission = ushort(commission),
-					.durationMonths = uchar(durationMonths),
-				},
-				.link = link,
-				.date = date,
-				.users = users,
-				.revoked = revoked,
-			},
-		});
-	}
-	return result;
-}
-
-[[nodiscard]] rpl::producer<TextWithEntities> FormatProgramDuration(
-		StarRefProgram program) {
-	return !program.durationMonths
-		? tr::lng_star_ref_one_about_for_forever(Ui::Text::RichLangValue)
-		: (program.durationMonths < 12)
-		? tr::lng_star_ref_one_about_for_months(
-			lt_count,
-			rpl::single(program.durationMonths * 1.),
-			Ui::Text::RichLangValue)
-		: tr::lng_star_ref_one_about_for_years(
-			lt_count,
-			rpl::single((program.durationMonths / 12) * 1.),
-			Ui::Text::RichLangValue);
-}
-
-[[nodiscard]] not_null<Ui::RoundButton*> AddFullWidthButton(
-		not_null<Ui::BoxContent*> box,
-		rpl::producer<QString> text,
-		Fn<void()> callback = nullptr) {
-	const auto &boxSt = box->getDelegate()->style();
-	const auto result = box->addButton(std::move(text), std::move(callback));
-	rpl::combine(
-		box->widthValue(),
-		result->widthValue()
-	) | rpl::start_with_next([=](int width, int buttonWidth) {
-		const auto correct = width
-			- boxSt.buttonPadding.left()
-			- boxSt.buttonPadding.right();
-		if (correct > 0 && buttonWidth != correct) {
-			result->resizeToWidth(correct);
-			result->moveToLeft(
-				boxSt.buttonPadding.left(),
-				boxSt.buttonPadding.top(),
-				width);
-		}
-	}, result->lifetime());
-	return result;
-}
-
-[[nodiscard]] object_ptr<Ui::BoxContent> StarRefLinkBox(
-		ConnectedBot row,
-		not_null<PeerData*> peer) {
-	return Box([=](not_null<Ui::GenericBox*> box) {
-		box->setTitle(tr::lng_star_ref_link_title());
-
-		const auto bot = row.bot;
-		const auto program = row.state.program;
-		auto duration = !program.durationMonths
-			? tr::lng_star_ref_one_about_for_forever(Ui::Text::RichLangValue)
-			: (program.durationMonths < 12)
-			? tr::lng_star_ref_one_about_for_months(
-				lt_count,
-				rpl::single(program.durationMonths * 1.),
-				Ui::Text::RichLangValue)
-			: tr::lng_star_ref_one_about_for_years(
-				lt_count,
-				rpl::single((program.durationMonths / 12) * 1.),
-				Ui::Text::RichLangValue);
-		auto text = tr::lng_star_ref_link_about_channel(
-			lt_amount,
-			rpl::single(Ui::Text::Bold(FormatStarRefCommission(program.commission))),
-			lt_app,
-			rpl::single(Ui::Text::Bold(bot->name())),
-			lt_duration,
-			std::move(duration),
-			Ui::Text::WithEntities);
-		box->addRow(
-			object_ptr<Ui::FlatLabel>(box, std::move(text), st::boxLabel));
-		Ui::AddSkip(box->verticalLayout());
-		box->addRow(
-			object_ptr<Ui::FlatLabel>(
-				box,
-				rpl::single(row.state.link) | Ui::Text::ToLink(),
-				st::boxLabel));
-		Ui::AddSkip(box->verticalLayout());
-		if (row.state.users > 0) {
-			box->addRow(
-				object_ptr<Ui::FlatLabel>(
-					box,
-					tr::lng_star_ref_link_copy_users(
-						lt_count,
-						rpl::single(row.state.users * 1.),
-						lt_app,
-						rpl::single(bot->name())),
-					st::boxLabel));
-		} else {
-			box->addRow(
-				object_ptr<Ui::FlatLabel>(
-					box,
-					tr::lng_star_ref_link_copy_none(
-						lt_app,
-						rpl::single(bot->name())),
-					st::boxLabel));
-		}
-
-		box->addButton(tr::lng_star_ref_link_copy(), [=] {
-			QApplication::clipboard()->setText(row.state.link);
-			box->uiShow()->showToast(tr::lng_username_copied(tr::now));
-		});
-		box->addButton(tr::lng_cancel(), [=] {
-			box->closeBox();
-		});
-	});
-}
-
-[[nodiscard]] object_ptr<Ui::BoxContent> JoinStarRefBox(
-		ConnectedBot row,
-		not_null<PeerData*> peer) {
-	Expects(row.bot->isUser());
-
-	return Box([=](not_null<Ui::GenericBox*> box) {
-		const auto show = box->uiShow();
-
-		const auto bot = row.bot;
-		const auto program = row.state.program;
-
-		box->setStyle(st::starrefFooterBox);
-		box->setNoContentMargin(true);
-		box->addTopButton(st::boxTitleClose, [=] {
-			box->closeBox();
-		});
-
-		box->addRow(
-			CreateUserpicsTransfer(
-				box,
-				rpl::single(std::vector{ not_null<PeerData*>(bot) }),
-				peer,
-				UserpicsTransferType::StarRefJoin),
-			st::boxRowPadding + st::starrefJoinUserpicsPadding);
-		box->addRow(
-			object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
-				box,
-				object_ptr<Ui::FlatLabel>(
-					box,
-					tr::lng_star_ref_title(),
-					st::boxTitle)),
-			st::boxRowPadding + st::starrefJoinTitlePadding);
-		box->addRow(
-			object_ptr<Ui::FlatLabel>(
-				box,
-				tr::lng_star_ref_one_about(
-					lt_app,
-					rpl::single(Ui::Text::Bold(bot->name())),
-					lt_amount,
-					rpl::single(Ui::Text::Bold(
-						FormatStarRefCommission(program.commission))),
-					lt_duration,
-					FormatProgramDuration(program),
-					Ui::Text::WithEntities),
-				st::boxLabel),
-			st::boxRowPadding);
-		struct State {
-			QPointer<Ui::GenericBox> weak;
-			bool sent = false;
-		};
-		const auto state = std::make_shared<State>();
-		state->weak = box;
-
-		const auto send = [=] {
-			if (state->sent) {
-				return;
-			}
-			state->sent = true;
-			bot->session().api().request(MTPpayments_ConnectStarRefBot(
-				peer->input,
-				bot->asUser()->inputUser
-			)).done([=](const MTPpayments_ConnectedStarRefBots &result) {
-				const auto parsed = Parse(&bot->session(), result);
-				if (parsed.empty()) {
-					state->sent = false;
-					show->showToast(u"Failed."_q);
-				} else {
-					show->show(StarRefLinkBox(parsed.front(), peer));
-					if (const auto strong = state->weak.data()) {
-						strong->closeBox();
-					}
-				}
-			}).fail([=](const MTP::Error &error) {
-				state->sent = false;
-				show->showToast(u"Failed: "_q + error.type());
-			}).send();
-		};
-		const auto button = AddFullWidthButton(
-			box,
-			tr::lng_star_ref_one_join(),
-			send);
-		const auto footer = Ui::CreateChild<Ui::FlatLabel>(
-			button->parentWidget(),
-			tr::lng_star_ref_one_join_text(
-				lt_terms,
-				tr::lng_star_ref_button_link(
-				) | Ui::Text::ToLink(tr::lng_star_ref_tos_url(tr::now)),
-				Ui::Text::WithEntities),
-			st::starrefJoinFooter);
-		button->geometryValue() | rpl::start_with_next([=](QRect geometry) {
-			footer->resizeToWidth(geometry.width());
-			const auto &st = box->getDelegate()->style();
-			const auto top = geometry.y() + geometry.height();
-			const auto available = st.buttonPadding.bottom();
-			footer->moveToLeft(
-				geometry.left(),
-				top + (available - footer->height()) / 2);
-		}, footer->lifetime());
-	});
-}
-
 ListController::ListController(
 	not_null<Controller*> controller,
 	not_null<PeerData*> peer,
diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp
index cdf269d79..87e402ffd 100644
--- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp
+++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_setup_widget.cpp
@@ -579,16 +579,15 @@ void InnerWidget::setupEnd() {
 			user->inputUser,
 			MTP_int(0),
 			MTP_int(0)
-		)).done([=] {
-			user->botInfo->starRefProgram.commission = 0;
-			user->botInfo->starRefProgram.durationMonths = 0;
-			user->updateFullForced();
-			if (weak) {
-				_controller->showToast("Removed!");
-				_controller->showBackFromStack();
+		)).done([=](const MTPStarRefProgram &result) {
+			user->setStarRefProgram(Data::ParseStarRefProgram(&result));
+			if (const auto controller = weak ? _controller.get() : nullptr) {
+				const auto window = controller->parentController();
+				controller->showBackFromStack();
+				window->showToast("Removed!");
 			}
-		}).fail(crl::guard(weak, [=] {
-			_controller->showToast("Remove failed!");
+		}).fail(crl::guard(weak, [=](const MTP::Error &error) {
+			_controller->showToast(u"Failed: "_q + error.type());
 		})).send();
 	});
 	Ui::AddSkip(_container);
diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
index a23d3b53f..cf50857b1 100644
--- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
+++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
@@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/stickers/data_stickers.h"
 #include "history/history.h"
 #include "history/history_item.h"
+#include "info/bot/starref/info_bot_starref_common.h" // MakePeerBubbleButton
 #include "info/profile/info_profile_values.h"
 #include "inline_bots/inline_bot_result.h"
 #include "inline_bots/inline_bot_confirm_prepared.h"
@@ -444,74 +445,32 @@ std::unique_ptr<Ui::RpWidget> MakeEmojiSetStatusPreview(
 		not_null<QWidget*> parent,
 		not_null<PeerData*> peer,
 		not_null<DocumentData*> document) {
-	auto result = std::make_unique<Ui::RpWidget>(parent);
-
-	const auto size = st::chatGiveawayPeerSize;
-	const auto padding = st::chatGiveawayPeerPadding;
-
-	const auto raw = result.get();
-
-	const auto width = raw->lifetime().make_state<int>();
-	const auto name = raw->lifetime().make_state<Ui::FlatLabel>(
-		raw,
-		rpl::single(peer->name()),
-		st::botEmojiStatusName);
 	const auto makeContext = [=](Fn<void()> update) {
 		return Core::MarkedTextContext{
 			.session = &peer->session(),
 			.customEmojiRepaint = update,
 		};
 	};
-	const auto emoji = raw->lifetime().make_state<Ui::FlatLabel>(
-		raw,
-		rpl::single(
-			Ui::Text::SingleCustomEmoji(
-				Data::SerializeCustomEmojiId(document->id),
-				document->sticker() ? document->sticker()->alt : QString())),
-		st::botEmojiStatusEmoji,
-		st::defaultPopupMenu,
-		makeContext);
-	const auto userpic = raw->lifetime().make_state<Ui::UserpicButton>(
-		raw,
+	const auto emoji = Ui::CreateChild<Ui::PaddingWrap<Ui::FlatLabel>>(
+		parent,
+		object_ptr<Ui::FlatLabel>(
+			parent,
+			rpl::single(
+				Ui::Text::SingleCustomEmoji(
+					Data::SerializeCustomEmojiId(document->id),
+					(document->sticker()
+						? document->sticker()->alt
+						: QString()))),
+			st::botEmojiStatusEmoji,
+			st::defaultPopupMenu,
+			makeContext),
+		style::margins(st::normalFont->spacew, 0, 0, 0));
+
+	auto result = Info::BotStarRef::MakePeerBubbleButton(
+		parent,
 		peer,
-		st::botEmojiStatusUserpic);
-
-	raw->resize(size, size);
-	raw->sizeValue() | rpl::start_with_next([=](QSize outer) {
-		const auto full = outer.width();
-		const auto decorations = size
-			+ padding.left()
-			+ padding.right()
-			+ emoji->width()
-			+ st::normalFont->spacew;
-		const auto inner = full - decorations;
-		const auto use = std::min(inner, name->textMaxWidth());
-		*width = use + decorations;
-		const auto left = (full - *width) / 2;
-		if (inner > 0) {
-			userpic->moveToLeft(left, 0, outer.width());
-			emoji->moveToLeft(
-				left + *width - padding.right() - emoji->width(),
-				padding.top(),
-				outer.width());
-			name->resizeToWidth(use);
-			name->moveToLeft(
-				left + size + padding.left(),
-				padding.top(),
-				outer.width());
-		}
-	}, raw->lifetime());
-	raw->paintRequest() | rpl::start_with_next([=] {
-		auto p = QPainter(raw);
-		const auto left = (raw->width() - *width) / 2;
-		const auto skip = size / 2;
-		p.setClipRect(left + skip, 0, *width - skip, size);
-		auto hq = PainterHighQualityEnabler(p);
-		p.setPen(Qt::NoPen);
-		p.setBrush(st::windowBgOver);
-		p.drawRoundedRect(left, 0, *width, size, skip, skip);
-	}, raw->lifetime());
-
+		emoji);
+	result->setAttribute(Qt::WA_TransparentForMouseEvents);
 	return result;
 }
 
diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style
index 5946ac357..4141a8d1a 100644
--- a/Telegram/SourceFiles/ui/effects/premium.style
+++ b/Telegram/SourceFiles/ui/effects/premium.style
@@ -81,24 +81,33 @@ starrefCover: PremiumCover(userPremiumCover) {
 	titlePadding: margins(0px, 12px, 0px, 11px);
 }
 starrefCoverHeight: 180px;
+starrefFooterButton: RoundButton(defaultActiveButton) {
+	height: 42px;
+	textTop: 12px;
+	style: semiboldTextStyle;
+}
 starrefFooterBox: Box(defaultBox) {
 	buttonPadding: margins(22px, 11px, 22px, 54px);
 	buttonHeight: 42px;
-	button: RoundButton(defaultActiveButton) {
-		height: 42px;
-		textTop: 12px;
-		style: semiboldTextStyle;
-	}
+	button: starrefFooterButton;
 	shadowIgnoreTopSkip: true;
 }
+starrefCopyButton: RoundButton(starrefFooterButton) {
+	icon: icon {{ "info/edit/links_copy", activeButtonFg }};
+	iconOver: icon {{ "info/edit/links_copy", activeButtonFgOver }};
+	iconPosition: point(-1px, 5px);
+}
+
 starrefJoinIcon: icon{{ "payments/small_star", premiumButtonFg }};
 starrefJoinUserpicsPadding: margins(0px, 32px, 0px, 10px);
 starrefJoinTitlePadding: margins(0px, 0px, 0px, 12px);
-starrefJoinFooter: FlatLabel(defaultFlatLabel) {
-	textFg: windowSubTextFg;
+starrefCenteredText: FlatLabel(defaultFlatLabel) {
 	align: align(top);
 	minWidth: 240px;
 }
+starrefJoinFooter: FlatLabel(starrefCenteredText) {
+	textFg: windowSubTextFg;
+}
 
 defaultPremiumBoxLabel: FlatLabel(defaultFlatLabel) {
 	minWidth: 220px;