diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift
index 604caf29b5..21fb5ed1f5 100644
--- a/submodules/AccountContext/Sources/AccountContext.swift
+++ b/submodules/AccountContext/Sources/AccountContext.swift
@@ -1039,6 +1039,10 @@ public protocol SharedAccountContext: AnyObject {
     func makeMessagesStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, messageId: EngineMessage.Id) -> ViewController
     func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController
     
+    func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController
+    func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [StarsTopUpOption], peerId: EnginePeer.Id?, requiredStars: Int32?) -> ViewController
+    func makeStarsTransferScreen(context: AccountContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>) -> ViewController
+
     func makeDebugSettingsController(context: AccountContext?) -> ViewController?
     
     func navigateToCurrentCall()
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift
index 3e0c29edef..04b7ea9619 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift
@@ -13,9 +13,9 @@ public final class BotCheckoutController: ViewController {
             case generic
         }
 
-        let form: BotPaymentForm
-        let validatedFormInfo: BotPaymentValidatedFormInfo?
-        let botPeer: EnginePeer?
+        public let form: BotPaymentForm
+        public let validatedFormInfo: BotPaymentValidatedFormInfo?
+        public let botPeer: EnginePeer?
 
         private init(
             form: BotPaymentForm,
diff --git a/submodules/ConfettiEffect/Sources/ConfettiView.swift b/submodules/ConfettiEffect/Sources/ConfettiView.swift
index fb600452bc..961b1d833f 100644
--- a/submodules/ConfettiEffect/Sources/ConfettiView.swift
+++ b/submodules/ConfettiEffect/Sources/ConfettiView.swift
@@ -43,7 +43,7 @@ public final class ConfettiView: UIView {
     
     private var localTime: Float = 0.0
     
-    override public init(frame: CGRect) {
+    public init(frame: CGRect, customImage: UIImage? = nil) {
         super.init(frame: frame)
         
         self.isUserInteractionEnabled = false
@@ -56,19 +56,25 @@ public final class ConfettiView: UIView {
         ] as [UInt32]).map(UIColor.init(rgb:))
         let imageSize = CGSize(width: 8.0, height: 8.0)
         var images: [(CGImage, CGSize)] = []
-        for imageType in 0 ..< 2 {
+        if let customImage {
             for color in colors {
-                if imageType == 0 {
-                    images.append((generateFilledCircleImage(diameter: imageSize.width, color: color)!.cgImage!, imageSize))
-                } else {
-                    let spriteSize = CGSize(width: 2.0, height: 6.0)
-                    images.append((generateImage(spriteSize, opaque: false, rotatedContext: { size, context in
-                        context.clear(CGRect(origin: CGPoint(), size: size))
-                        context.setFillColor(color.cgColor)
-                        context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.width)))
-                        context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width)))
-                        context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.width / 2.0), size: CGSize(width: size.width, height: size.height - size.width)))
-                    })!.cgImage!, spriteSize))
+                images.append((generateTintedImage(image: customImage, color: color)!.cgImage!, customImage.size))
+            }
+        } else {
+            for imageType in 0 ..< 2 {
+                for color in colors {
+                    if imageType == 0 {
+                        images.append((generateFilledCircleImage(diameter: imageSize.width, color: color)!.cgImage!, imageSize))
+                    } else {
+                        let spriteSize = CGSize(width: 2.0, height: 6.0)
+                        images.append((generateImage(spriteSize, opaque: false, rotatedContext: { size, context in
+                            context.clear(CGRect(origin: CGPoint(), size: size))
+                            context.setFillColor(color.cgColor)
+                            context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.width)))
+                            context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width)))
+                            context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.width / 2.0), size: CGSize(width: size.width, height: size.height - size.width)))
+                        })!.cgImage!, spriteSize))
+                    }
                 }
             }
         }
diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift
index bc7206ceeb..39e93e1263 100644
--- a/submodules/Display/Source/GenerateImage.swift
+++ b/submodules/Display/Source/GenerateImage.swift
@@ -369,6 +369,9 @@ public func generateGradientTintedImage(image: UIImage?, colors: [UIColor], dire
             case .diagonal:
                 start = CGPoint(x: 0.0, y: 0.0)
                 end = CGPoint(x: imageRect.width, y: imageRect.height)
+            case .mirroredDiagonal:
+                start = CGPoint(x: imageRect.width, y: 0.0)
+                end = CGPoint(x: 0.0, y: imageRect.height)
             }
             
             context.drawLinearGradient(gradient, start: start, end: end, options: CGGradientDrawingOptions())
@@ -390,6 +393,7 @@ public enum GradientImageDirection {
     case vertical
     case horizontal
     case diagonal
+    case mirroredDiagonal
 }
 
 public func generateGradientImage(size: CGSize, scale: CGFloat = 0.0, colors: [UIColor], locations: [CGFloat], direction: GradientImageDirection = .vertical) -> UIImage? {
@@ -440,6 +444,9 @@ public func generateGradientFilledCircleImage(diameter: CGFloat, colors: NSArray
         case .diagonal:
             start = CGPoint(x: 0.0, y: 0.0)
             end = CGPoint(x: size.width, y: size.height)
+        case .mirroredDiagonal:
+            start = CGPoint(x: size.width, y: 0.0)
+            end = CGPoint(x: 0.0, y: size.height)
         }
         
         context.drawLinearGradient(gradient, start: start, end:end, options: CGGradientDrawingOptions())
diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD
index 15fcbb0807..9cad24de43 100644
--- a/submodules/PremiumUI/BUILD
+++ b/submodules/PremiumUI/BUILD
@@ -119,6 +119,7 @@ swift_library(
         "//submodules/TelegramUI/Components/PremiumPeerShortcutComponent",
         "//submodules/TelegramUI/Components/EmojiActionIconComponent",
         "//submodules/TelegramUI/Components/ScrollComponent",
+        "//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
     ],
     visibility = [
         "//visibility:public",
diff --git a/submodules/PremiumUI/Resources/star.scn b/submodules/PremiumUI/Resources/star.scn
new file mode 100644
index 0000000000..c336915b82
Binary files /dev/null and b/submodules/PremiumUI/Resources/star.scn differ
diff --git a/submodules/PremiumUI/Sources/BadgeBusinessView.swift b/submodules/PremiumUI/Sources/BadgeBusinessView.swift
index 1127c15366..a98d1b6567 100644
--- a/submodules/PremiumUI/Sources/BadgeBusinessView.swift
+++ b/submodules/PremiumUI/Sources/BadgeBusinessView.swift
@@ -3,6 +3,7 @@ import UIKit
 import SceneKit
 import Display
 import AppBundle
+import PremiumStarComponent
 
 private let sceneVersion: Int = 1
 
diff --git a/submodules/PremiumUI/Sources/BadgeStarsView.swift b/submodules/PremiumUI/Sources/BadgeStarsView.swift
index f1374916e5..d5b770ec20 100644
--- a/submodules/PremiumUI/Sources/BadgeStarsView.swift
+++ b/submodules/PremiumUI/Sources/BadgeStarsView.swift
@@ -3,6 +3,7 @@ import UIKit
 import SceneKit
 import Display
 import AppBundle
+import PremiumStarComponent
 
 final class BadgeStarsView: UIView, PhoneDemoDecorationView {
     private let sceneView: SCNView
diff --git a/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift b/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift
index fc1216ae68..566228e5e4 100644
--- a/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift
+++ b/submodules/PremiumUI/Sources/BoostHeaderBackgroundComponent.swift
@@ -11,6 +11,7 @@ import AvatarNode
 import TelegramCore
 import MultilineTextComponent
 import TelegramPresentationData
+import PremiumStarComponent
 
 private let sceneVersion: Int = 1
 
diff --git a/submodules/PremiumUI/Sources/BusinessPageComponent.swift b/submodules/PremiumUI/Sources/BusinessPageComponent.swift
index f5fe82ec6f..fba7895323 100644
--- a/submodules/PremiumUI/Sources/BusinessPageComponent.swift
+++ b/submodules/PremiumUI/Sources/BusinessPageComponent.swift
@@ -498,7 +498,7 @@ final class BusinessPageComponent: CombinedComponent {
         let updateDismissOffset: (CGFloat) -> Void
         let updatedIsDisplaying: (Bool) -> Void
         
-        var resetScroll: ActionSlot<Void>?
+        var resetScroll: ActionSlot<CGPoint?>?
         
         var topContentOffset: CGFloat = 0.0
         var bottomContentOffset: CGFloat = 100.0 {
@@ -519,7 +519,7 @@ final class BusinessPageComponent: CombinedComponent {
                     self.updatedIsDisplaying(self.isDisplaying)
                     
                     if !self.isDisplaying {
-                        self.resetScroll?.invoke(Void())
+                        self.resetScroll?.invoke(nil)
                     }
                 }
             }
@@ -566,7 +566,7 @@ final class BusinessPageComponent: CombinedComponent {
         let topSeparator = Child(Rectangle.self)
         let title = Child(MultilineTextComponent.self)
         
-        let resetScroll = ActionSlot<Void>()
+        let resetScroll = ActionSlot<CGPoint?>()
         
         return { context in
             let state = context.state
diff --git a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift
index daeb42a2e0..25a3269efc 100644
--- a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift
+++ b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift
@@ -8,6 +8,7 @@ import ItemListUI
 import PresentationDataUtils
 import Markdown
 import ComponentFlow
+import PremiumStarComponent
 
 final class CreateGiveawayHeaderItem: ItemListControllerHeaderItem {
     let theme: PresentationTheme
diff --git a/submodules/PremiumUI/Sources/FasterStarsView.swift b/submodules/PremiumUI/Sources/FasterStarsView.swift
index 967fc2213d..baddaba3d8 100644
--- a/submodules/PremiumUI/Sources/FasterStarsView.swift
+++ b/submodules/PremiumUI/Sources/FasterStarsView.swift
@@ -4,6 +4,7 @@ import SceneKit
 import Display
 import AppBundle
 import LegacyComponents
+import PremiumStarComponent
 
 private let sceneVersion: Int = 1
 
diff --git a/submodules/PremiumUI/Sources/LimitsPageComponent.swift b/submodules/PremiumUI/Sources/LimitsPageComponent.swift
index c5ea6cf0ad..c497b6ad57 100644
--- a/submodules/PremiumUI/Sources/LimitsPageComponent.swift
+++ b/submodules/PremiumUI/Sources/LimitsPageComponent.swift
@@ -453,7 +453,7 @@ final class LimitsPageComponent: CombinedComponent {
         let updateDismissOffset: (CGFloat) -> Void
         let updatedIsDisplaying: (Bool) -> Void
         
-        var resetScroll: ActionSlot<Void>?
+        var resetScroll: ActionSlot<CGPoint?>?
         
         var topContentOffset: CGFloat = 0.0
         var bottomContentOffset: CGFloat = 100.0 {
@@ -474,7 +474,7 @@ final class LimitsPageComponent: CombinedComponent {
                     self.updatedIsDisplaying(self.isDisplaying)
                     
                     if !self.isDisplaying {
-                        self.resetScroll?.invoke(Void())
+                        self.resetScroll?.invoke(nil)
                     }
                 }
             }
@@ -521,7 +521,7 @@ final class LimitsPageComponent: CombinedComponent {
         let topSeparator = Child(Rectangle.self)
         let title = Child(MultilineTextComponent.self)
         
-        let resetScroll = ActionSlot<Void>()
+        let resetScroll = ActionSlot<CGPoint?>()
         
         return { context in
             let state = context.state
diff --git a/submodules/PremiumUI/Sources/PremiumCoinComponent.swift b/submodules/PremiumUI/Sources/PremiumCoinComponent.swift
index ca8926ab1f..851bdd656a 100644
--- a/submodules/PremiumUI/Sources/PremiumCoinComponent.swift
+++ b/submodules/PremiumUI/Sources/PremiumCoinComponent.swift
@@ -7,6 +7,7 @@ import SceneKit
 import GZip
 import AppBundle
 import LegacyComponents
+import PremiumStarComponent
 
 private let sceneVersion: Int = 3
 
diff --git a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift
index a6503bac26..fa58f2b3f5 100644
--- a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift
@@ -21,6 +21,7 @@ import TextFormat
 import TelegramStringFormatting
 import UndoUI
 import InvisibleInkDustNode
+import PremiumStarComponent
 
 private final class PremiumGiftCodeSheetContent: CombinedComponent {
     typealias EnvironmentType = ViewControllerComponentContainer.Environment
diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
index a1756cc768..e82f316816 100644
--- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
@@ -21,6 +21,7 @@ import TextFormat
 import UniversalMediaPlayer
 import InstantPageCache
 import ScrollComponent
+import PremiumStarComponent
 
 extension PremiumGiftSource {
     var identifier: String? {
diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift
index 521ad43d0a..8871eff5c1 100644
--- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift
@@ -13,7 +13,6 @@ import SolidRoundedButtonComponent
 import MultilineTextComponent
 import MultilineTextWithEntitiesComponent
 import BundleIconComponent
-import SolidRoundedButtonComponent
 import BlurredBackgroundComponent
 import Markdown
 import InAppPurchaseManager
@@ -34,6 +33,7 @@ import EmojiStatusComponent
 import EntityKeyboard
 import EmojiActionIconComponent
 import ScrollComponent
+import PremiumStarComponent
 
 public enum PremiumSource: Equatable {
     public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool {
diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift
index 106d512df0..6b37edbcfb 100644
--- a/submodules/PremiumUI/Sources/StoriesPageComponent.swift
+++ b/submodules/PremiumUI/Sources/StoriesPageComponent.swift
@@ -516,7 +516,7 @@ final class StoriesPageComponent: CombinedComponent {
         let updateDismissOffset: (CGFloat) -> Void
         let updatedIsDisplaying: (Bool) -> Void
         
-        var resetScroll: ActionSlot<Void>?
+        var resetScroll: ActionSlot<CGPoint?>?
         
         var topContentOffset: CGFloat = 0.0
         var bottomContentOffset: CGFloat = 100.0 {
@@ -537,7 +537,7 @@ final class StoriesPageComponent: CombinedComponent {
                     self.updatedIsDisplaying(self.isDisplaying)
                     
                     if !self.isDisplaying {
-                        self.resetScroll?.invoke(Void())
+                        self.resetScroll?.invoke(nil)
                     }
                 }
             }
@@ -584,7 +584,7 @@ final class StoriesPageComponent: CombinedComponent {
         let topSeparator = Child(Rectangle.self)
         let title = Child(MultilineTextComponent.self)
         
-        let resetScroll = ActionSlot<Void>()
+        let resetScroll = ActionSlot<CGPoint?>()
         
         return { context in
             let state = context.state
diff --git a/submodules/PremiumUI/Sources/SwirlStarsView.swift b/submodules/PremiumUI/Sources/SwirlStarsView.swift
index 8c3ce911b0..d4cddb34f9 100644
--- a/submodules/PremiumUI/Sources/SwirlStarsView.swift
+++ b/submodules/PremiumUI/Sources/SwirlStarsView.swift
@@ -4,6 +4,7 @@ import SceneKit
 import Display
 import AppBundle
 import SwiftSignalKit
+import PremiumStarComponent
 
 private let sceneVersion: Int = 1
 
diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift
index 3b9ff80950..f710657cce 100644
--- a/submodules/SearchBarNode/Sources/SearchBarNode.swift
+++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift
@@ -16,6 +16,10 @@ private func generateLoupeIcon(color: UIColor) -> UIImage? {
     return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: color)
 }
 
+private func generateHashtagIcon(color: UIColor) -> UIImage? {
+    return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Hashtag"), color: color)
+}
+
 private func generateClearIcon(color: UIColor) -> UIImage? {
     return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color)
 }
@@ -844,6 +848,12 @@ public enum SearchBarStyle {
 }
 
 public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
+    public enum Icon {
+        case loupe
+        case hashtag
+    }
+    public let icon: Icon
+    
     public var cancel: (() -> Void)?
     public var textUpdated: ((String, String?) -> Void)?
     public var textReturned: ((String) -> Void)?
@@ -947,10 +957,11 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
     private var strings: PresentationStrings?
     private let cancelText: String?
     
-    public init(theme: SearchBarNodeTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, forceSeparator: Bool = false, displayBackground: Bool = true, cancelText: String? = nil) {
+    public init(theme: SearchBarNodeTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, icon: Icon = .loupe, forceSeparator: Bool = false, displayBackground: Bool = true, cancelText: String? = nil) {
         self.fieldStyle = fieldStyle
         self.forceSeparator = forceSeparator
         self.cancelText = cancelText
+        self.icon = icon
         
         self.backgroundNode = NavigationBackgroundNode(color: theme.background)
         self.backgroundNode.isUserInteractionEnabled = false
@@ -1036,7 +1047,14 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
             self.textBackgroundNode.backgroundColor = theme.inputFill
             self.textField.textColor = theme.primaryText
             self.clearButton.setImage(generateClearIcon(color: theme.inputClear), for: [])
-            self.iconNode.image = generateLoupeIcon(color: theme.inputIcon)
+            let icon: UIImage?
+            switch self.icon {
+            case .loupe:
+                icon = generateLoupeIcon(color: theme.inputIcon)
+            case .hashtag:
+                icon = generateHashtagIcon(color: theme.inputIcon)
+            }
+            self.iconNode.image = icon
             self.textField.keyboardAppearance = theme.keyboard.keyboardAppearance
             self.textField.tintColor = theme.accent
             
diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift
index 773bf310f3..7bfe51dc5c 100644
--- a/submodules/TelegramApi/Sources/Api0.swift
+++ b/submodules/TelegramApi/Sources/Api0.swift
@@ -280,6 +280,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
     dict[1103040667] = { return Api.ExportedContactToken.parse_exportedContactToken($0) }
     dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) }
     dict[1070138683] = { return Api.ExportedStoryLink.parse_exportedStoryLink($0) }
+    dict[-1197736753] = { return Api.FactCheck.parse_factCheck($0) }
     dict[-207944868] = { return Api.FileHash.parse_fileHash($0) }
     dict[-11252123] = { return Api.Folder.parse_folder($0) }
     dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
@@ -515,7 +516,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
     dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) }
     dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) }
     dict[64088654] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
-    dict[-1109353426] = { return Api.Message.parse_message($0) }
+    dict[-1808510398] = { return Api.Message.parse_message($0) }
     dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
     dict[721967202] = { return Api.Message.parse_messageService($0) }
     dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) }
@@ -1604,6 +1605,8 @@ public extension Api {
                 _1.serialize(buffer, boxed)
             case let _1 as Api.ExportedStoryLink:
                 _1.serialize(buffer, boxed)
+            case let _1 as Api.FactCheck:
+                _1.serialize(buffer, boxed)
             case let _1 as Api.FileHash:
                 _1.serialize(buffer, boxed)
             case let _1 as Api.Folder:
diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift
index dd46a44d24..f9b2095a7c 100644
--- a/submodules/TelegramApi/Sources/Api14.swift
+++ b/submodules/TelegramApi/Sources/Api14.swift
@@ -316,15 +316,15 @@ public extension Api {
 }
 public extension Api {
     indirect enum Message: TypeConstructorDescription {
-        case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?)
+        case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?)
         case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
         case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, ttlPeriod: Int32?)
     
     public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
     switch self {
-                case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect):
+                case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck):
                     if boxed {
-                        buffer.appendInt32(-1109353426)
+                        buffer.appendInt32(-1808510398)
                     }
                     serializeInt32(flags, buffer: buffer, boxed: false)
                     serializeInt32(flags2, buffer: buffer, boxed: false)
@@ -361,6 +361,7 @@ public extension Api {
                     if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)}
                     if Int(flags) & Int(1 << 30) != 0 {serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false)}
                     if Int(flags2) & Int(1 << 2) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)}
+                    if Int(flags2) & Int(1 << 3) != 0 {factcheck!.serialize(buffer, true)}
                     break
                 case .messageEmpty(let flags, let id, let peerId):
                     if boxed {
@@ -388,8 +389,8 @@ public extension Api {
     
     public func descriptionFields() -> (String, [(String, Any)]) {
         switch self {
-                case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect):
-                return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any)])
+                case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck):
+                return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any)])
                 case .messageEmpty(let flags, let id, let peerId):
                 return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)])
                 case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod):
@@ -474,6 +475,10 @@ public extension Api {
             if Int(_1!) & Int(1 << 30) != 0 {_26 = reader.readInt32() }
             var _27: Int64?
             if Int(_2!) & Int(1 << 2) != 0 {_27 = reader.readInt64() }
+            var _28: Api.FactCheck?
+            if Int(_2!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
+                _28 = Api.parse(reader, signature: signature) as? Api.FactCheck
+            } }
             let _c1 = _1 != nil
             let _c2 = _2 != nil
             let _c3 = _3 != nil
@@ -501,8 +506,9 @@ public extension Api {
             let _c25 = (Int(_1!) & Int(1 << 25) == 0) || _25 != nil
             let _c26 = (Int(_1!) & Int(1 << 30) == 0) || _26 != nil
             let _c27 = (Int(_2!) & Int(1 << 2) == 0) || _27 != nil
-            if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 {
-                return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27)
+            let _c28 = (Int(_2!) & Int(1 << 3) == 0) || _28 != nil
+            if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 {
+                return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28)
             }
             else {
                 return nil
diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift
index 8e0cc2000b..b6cf49ba95 100644
--- a/submodules/TelegramApi/Sources/Api36.swift
+++ b/submodules/TelegramApi/Sources/Api36.swift
@@ -4952,6 +4952,22 @@ public extension Api.functions.messages {
                     })
                 }
 }
+public extension Api.functions.messages {
+                static func deleteFactCheck(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
+                    let buffer = Buffer()
+                    buffer.appendInt32(-774204404)
+                    peer.serialize(buffer, true)
+                    serializeInt32(msgId, buffer: buffer, boxed: false)
+                    return (FunctionDescription(name: "messages.deleteFactCheck", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
+                        let reader = BufferReader(buffer)
+                        var result: Api.Updates?
+                        if let signature = reader.readInt32() {
+                            result = Api.parse(reader, signature: signature) as? Api.Updates
+                        }
+                        return result
+                    })
+                }
+}
 public extension Api.functions.messages {
                 static func deleteHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
                     let buffer = Buffer()
@@ -5214,6 +5230,23 @@ public extension Api.functions.messages {
                     })
                 }
 }
+public extension Api.functions.messages {
+                static func editFactCheck(peer: Api.InputPeer, msgId: Int32, text: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
+                    let buffer = Buffer()
+                    buffer.appendInt32(92925557)
+                    peer.serialize(buffer, true)
+                    serializeInt32(msgId, buffer: buffer, boxed: false)
+                    text.serialize(buffer, true)
+                    return (FunctionDescription(name: "messages.editFactCheck", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("text", String(describing: text))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
+                        let reader = BufferReader(buffer)
+                        var result: Api.Updates?
+                        if let signature = reader.readInt32() {
+                            result = Api.parse(reader, signature: signature) as? Api.Updates
+                        }
+                        return result
+                    })
+                }
+}
 public extension Api.functions.messages {
                 static func editInlineBotMessage(flags: Int32, id: Api.InputBotInlineMessageID, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
                     let buffer = Buffer()
@@ -5921,6 +5954,26 @@ public extension Api.functions.messages {
                     })
                 }
 }
+public extension Api.functions.messages {
+                static func getFactCheck(peer: Api.InputPeer, msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FactCheck]>) {
+                    let buffer = Buffer()
+                    buffer.appendInt32(-1177696786)
+                    peer.serialize(buffer, true)
+                    buffer.appendInt32(481674261)
+                    buffer.appendInt32(Int32(msgId.count))
+                    for item in msgId {
+                        serializeInt32(item, buffer: buffer, boxed: false)
+                    }
+                    return (FunctionDescription(name: "messages.getFactCheck", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FactCheck]? in
+                        let reader = BufferReader(buffer)
+                        var result: [Api.FactCheck]?
+                        if let _ = reader.readInt32() {
+                            result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FactCheck.self)
+                        }
+                        return result
+                    })
+                }
+}
 public extension Api.functions.messages {
                 static func getFavedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.FavedStickers>) {
                     let buffer = Buffer()
diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift
index 1b56cb7fbb..af25d1b035 100644
--- a/submodules/TelegramApi/Sources/Api7.swift
+++ b/submodules/TelegramApi/Sources/Api7.swift
@@ -168,6 +168,56 @@ public extension Api {
     
     }
 }
+public extension Api {
+    enum FactCheck: TypeConstructorDescription {
+        case factCheck(flags: Int32, country: String?, text: Api.TextWithEntities?, hash: Int64)
+    
+    public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
+    switch self {
+                case .factCheck(let flags, let country, let text, let hash):
+                    if boxed {
+                        buffer.appendInt32(-1197736753)
+                    }
+                    serializeInt32(flags, buffer: buffer, boxed: false)
+                    if Int(flags) & Int(1 << 1) != 0 {serializeString(country!, buffer: buffer, boxed: false)}
+                    if Int(flags) & Int(1 << 1) != 0 {text!.serialize(buffer, true)}
+                    serializeInt64(hash, buffer: buffer, boxed: false)
+                    break
+    }
+    }
+    
+    public func descriptionFields() -> (String, [(String, Any)]) {
+        switch self {
+                case .factCheck(let flags, let country, let text, let hash):
+                return ("factCheck", [("flags", flags as Any), ("country", country as Any), ("text", text as Any), ("hash", hash as Any)])
+    }
+    }
+    
+        public static func parse_factCheck(_ reader: BufferReader) -> FactCheck? {
+            var _1: Int32?
+            _1 = reader.readInt32()
+            var _2: String?
+            if Int(_1!) & Int(1 << 1) != 0 {_2 = parseString(reader) }
+            var _3: Api.TextWithEntities?
+            if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
+                _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
+            } }
+            var _4: Int64?
+            _4 = reader.readInt64()
+            let _c1 = _1 != nil
+            let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
+            let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
+            let _c4 = _4 != nil
+            if _c1 && _c2 && _c3 && _c4 {
+                return Api.FactCheck.factCheck(flags: _1!, country: _2, text: _3, hash: _4!)
+            }
+            else {
+                return nil
+            }
+        }
+    
+    }
+}
 public extension Api {
     enum FileHash: TypeConstructorDescription {
         case fileHash(offset: Int64, limit: Int32, hash: Buffer)
diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift
index 1a8628c783..f5f24cff0b 100644
--- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift
+++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift
@@ -126,7 +126,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
 
 func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
     switch messsage {
-        case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
+        case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
             let chatPeerId = messagePeerId
             return chatPeerId.peerId
         case let .messageEmpty(_, _, peerId):
@@ -142,7 +142,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
 
 func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
     switch message {
-        case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _):
+        case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _):
             let peerId: PeerId = chatPeerId.peerId
             
             var result = [peerId]
@@ -266,7 +266,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
 
 func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? {
     switch message {
-        case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
+        case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
             if let replyTo = replyTo {
                 let peerId: PeerId = chatPeerId.peerId
                 
@@ -609,7 +609,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
 extension StoreMessage {
     convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
         switch apiMessage {
-            case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId):
+            case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, _):
                 let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
             
                 var namespace = namespace
diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift
index d30668fb6a..d53abc3f95 100644
--- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift
+++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift
@@ -96,7 +96,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
         var updatedTimestamp: Int32?
         if let apiMessage = apiMessage {
             switch apiMessage {
-                case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
+                case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
                     updatedTimestamp = date
                 case .messageEmpty:
                     break
diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift
index 09276f544e..9ece0b2395 100644
--- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift
+++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift
@@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService {
                     self.putNext(groups)
                 }
             case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
-                let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil)
+                let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil)
                 let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
                 let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
                 if groups.count != 0 {
@@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService {
                 
                 let generatedPeerId = Api.Peer.peerUser(userId: userId)
                 
-                let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil)
+                let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil)
                 let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
                 let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
                 if groups.count != 0 {
diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift
index 1cb21c63b5..f938a9ea43 100644
--- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift
+++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift
@@ -104,7 +104,7 @@ extension Api.MessageMedia {
 extension Api.Message {
     var rawId: Int32 {
         switch self {
-        case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
+        case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
                 return id
             case let .messageEmpty(_, id, _):
                 return id
@@ -115,7 +115,7 @@ extension Api.Message {
     
     func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? {
         switch self {
-            case let .message(_, _, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
+            case let .message(_, _, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
                 let peerId: PeerId = messagePeerId.peerId
                 return MessageId(peerId: peerId, namespace: namespace, id: id)
             case let .messageEmpty(_, id, peerId):
@@ -132,7 +132,7 @@ extension Api.Message {
     
     var peerId: PeerId? {
         switch self {
-        case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
+        case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
             let peerId: PeerId = messagePeerId.peerId
             return peerId
         case let .messageEmpty(_, _, peerId):
@@ -145,7 +145,7 @@ extension Api.Message {
 
     var timestamp: Int32? {
         switch self {
-            case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
+            case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
                 return date
             case let .messageService(_, _, _, _, _, date, _, _):
                 return date
@@ -156,7 +156,7 @@ extension Api.Message {
     
     var preCachedResources: [(MediaResource, Data)]? {
         switch self {
-        case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _):
+        case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
             return media?.preCachedResources
         default:
             return nil
@@ -165,7 +165,7 @@ extension Api.Message {
     
     var preCachedStories: [StoryId: Api.StoryItem]? {
         switch self {
-        case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _):
+        case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
             return media?.preCachedStories
         default:
             return nil
diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift
index 0f654a2a25..20722903e4 100644
--- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift
+++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift
@@ -66,13 +66,13 @@ func _internal_starsTopUpOptions(account: Account) -> Signal<[StarsTopUpOption],
     }
 }
 
-private struct InternalStarsStatus {
+struct InternalStarsStatus {
     let balance: Int64
     let transactions: [StarsContext.State.Transaction]
     let nextOffset: String?
 }
 
-private func requestStarsState(account: Account, peerId: EnginePeer.Id, offset: String?) -> Signal<InternalStarsStatus?, NoError> {
+func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, offset: String?) -> Signal<InternalStarsStatus?, NoError> {
     return account.postbox.transaction { transaction -> Peer? in
         return transaction.getPeer(peerId)
     } |> mapToSignal { peer -> Signal<InternalStarsStatus?, NoError> in
@@ -152,6 +152,7 @@ private final class StarsContextImpl {
                 return
             }
             self._state = StarsContext.State(balance: balance, transactions: state.transactions)
+            self.load()
         })
     }
     
@@ -164,12 +165,14 @@ private final class StarsContextImpl {
     func load() {
         assert(Queue.mainQueue().isCurrent())
         
-        self.disposable.set((requestStarsState(account: self.account, peerId: self.peerId, offset: nil)
+        self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, offset: nil)
         |> deliverOnMainQueue).start(next: { [weak self] status in
             if let self {
                 if let status {
                     self._state = StarsContext.State(balance: status.balance, transactions: status.transactions)
                     self.nextOffset = status.nextOffset
+                    
+                    self.loadMore()
                 } else {
                     self._state = nil
                 }
@@ -183,7 +186,7 @@ private final class StarsContextImpl {
         guard let currentState = self._state, let nextOffset = self.nextOffset else {
             return
         }
-        self.disposable.set((requestStarsState(account: self.account, peerId: self.peerId, offset: nextOffset)
+        self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, offset: nextOffset)
         |> deliverOnMainQueue).start(next: { [weak self] status in
             if let self {
                 if let status {
diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift
index 2d68420866..0ab23a9c12 100644
--- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift
+++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift
@@ -74,6 +74,16 @@ public extension TelegramEngine {
             return StarsContext(account: self.account, peerId: peerId)
         }
         
+        public func peerStarsState(peerId: EnginePeer.Id) -> Signal<StarsContext.State?, NoError> {
+            return _internal_requestStarsState(account: self.account, peerId: peerId, offset: nil)
+            |> map { state in
+                guard let state else {
+                    return nil
+                }
+                return StarsContext.State(balance: state.balance, transactions: state.transactions)
+            }
+        }
+        
         public func sendStarsPaymentForm(formId: Int64, source: BotPaymentInvoiceSource) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
             return _internal_sendStarsPaymentForm(account: self.account, formId: formId, source: source)
         }
diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift
index ffc3d978d2..f53cbaa8bf 100644
--- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift
+++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift
@@ -375,6 +375,10 @@ public extension Message {
             return false
         }
     }
+    
+    func isAgeRestricted() -> Bool {
+        return false
+    }
 }
 
 public extension Message {
diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift
index 982808f637..70ae45f085 100644
--- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift
+++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift
@@ -109,6 +109,30 @@ public struct PresentationResourcesSettings {
         
         drawBorder(context: context, rect: bounds)
     })
+    
+    public static let stars = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
+        let bounds = CGRect(origin: CGPoint(), size: size)
+        context.clear(bounds)
+        
+        let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0)
+        context.addPath(path.cgPath)
+        context.clip()
+        
+        let colorsArray: [CGColor] = [
+            UIColor(rgb: 0xfec80f).cgColor,
+            UIColor(rgb: 0xdd6f12).cgColor
+        ]
+        var locations: [CGFloat] = [0.0, 1.0]
+        let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
+
+        context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
+        
+        if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/ButtonIcon"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
+            context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size))
+        }
+        
+        drawBorder(context: context, rect: bounds)
+    })
 
     public static let passport = renderIcon(name: "Settings/Menu/Passport")
     public static let watch = renderIcon(name: "Settings/Menu/Watch")
diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift
index a9d665563c..a279c40ad6 100644
--- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift
+++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift
@@ -547,7 +547,18 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
                 
                 range = (mutableString.string as NSString).range(of: "{amount}")
                 if range.location != NSNotFound {
-                    mutableString.replaceCharacters(in: range, with: NSAttributedString(string: formatCurrencyAmount(totalAmount, currency: currency), font: titleBoldFont, textColor: primaryTextColor))
+                    if currency == "XTR" {
+                        let amountAttributedString = NSMutableAttributedString(string: "  >  \(totalAmount)", font: titleBoldFont, textColor: primaryTextColor)
+                        if let range = amountAttributedString.string.range(of: ">"), let starImage = generateScaledImage(image: UIImage(bundleImageName: "Premium/Stars/Star"), size: CGSize(width: 16.0, height: 16.0), opaque: false)?.withRenderingMode(.alwaysTemplate) {
+                            amountAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: amountAttributedString.string))
+                            amountAttributedString.addAttribute(.foregroundColor, value: primaryTextColor, range: NSRange(range, in: amountAttributedString.string))
+                            amountAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: amountAttributedString.string))
+                        }
+                        
+                        mutableString.replaceCharacters(in: range, with: amountAttributedString)
+                    } else {
+                        mutableString.replaceCharacters(in: range, with: NSAttributedString(string: formatCurrencyAmount(totalAmount, currency: currency), font: titleBoldFont, textColor: primaryTextColor))
+                    }
                 }
                 range = (mutableString.string as NSString).range(of: "{name}")
                 if range.location != NSNotFound {
diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD
index 11d1da5c76..27a207b85f 100644
--- a/submodules/TelegramUI/BUILD
+++ b/submodules/TelegramUI/BUILD
@@ -448,6 +448,9 @@ swift_library(
         "//submodules/TelegramUI/Components/Settings/BotSettingsScreen",
         "//submodules/TelegramUI/Components/AdminUserActionsSheet",
         "//submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview",
+        "//submodules/TelegramUI/Components/Stars/StarsTransactionsScreen",
+        "//submodules/TelegramUI/Components/Stars/StarsPurchaseScreen",
+        "//submodules/TelegramUI/Components/Stars/StarsTransferScreen",
     ] + select({
         "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
         "//build-system:ios_sim_arm64": [],
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift
index af49b7cd3c..1b24ac7b2c 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift
@@ -191,7 +191,11 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
                 case .switchInline:
                     iconImage = incoming ? graphics.chatBubbleActionButtonIncomingShareIconImage : graphics.chatBubbleActionButtonOutgoingShareIconImage
                 case .payment:
-                    iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage
+                    if button.title.contains("Pay XTR") {
+                        iconImage = nil
+                    } else {
+                        iconImage = incoming ? graphics.chatBubbleActionButtonIncomingPaymentIconImage : graphics.chatBubbleActionButtonOutgoingPaymentIconImage
+                    }
                 case .openUserProfile:
                     iconImage = incoming ? graphics.chatBubbleActionButtonIncomingProfileIconImage : graphics.chatBubbleActionButtonOutgoingProfileIconImage
                 case .openWebView:
@@ -215,7 +219,23 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
             }
             
             let messageTheme = incoming ? theme.theme.chat.message.incoming : theme.theme.chat.message.outgoing
-            let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor:  bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
+            
+            let titleColor = bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper)
+            let attributedTitle: NSAttributedString
+            if title.contains("Pay XTR") {
+                let stars = title.replacingOccurrences(of: "Pay XTR", with: "")
+                let buttonAttributedString = NSMutableAttributedString(string: "Pay  >  \(stars)", font: titleFont, textColor: titleColor, paragraphAlignment: .center)
+                if let range = buttonAttributedString.string.range(of: ">"), let starImage = UIImage(bundleImageName: "Item List/PremiumIcon") {
+                    buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
+                    buttonAttributedString.addAttribute(.foregroundColor, value: titleColor, range: NSRange(range, in: buttonAttributedString.string))
+                    buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string))
+                }
+                attributedTitle = buttonAttributedString
+            } else {
+                attributedTitle = NSAttributedString(string: title, font: titleFont, textColor: titleColor)
+            }
+            
+            let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
 
             return (titleSize.size.width + sideInset + sideInset, { width in
                 return (CGSize(width: width, height: 42.0), { animation in
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
index e651de7a72..14c4fab91b 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
@@ -208,8 +208,12 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
                 if let _ = invoice.extendedMedia {
                     result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
                 } else {
-                    skipText = true
-                    result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
+                    if invoice.currency == "XTR" {
+                        result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
+                    } else {
+                        skipText = true
+                        result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
+                    }
                 }
                 needReactions = false
                 break inner
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift
index 2e71cd7cd8..c6ce0ca329 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift
@@ -290,11 +290,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
                 var isSeekableWebMedia = false
                 var isUnsupportedMedia = false
                 var story: Stories.Item?
+                var invoice: TelegramMediaInvoice?
                 for media in item.message.media {
                     if let file = media as? TelegramMediaFile, let duration = file.duration {
                         mediaDuration = Double(duration)
                     }
-                    if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, webEmbedType(content: content).supportsSeeking {
+                    if let media = media as? TelegramMediaInvoice, media.currency == "XTR" {
+                        invoice = media
+                    } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, webEmbedType(content: content).supportsSeeking {
                         isSeekableWebMedia = true
                     } else if media is TelegramMediaUnsupported {
                         isUnsupportedMedia = true
@@ -308,7 +311,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
                 }
                 
                 var isTranslating = false
-                if let story {
+                if let invoice {
+                    rawText = invoice.description
+                } else if let story {
                     rawText = story.text
                     messageEntities = story.entities
                 } else if isUnsupportedMedia {
diff --git a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift
index 11620ebcf9..2e45b46a04 100644
--- a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift
+++ b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift
@@ -113,8 +113,14 @@ public final class ListActionItemComponent: Component {
         case disabled
     }
     
+    public enum Alignment {
+        case `default`
+        case center
+    }
+    
     public let theme: PresentationTheme
     public let title: AnyComponent<Empty>
+    public let titleAlignment: Alignment
     public let contentInsets: UIEdgeInsets
     public let leftIcon: LeftIcon?
     public let icon: Icon?
@@ -125,6 +131,7 @@ public final class ListActionItemComponent: Component {
     public init(
         theme: PresentationTheme,
         title: AnyComponent<Empty>,
+        titleAlignment: Alignment = .default,
         contentInsets: UIEdgeInsets = UIEdgeInsets(top: 12.0, left: 0.0, bottom: 12.0, right: 0.0),
         leftIcon: LeftIcon? = nil,
         icon: Icon? = nil,
@@ -134,6 +141,7 @@ public final class ListActionItemComponent: Component {
     ) {
         self.theme = theme
         self.title = title
+        self.titleAlignment = titleAlignment
         self.contentInsets = contentInsets
         self.leftIcon = leftIcon
         self.icon = icon
@@ -149,6 +157,9 @@ public final class ListActionItemComponent: Component {
         if lhs.title != rhs.title {
             return false
         }
+        if lhs.titleAlignment != rhs.titleAlignment {
+            return false
+        }
         if lhs.contentInsets != rhs.contentInsets {
             return false
         }
@@ -373,6 +384,11 @@ public final class ListActionItemComponent: Component {
                 environment: {},
                 containerSize: CGSize(width: availableSize.width - contentLeftInset - contentRightInset, height: availableSize.height)
             )
+            
+            if case .center = component.titleAlignment {
+                contentLeftInset = floor((availableSize.width - titleSize.width) / 2.0)
+            }
+            
             let titleFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: contentHeight), size: titleSize)
             if let titleView = self.title.view {
                 if titleView.superview == nil {
diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift
index 1736295a98..aa8e3b2029 100644
--- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift
+++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift
@@ -348,6 +348,7 @@ final class PeerInfoScreenData {
     let hasSavedMessageTags: Bool
     let isPremiumRequiredForStoryPosting: Bool
     let personalChannel: PeerInfoPersonalChannelData?
+    let starsState: StarsContext.State?
     
     let _isContact: Bool
     var forceIsContact: Bool = false
@@ -387,7 +388,8 @@ final class PeerInfoScreenData {
         accountIsPremium: Bool,
         hasSavedMessageTags: Bool,
         isPremiumRequiredForStoryPosting: Bool,
-        personalChannel: PeerInfoPersonalChannelData?
+        personalChannel: PeerInfoPersonalChannelData?,
+        starsState: StarsContext.State?
     ) {
         self.peer = peer
         self.chatPeer = chatPeer
@@ -416,6 +418,7 @@ final class PeerInfoScreenData {
         self.hasSavedMessageTags = hasSavedMessageTags
         self.isPremiumRequiredForStoryPosting = isPremiumRequiredForStoryPosting
         self.personalChannel = personalChannel
+        self.starsState = starsState
     }
 }
 
@@ -675,7 +678,7 @@ private func peerInfoPersonalChannel(context: AccountContext, peerId: EnginePeer
     |> distinctUntilChanged
 }
 
-func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, accountsAndPeers: Signal<[(AccountContext, EnginePeer, Int32)], NoError>, activeSessionsContextAndCount: Signal<(ActiveSessionsContext, Int, WebSessionsContext)?, NoError>, notificationExceptions: Signal<NotificationExceptionsList?, NoError>, privacySettings: Signal<AccountPrivacySettings?, NoError>, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, hasPassport: Signal<Bool, NoError>) -> Signal<PeerInfoScreenData, NoError> {
+func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, accountsAndPeers: Signal<[(AccountContext, EnginePeer, Int32)], NoError>, activeSessionsContextAndCount: Signal<(ActiveSessionsContext, Int, WebSessionsContext)?, NoError>, notificationExceptions: Signal<NotificationExceptionsList?, NoError>, privacySettings: Signal<AccountPrivacySettings?, NoError>, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, hasPassport: Signal<Bool, NoError>, starsContext: StarsContext?) -> Signal<PeerInfoScreenData, NoError> {
     let preferences = context.sharedContext.accountManager.sharedData(keys: [
         SharedDataKeys.proxySettings,
         ApplicationSpecificSharedDataKeys.inAppNotificationSettings,
@@ -794,6 +797,13 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
         }
     }
     
+    let starsState: Signal<StarsContext.State?, NoError>
+    if let starsContext {
+        starsState = starsContext.state
+    } else {
+        starsState = .single(nil)
+    }
+    
     return combineLatest(
         context.account.viewTracker.peerView(peerId, updateData: true),
         accountsAndPeers,
@@ -818,9 +828,10 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
         |> distinctUntilChanged,
         hasStories,
         bots,
-        peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: true)
+        peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: true),
+        starsState
     )
-    |> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences, suggestions, limits, hasPassword, isPowerSavingEnabled, hasStories, bots, personalChannel -> PeerInfoScreenData in
+    |> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences, suggestions, limits, hasPassword, isPowerSavingEnabled, hasStories, bots, personalChannel, starsState -> PeerInfoScreenData in
         let (notificationExceptions, notificationsAuthorizationStatus, notificationsWarningSuppressed) = notifications
         let (featuredStickerPacks, archivedStickerPacks) = stickerPacks
         
@@ -893,7 +904,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
             accountIsPremium: peer?.isPremium ?? false,
             hasSavedMessageTags: false,
             isPremiumRequiredForStoryPosting: true,
-            personalChannel: personalChannel
+            personalChannel: personalChannel,
+            starsState: starsState
         )
     }
 }
@@ -932,7 +944,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
                 accountIsPremium: false,
                 hasSavedMessageTags: false,
                 isPremiumRequiredForStoryPosting: true,
-                personalChannel: nil
+                personalChannel: nil,
+                starsState: nil
             ))
         case let .user(userPeerId, secretChatId, kind):
             let groupsInCommon: GroupsInCommonContext?
@@ -1259,7 +1272,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
                     accountIsPremium: accountIsPremium,
                     hasSavedMessageTags: hasSavedMessageTags,
                     isPremiumRequiredForStoryPosting: false,
-                    personalChannel: personalChannel
+                    personalChannel: personalChannel,
+                    starsState: nil
                 )
             }
         case .channel:
@@ -1430,7 +1444,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
                     accountIsPremium: accountIsPremium,
                     hasSavedMessageTags: hasSavedMessageTags,
                     isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting,
-                    personalChannel: nil
+                    personalChannel: nil,
+                    starsState: nil
                 )
             }
         case let .group(groupId):
@@ -1724,7 +1739,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
                     accountIsPremium: accountIsPremium,
                     hasSavedMessageTags: hasSavedMessageTags,
                     isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting,
-                    personalChannel: nil
+                    personalChannel: nil,
+                    starsState: nil
                 ))
             }
         }
diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
index f04bd4b412..bd42884a95 100644
--- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
+++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
@@ -521,6 +521,7 @@ private enum PeerInfoSettingsSection {
     case businessSetup
     case profile
     case premiumManagement
+    case stars
 }
 
 private enum PeerInfoReportType {
@@ -978,10 +979,20 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
         items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: presentationData.strings.Settings_Premium, icon: PresentationResourcesSettings.premium, action: {
             interaction.openSettings(.premium)
         }))
-        items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 101, label: .text(""), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.Settings_Business, icon: PresentationResourcesSettings.business, action: {
+        //TODO:localize
+        let balanceText: String
+        if let balance = data.starsState?.balance, balance > 0 {
+            balanceText = "\(balance)"
+        } else {
+            balanceText = ""
+        }
+        items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 102, label: .text(balanceText), text: "Your Stars", icon: PresentationResourcesSettings.stars, action: {
+            interaction.openSettings(.stars)
+        }))
+        items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 103, label: .text(""), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.Settings_Business, icon: PresentationResourcesSettings.business, action: {
             interaction.openSettings(.businessSetup)
         }))
-        items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 102, label: .text(""), text: presentationData.strings.Settings_PremiumGift, icon: PresentationResourcesSettings.premiumGift, action: {
+        items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 104, label: .text(""), text: presentationData.strings.Settings_PremiumGift, icon: PresentationResourcesSettings.premiumGift, action: {
             interaction.openSettings(.premiumGift)
         }))
     }
@@ -2521,7 +2532,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
     }
     private var didSetReady = false
     
-    init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, initialPaneKey: PeerInfoPaneKey?) {
+    init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, starsContext: StarsContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, initialPaneKey: PeerInfoPaneKey?) {
         self.controller = controller
         self.context = context
         self.peerId = peerId
@@ -4158,7 +4169,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
             
             self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init)))
             
-            screenData = peerInfoScreenSettingsData(context: context, peerId: peerId, accountsAndPeers: self.accountsAndPeers.get(), activeSessionsContextAndCount: self.activeSessionsContextAndCount.get(), notificationExceptions: self.notificationExceptions.get(), privacySettings: self.privacySettings.get(), archivedStickerPacks: self.archivedPacks.get(), hasPassport: hasPassport)
+            screenData = peerInfoScreenSettingsData(context: context, peerId: peerId, accountsAndPeers: self.accountsAndPeers.get(), activeSessionsContextAndCount: self.activeSessionsContextAndCount.get(), notificationExceptions: self.notificationExceptions.get(), privacySettings: self.privacySettings.get(), archivedStickerPacks: self.archivedPacks.get(), hasPassport: hasPassport, starsContext: starsContext)
             
             
             self.headerNode.displayCopyContextMenu = { [weak self] node, copyPhone, copyUsername in
@@ -9998,6 +10009,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
                 return
             }
             self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://") && !url.contains("?start="), presentationData: self.context.sharedContext.currentPresentationData.with({$0}), navigationController: controller.navigationController as? NavigationController, dismissInput: {})
+        case .stars:
+            if let starsContext = self.controller?.starsContext {
+                push(self.context.sharedContext.makeStarsTransactionsScreen(context: self.context, starsContext: starsContext))
+            }
         }
     }
     
@@ -11834,6 +11849,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
     private let isMyProfile: Bool
     private let hintGroupInCommon: PeerId?
     private weak var requestsContext: PeerInvitationImportersContext?
+    fileprivate let starsContext: StarsContext?
     private let switchToRecommendedChannels: Bool
     private let chatLocation: ChatLocation
     private let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
@@ -11912,6 +11928,12 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
             self.chatLocation = .peer(id: peerId)
         }
         
+        if isSettings {
+            self.starsContext = context.engine.payments.peerStarsContext(peerId: context.account.peerId)
+        } else {
+            self.starsContext = nil
+        }
+        
         self.presentationData = updatedPresentationData?.0 ?? context.sharedContext.currentPresentationData.with { $0 }
         
         let baseNavigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
@@ -12238,7 +12260,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
     }
     
     override public func loadDisplayNode() {
-        self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: self.switchToRecommendedChannels ? .recommended : nil)
+        self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, starsContext: self.starsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, initialPaneKey: self.switchToRecommendedChannels ? .recommended : nil)
         self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 })
         self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get())
         self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get())
diff --git a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD
new file mode 100644
index 0000000000..580b3ce26b
--- /dev/null
+++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/BUILD
@@ -0,0 +1,28 @@
+load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
+
+swift_library(
+    name = "PremiumStarComponent",
+    module_name = "PremiumStarComponent",
+    srcs = glob([
+        "Sources/**/*.swift",
+    ]),
+    copts = [
+        "-warnings-as-errors",
+    ],
+    deps = [
+        "//submodules/AsyncDisplayKit",
+        "//submodules/Display",
+        "//submodules/SSignalKit/SwiftSignalKit",
+        "//submodules/ComponentFlow",
+        "//submodules/AccountContext",
+        "//submodules/AppBundle",
+        "//submodules/GZip",
+        "//submodules/LegacyComponents",
+        "//submodules/AvatarNode",
+        "//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
+        "//submodules/Components/MultilineTextComponent:MultilineTextComponent",
+    ],
+    visibility = [
+        "//visibility:public",
+    ],
+)
diff --git a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift
similarity index 85%
rename from submodules/PremiumUI/Sources/GiftAvatarComponent.swift
rename to submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift
index 771982bd65..60e8191424 100644
--- a/submodules/PremiumUI/Sources/GiftAvatarComponent.swift
+++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift
@@ -16,38 +16,49 @@ import TelegramPresentationData
 
 private let sceneVersion: Int = 1
 
-final class GiftAvatarComponent: Component {
+public final class GiftAvatarComponent: Component {
     let context: AccountContext
     let theme: PresentationTheme
     let peers: [EnginePeer]
     let isVisible: Bool
     let hasIdleAnimations: Bool
+    let hasScaleAnimation: Bool
+    let color: UIColor?
+    let offset: CGFloat?
         
-    init(context: AccountContext, theme: PresentationTheme, peers: [EnginePeer], isVisible: Bool, hasIdleAnimations: Bool) {
+    public init(context: AccountContext, theme: PresentationTheme, peers: [EnginePeer], isVisible: Bool, hasIdleAnimations: Bool, hasScaleAnimation: Bool = true, color: UIColor? = nil, offset: CGFloat? = nil) {
         self.context = context
         self.theme = theme
         self.peers = peers
         self.isVisible = isVisible
         self.hasIdleAnimations = hasIdleAnimations
+        self.hasScaleAnimation = hasScaleAnimation
+        self.color = color
+        self.offset = offset
     }
     
-    static func ==(lhs: GiftAvatarComponent, rhs: GiftAvatarComponent) -> Bool {
-        return lhs.peers == rhs.peers && lhs.theme === rhs.theme && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations
+    public static func ==(lhs: GiftAvatarComponent, rhs: GiftAvatarComponent) -> Bool {
+        return lhs.peers == rhs.peers && lhs.theme === rhs.theme && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.hasScaleAnimation == rhs.hasScaleAnimation && lhs.offset == rhs.offset
     }
     
-    final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
-        final class Tag {
+    public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
+        public final class Tag {
+            public init() {
+                
+            }
         }
         
-        func matches(tag: Any) -> Bool {
+        public func matches(tag: Any) -> Bool {
             if let _ = tag as? Tag {
                 return true
             }
             return false
         }
         
+        private var component: GiftAvatarComponent?
+        
         private var _ready = Promise<Bool>()
-        var ready: Signal<Bool, NoError> {
+        public var ready: Signal<Bool, NoError> {
             return self._ready.get()
         }
         
@@ -66,7 +77,7 @@ final class GiftAvatarComponent: Component {
         private var timer: SwiftSignalKit.Timer?
         private var hasIdleAnimations = false
         
-        override init(frame: CGRect) {
+        public override init(frame: CGRect) {
             self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0)))
             self.sceneView.backgroundColor = .clear
             self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
@@ -80,9 +91,7 @@ final class GiftAvatarComponent: Component {
             
             self.addSubview(self.sceneView)
             self.addSubview(self.avatarNode.view)
-            
-            self.setup()
-                        
+                                    
             let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
             self.addGestureRecognizer(tapGestureRecoginzer)
             
@@ -105,19 +114,43 @@ final class GiftAvatarComponent: Component {
             self.playAppearanceAnimation(velocity: nil, mirror: false, explode: true)
         }
         
+        private var didSetup = false
         private func setup() {
-            guard let scene = loadCompressedScene(name: "gift", version: sceneVersion) else {
+            guard let scene = loadCompressedScene(name: "gift", version: sceneVersion), !self.didSetup else {
                 return
             }
             
+            self.didSetup = true
+            
             self.sceneView.scene = scene
             self.sceneView.delegate = self
             
-            let _ = self.sceneView.snapshot()
+            if let color = self.component?.color {
+                let names: [String] = [
+                    "particles_left",
+                    "particles_right",
+                    "particles_left_bottom",
+                    "particles_right_bottom",
+                    "particles_center"
+                ]
+                
+                for name in names {
+                    if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first {
+                        particleSystem.particleColor = color
+                        particleSystem.particleColorVariation = SCNVector4Make(0, 0, 0, 0)
+                    }
+                }
+
+                self.didSetReady = true
+                self._ready.set(.single(true))
+                self.onReady()
+            } else {
+                let _ = self.sceneView.snapshot()
+            }
         }
         
         private var didSetReady = false
-        func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
+        public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
             if !self.didSetReady {
                 self.didSetReady = true
                 
@@ -146,6 +179,10 @@ final class GiftAvatarComponent: Component {
         }
         
         private func setupScaleAnimation() {
+            guard self.component?.hasScaleAnimation == true else {
+                return
+            }
+            
             let animation = CABasicAnimation(keyPath: "transform.scale")
             animation.duration = 2.0
             animation.fromValue = 1.0
@@ -245,9 +282,13 @@ final class GiftAvatarComponent: Component {
         }
         
         func update(component: GiftAvatarComponent, availableSize: CGSize, transition: Transition) -> CGSize {
+            self.component = component
+            
+            self.setup()
+            
             self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0))
             if self.sceneView.superview == self {
-                self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)
+                self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0 + (component.offset ?? 0.0))
             }
             
             self.hasIdleAnimations = component.hasIdleAnimations
@@ -325,11 +366,11 @@ final class GiftAvatarComponent: Component {
         }
     }
     
-    func makeView() -> View {
+    public func makeView() -> View {
         return View(frame: CGRect())
     }
     
-    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
+    public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
         return view.update(component: self, availableSize: availableSize, transition: transition)
     }
 }
diff --git a/submodules/PremiumUI/Sources/PremiumStarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift
similarity index 89%
rename from submodules/PremiumUI/Sources/PremiumStarComponent.swift
rename to submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift
index baea57bc96..e99e47c3c5 100644
--- a/submodules/PremiumUI/Sources/PremiumStarComponent.swift
+++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift
@@ -30,22 +30,17 @@ private func generateShineTexture() -> UIImage {
     return UIImage()
 }
 
-private func generateDiffuseTexture() -> UIImage {
+private func generateDiffuseTexture(colors: [UIColor]) -> UIImage {
     return generateImage(CGSize(width: 256, height: 256), rotatedContext: { size, context in
-        let colorsArray: [CGColor] = [
-            UIColor(rgb: 0x0079ff).cgColor,
-            UIColor(rgb: 0x6a93ff).cgColor,
-            UIColor(rgb: 0x9172fe).cgColor,
-            UIColor(rgb: 0xe46acd).cgColor,
-        ]
+        let colorsArray: [CGColor] = colors.map { $0.cgColor }
         var locations: [CGFloat] = [0.0, 0.25, 0.5, 0.75, 1.0]
         let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
 
-        context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
+        context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: size.height), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
     })!
 }
 
-func loadCompressedScene(name: String, version: Int) -> SCNScene? {
+public func loadCompressedScene(name: String, version: Int) -> SCNScene? {
     let resourceUrl: URL
     if let url = getAppBundle().url(forResource: name, withExtension: "scn") {
         resourceUrl = url
@@ -69,40 +64,52 @@ func loadCompressedScene(name: String, version: Int) -> SCNScene? {
     return scene
 }
 
-final class PremiumStarComponent: Component {
+public final class PremiumStarComponent: Component {
     let isIntro: Bool
     let isVisible: Bool
     let hasIdleAnimations: Bool
-        
-    init(isIntro: Bool, isVisible: Bool, hasIdleAnimations: Bool) {
+    let colors: [UIColor]?
+    
+    public init(
+        isIntro: Bool,
+        isVisible: Bool,
+        hasIdleAnimations: Bool,
+        colors: [UIColor]? = nil
+    ) {
         self.isIntro = isIntro
         self.isVisible = isVisible
         self.hasIdleAnimations = hasIdleAnimations
+        self.colors = colors
     }
     
-    static func ==(lhs: PremiumStarComponent, rhs: PremiumStarComponent) -> Bool {
-        return lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations
+    public static func ==(lhs: PremiumStarComponent, rhs: PremiumStarComponent) -> Bool {
+        return lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.colors == rhs.colors
     }
     
-    final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
-        final class Tag {
+    public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
+        public final class Tag {
+            public init() {
+                
+            }
         }
         
-        func matches(tag: Any) -> Bool {
+        public func matches(tag: Any) -> Bool {
             if let _ = tag as? Tag {
                 return true
             }
             return false
         }
         
+        private var component: PremiumStarComponent?
+        
         private var _ready = Promise<Bool>()
-        var ready: Signal<Bool, NoError> {
+        public var ready: Signal<Bool, NoError> {
             return self._ready.get()
         }
         
-        weak var animateFrom: UIView?
-        weak var containerView: UIView?
-        var animationColor: UIColor?
+        public weak var animateFrom: UIView?
+        public weak var containerView: UIView?
+        public var animationColor: UIColor?
         
         private let sceneView: SCNView
                 
@@ -126,8 +133,6 @@ final class PremiumStarComponent: Component {
             
             self.addSubview(self.sceneView)
             
-            self.setup()
-            
             let panGestureRecoginzer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
             self.addGestureRecognizer(panGestureRecoginzer)
             
@@ -274,21 +279,46 @@ final class PremiumStarComponent: Component {
             }
         }
         
+        private var didSetup = false
         private func setup() {
-            guard let scene = loadCompressedScene(name: "star", version: sceneVersion) else {
+            guard !self.didSetup, let scene = loadCompressedScene(name: "star", version: sceneVersion) else {
                 return
             }
             
+            self.didSetup = true
             self.sceneView.scene = scene
             self.sceneView.delegate = self
             
-            self.didSetReady = true
-            self._ready.set(.single(true))
-            self.onReady()
+            if let node = scene.rootNode.childNode(withName: "star", recursively: false), let colors = self.component?.colors, let color = colors.first {
+                node.geometry?.materials.first?.diffuse.contents = generateDiffuseTexture(colors: colors)
+                
+                let names: [String] = [
+                    "particles_left",
+                    "particles_right",
+                    "particles_left_bottom",
+                    "particles_right_bottom",
+                    "particles_center"
+                ]
+                
+                for name in names {
+                    if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first {
+                        particleSystem.particleColor = color
+                        particleSystem.particleColorVariation = SCNVector4Make(0, 0, 0, 0)
+                    }
+                }
+            }
+            
+            if self.animateFrom != nil {
+                let _ = self.sceneView.snapshot()
+            } else {
+                self.didSetReady = true
+                self._ready.set(.single(true))
+                self.onReady()
+            }
         }
         
         private var didSetReady = false
-        func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
+        public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
             if !self.didSetReady {
                 self.didSetReady = true
                 
@@ -305,7 +335,7 @@ final class PremiumStarComponent: Component {
             }
                         
             containerView = containerView.subviews[2].subviews[1]
-            
+                        
             if let animationColor = self.animationColor {
                 let newNode = node.clone()
                 newNode.geometry = node.geometry?.copy() as? SCNGeometry
@@ -562,6 +592,10 @@ final class PremiumStarComponent: Component {
         }
         
         func update(component: PremiumStarComponent, availableSize: CGSize, transition: Transition) -> CGSize {
+            self.component = component
+            
+            self.setup()
+            
             self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0))
             if self.sceneView.superview == self {
                 self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)
@@ -573,11 +607,11 @@ final class PremiumStarComponent: Component {
         }
     }
     
-    func makeView() -> View {
+    public func makeView() -> View {
         return View(frame: CGRect(), isIntro: self.isIntro)
     }
     
-    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
+    public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
         return view.update(component: self, availableSize: availableSize, transition: transition)
     }
 }
diff --git a/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift b/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift
index f9421338cb..9f4cb768d6 100644
--- a/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift
+++ b/submodules/TelegramUI/Components/ScrollComponent/Sources/ScrollComponent.swift
@@ -35,7 +35,7 @@ public final class ScrollComponent<ChildEnvironment: Equatable>: Component {
     let contentInsets: UIEdgeInsets
     let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void
     let contentOffsetWillCommit: (UnsafeMutablePointer<CGPoint>) -> Void
-    let resetScroll: ActionSlot<Void>
+    let resetScroll: ActionSlot<CGPoint?>
     
     public init(
         content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>,
@@ -43,7 +43,7 @@ public final class ScrollComponent<ChildEnvironment: Equatable>: Component {
         contentInsets: UIEdgeInsets,
         contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void,
         contentOffsetWillCommit:  @escaping (UnsafeMutablePointer<CGPoint>) -> Void,
-        resetScroll: ActionSlot<Void> = ActionSlot()
+        resetScroll: ActionSlot<CGPoint?> = ActionSlot()
     ) {
         self.content = content
         self.externalState = externalState
@@ -120,8 +120,8 @@ public final class ScrollComponent<ChildEnvironment: Equatable>: Component {
             )
             transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil)
             
-            component.resetScroll.connect { [weak self] _ in
-                self?.setContentOffset(.zero, animated: false)
+            component.resetScroll.connect { [weak self] point in
+                self?.setContentOffset(point ?? .zero, animated: point != nil)
             }
             
             if self.contentSize != contentSize {
diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD
new file mode 100644
index 0000000000..91f04b157a
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD
@@ -0,0 +1,43 @@
+load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
+
+swift_library(
+    name = "StarsPurchaseScreen",
+    module_name = "StarsPurchaseScreen",
+    srcs = glob([
+        "Sources/**/*.swift",
+    ]),
+    copts = [
+        "-warnings-as-errors",
+    ],
+    deps = [
+        "//submodules/AsyncDisplayKit",
+        "//submodules/Display",
+        "//submodules/Postbox",
+        "//submodules/TelegramCore",
+        "//submodules/SSignalKit/SwiftSignalKit",
+        "//submodules/ComponentFlow",
+        "//submodules/Components/ViewControllerComponent",
+        "//submodules/Components/ComponentDisplayAdapters",
+        "//submodules/Components/MultilineTextComponent",
+        "//submodules/Components/BalancedTextComponent",
+        "//submodules/TelegramPresentationData",
+        "//submodules/AccountContext",
+        "//submodules/AppBundle",
+        "//submodules/ItemListUI",
+        "//submodules/TelegramStringFormatting",
+        "//submodules/PresentationDataUtils",
+        "//submodules/Components/SheetComponent",
+        "//submodules/UndoUI",
+        "//submodules/TextFormat",
+        "//submodules/TelegramUI/Components/ListSectionComponent",
+        "//submodules/TelegramUI/Components/ListActionItemComponent",
+        "//submodules/TelegramUI/Components/ScrollComponent",
+        "//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
+        "//submodules/Components/BlurredBackgroundComponent",
+        "//submodules/Components/BundleIconComponent",
+        "//submodules/ConfettiEffect",
+    ],
+    visibility = [
+        "//visibility:public",
+    ],
+)
diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift
new file mode 100644
index 0000000000..a4791b830e
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift
@@ -0,0 +1,1052 @@
+import Foundation
+import UIKit
+import Display
+import ComponentFlow
+import SwiftSignalKit
+import TelegramCore
+import Postbox
+import TelegramPresentationData
+import PresentationDataUtils
+import ViewControllerComponent
+import AccountContext
+import MultilineTextComponent
+import BalancedTextComponent
+import Markdown
+import InAppPurchaseManager
+import AnimationCache
+import MultiAnimationRenderer
+import UndoUI
+import TelegramStringFormatting
+import ListSectionComponent
+import ListActionItemComponent
+import ScrollComponent
+import BlurredBackgroundComponent
+import TextFormat
+import PremiumStarComponent
+import BundleIconComponent
+import ConfettiEffect
+
+private struct StarsProduct: Equatable {
+    let option: StarsTopUpOption
+    let storeProduct: InAppPurchaseManager.Product
+    
+    var id: String {
+        return self.storeProduct.id
+    }
+
+    var price: String {
+        return self.storeProduct.price
+    }
+}
+
+private final class StarsPurchaseScreenContentComponent: CombinedComponent {
+    typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment)
+    
+    let context: AccountContext
+    let options: [StarsTopUpOption]
+    let peerId: EnginePeer.Id?
+    let requiredStars: Int32?
+    let forceDark: Bool
+    let products: [StarsProduct]?
+    let expanded: Bool
+    let stateUpdated: (Transition) -> Void
+    let buy: (StarsProduct) -> Void
+    
+    init(
+        context: AccountContext,
+        options: [StarsTopUpOption],
+        peerId: EnginePeer.Id?,
+        requiredStars: Int32?,
+        forceDark: Bool,
+        products: [StarsProduct]?,
+        expanded: Bool,
+        stateUpdated: @escaping (Transition) -> Void,
+        buy: @escaping (StarsProduct) -> Void
+    ) {
+        self.context = context
+        self.options = options
+        self.peerId = peerId
+        self.requiredStars = requiredStars
+        self.forceDark = forceDark
+        self.products = products
+        self.expanded = expanded
+        self.stateUpdated = stateUpdated
+        self.buy = buy
+    }
+    
+    static func ==(lhs: StarsPurchaseScreenContentComponent, rhs: StarsPurchaseScreenContentComponent) -> Bool {
+        if lhs.context !== rhs.context {
+            return false
+        }
+        if lhs.options != rhs.options {
+            return false
+        }
+        if lhs.peerId != rhs.peerId {
+            return false
+        }
+        if lhs.requiredStars != rhs.requiredStars {
+            return false
+        }
+        if lhs.forceDark != rhs.forceDark {
+            return false
+        }
+        if lhs.products != rhs.products {
+            return false
+        }
+        if lhs.expanded != rhs.expanded {
+            return false
+        }
+        return true
+    }
+    
+    final class State: ComponentState {
+        private let context: AccountContext
+        
+        var products: [StarsProduct]?
+        var peer: EnginePeer?
+        
+        private var disposable: Disposable?
+    
+        var cachedChevronImage: (UIImage, PresentationTheme)?
+        
+        init(
+            context: AccountContext,
+            peerId: EnginePeer.Id?
+        ) {
+            self.context = context
+            
+            super.init()
+            
+            if let peerId {
+                self.disposable = (context.engine.data.subscribe(
+                    TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
+                )
+                |> deliverOnMainQueue).start(next: { [weak self] peer in
+                    if let self, let peer {
+                        self.peer = peer
+                        self.updated(transition: .immediate)
+                    }
+                })
+            }
+            
+            let _ = updatePremiumPromoConfigurationOnce(account: context.account).start()
+        }
+        
+        deinit {
+            self.disposable?.dispose()
+        }
+    }
+    
+    func makeState() -> State {
+        return State(context: self.context, peerId: self.peerId)
+    }
+    
+    static var body: Body {
+        let overscroll = Child(Rectangle.self)
+        let fade = Child(RoundedRectangle.self)
+        let text = Child(BalancedTextComponent.self)
+        let list = Child(VStack<Empty>.self)
+        let termsText = Child(BalancedTextComponent.self)
+             
+        return { context in
+            let sideInset: CGFloat = 16.0
+            
+            let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
+            let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
+            let state = context.state
+            state.products = context.component.products
+            
+            let theme = environment.theme
+            let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 }
+            
+            let availableWidth = context.availableSize.width
+            let sideInsets = sideInset * 2.0 + environment.safeInsets.left + environment.safeInsets.right
+            var size = CGSize(width: context.availableSize.width, height: 0.0)
+            
+            var topBackgroundColor = theme.list.plainBackgroundColor
+            let bottomBackgroundColor = theme.list.blocksBackgroundColor
+            if theme.overallDarkAppearance {
+                topBackgroundColor = bottomBackgroundColor
+            }
+        
+            let overscroll = overscroll.update(
+                component: Rectangle(color: topBackgroundColor),
+                availableSize: CGSize(width: context.availableSize.width, height: 1000),
+                transition: context.transition
+            )
+            context.add(overscroll
+                .position(CGPoint(x: overscroll.size.width / 2.0, y: -overscroll.size.height / 2.0))
+            )
+            
+            let fade = fade.update(
+                component: RoundedRectangle(
+                    colors: [
+                        topBackgroundColor,
+                        bottomBackgroundColor
+                    ],
+                    cornerRadius: 0.0,
+                    gradientDirection: .vertical
+                ),
+                availableSize: CGSize(width: availableWidth, height: 300),
+                transition: context.transition
+            )
+            context.add(fade
+                .position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0))
+            )
+            
+            size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0
+            
+            let textColor = theme.list.itemPrimaryTextColor
+            let accentColor = theme.list.itemAccentColor
+            
+            let textFont = Font.regular(15.0)
+            let boldTextFont = Font.semibold(15.0)
+            
+            //TODO:localize
+            let textString: String
+//            if let peer = state.peer, let requiredStars = context.component.requiredStars {
+//                textString = "\(peer.compactDisplayTitle) requests \(requiredStars) Stars.\n\nAvailable balance: **1000 Stars**.\n\nBuy **Stars** to unlock **content and services** in miniapps on **Telegram**."
+//            } else {
+            textString = "Choose how many Stars you would like to buy."
+//            }
+            
+            let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { contents in
+                return (TelegramTextAttributes.URL, contents)
+            })
+            
+            let text = text.update(
+                component: BalancedTextComponent(
+                    text: .markdown(
+                        text: textString,
+                        attributes: markdownAttributes
+                    ),
+                    horizontalAlignment: .center,
+                    maximumNumberOfLines: 0,
+                    lineSpacing: 0.2,
+                    highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2),
+                    highlightAction: { attributes in
+                        if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
+                            return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
+                        } else {
+                            return nil
+                        }
+                    },
+                    tapAction: { _, _ in
+                    }
+                ),
+                environment: {},
+                availableSize: CGSize(width: availableWidth - sideInsets - 8.0, height: 240.0),
+                transition: .immediate
+            )
+            context.add(text
+                .position(CGPoint(x: size.width / 2.0, y: size.height + text.size.height / 2.0))
+                .appear(.default(alpha: true))
+                .disappear(.default(alpha: true))
+            )
+            size.height += text.size.height
+            size.height += 21.0
+               
+            let initialValues: [Int64] = [
+                15,
+                75,
+                250,
+                500,
+                1000,
+                2500
+            ]
+            
+            let stars: [Int64: Int] = [
+                15: 1,
+                75: 2,
+                250: 3,
+                500: 4,
+                1000: 5,
+                2500: 6,
+                25: 1,
+                50: 1,
+                100: 2,
+                150: 2,
+                350: 3,
+                750: 4,
+                1500: 5
+            ]
+            
+            let externalStateUpdated = context.component.stateUpdated
+            let layoutPerks = {
+                size.height += 8.0
+                                
+                var i = 0
+                var items: [AnyComponentWithIdentity<Empty>] = []
+                                                
+                guard let products = state.products else {
+                    return
+                }
+                for product in products {
+                    if !context.component.expanded && !initialValues.contains(product.option.count) {
+                        continue
+                    }
+                        
+                    let title = "\(product.option.count) Stars"
+                    let price = product.price
+                    
+                    let titleComponent = AnyComponent(MultilineTextComponent(
+                        text: .plain(NSAttributedString(
+                            string: title,
+                            font: Font.medium(presentationData.listsFontSize.baseDisplaySize),
+                            textColor: environment.theme.list.itemPrimaryTextColor
+                        )),
+                        maximumNumberOfLines: 0
+                    ))
+                    
+                    let buy = context.component.buy
+                    items.append(AnyComponentWithIdentity(
+                        id: product.id,
+                        component: AnyComponent(ListSectionComponent(
+                            theme: environment.theme,
+                            header: nil,
+                            footer: nil,
+                            items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
+                                theme: environment.theme,
+                                title: titleComponent,
+                                contentInsets: UIEdgeInsets(top: 12.0, left: -6.0, bottom: 12.0, right: 0.0),
+                                leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(StarsIconComponent(
+                                    count: stars[product.option.count] ?? 1
+                                )))),
+                                accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
+                                    text: .plain(NSAttributedString(
+                                        string: price,
+                                        font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
+                                        textColor: environment.theme.list.itemSecondaryTextColor
+                                    )),
+                                    maximumNumberOfLines: 0
+                                ))), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 16.0))),
+                                action: { _ in
+                                    buy(product)
+                                }
+                            )))]
+                        ))
+                    ))
+                    i += 1
+                }
+                
+                if !context.component.expanded {
+                    let titleComponent = AnyComponent(MultilineTextComponent(
+                        text: .plain(NSAttributedString(
+                            string: "Show More Options",
+                            font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
+                            textColor: environment.theme.list.itemAccentColor
+                        )),
+                        horizontalAlignment: .center,
+                        maximumNumberOfLines: 0
+                    ))
+                    
+                    let titleCombinedComponent = AnyComponent(HStack([
+                        AnyComponentWithIdentity(id: AnyHashable(0), component: titleComponent),
+                        AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(BundleIconComponent(name: "Chat/Input/Search/DownButton", tintColor: environment.theme.list.itemAccentColor)))
+                    ], spacing: 1.0))
+                    
+                    items.append(AnyComponentWithIdentity(
+                        id: items.count,
+                        component: AnyComponent(ListSectionComponent(
+                            theme: environment.theme,
+                            header: nil,
+                            footer: nil,
+                            items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
+                                theme: environment.theme,
+                                title: titleCombinedComponent,
+                                titleAlignment: .center,
+                                contentInsets: UIEdgeInsets(top: 7.0, left: 0.0, bottom: 7.0, right: 0.0),
+                                leftIcon: nil,
+                                accessory: .none,
+                                action: { _ in
+                                    externalStateUpdated(.easeInOut(duration: 0.3))
+                                }
+                            )))]
+                        ))
+                    ))
+                }
+ 
+                let list = list.update(
+                    component: VStack(items, spacing: 16.0),
+                    environment: {},
+                    availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
+                    transition: context.transition
+                )
+                context.add(list
+                    .position(CGPoint(x: availableWidth / 2.0, y: size.height + list.size.height / 2.0))
+                )
+                size.height += list.size.height
+                
+                size.height += 23.0
+            }
+            
+            layoutPerks()
+            
+            
+            let termsFont = Font.regular(13.0)
+            let termsTextColor = environment.theme.list.freeTextColor
+            let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsFont, textColor: environment.theme.list.itemAccentColor), linkAttribute: { contents in
+                return (TelegramTextAttributes.URL, contents)
+            })
+            let textSideInset: CGFloat = 16.0
+            
+            let termsText = termsText.update(
+                component: BalancedTextComponent(
+                    text: .markdown(text: "By proceeding and purchasing Stars, you agree with [Terms and Conditions]().", attributes: termsMarkdownAttributes),
+                    horizontalAlignment: .center,
+                    maximumNumberOfLines: 0,
+                    lineSpacing: 0.2,
+                    highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2),
+                    highlightAction: { attributes in
+                        if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
+                            return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
+                        } else {
+                            return nil
+                        }
+                    },
+                    tapAction: { attributes, _ in
+                        
+                    }
+                ),
+                environment: {},
+                availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 3.0, height: .greatestFiniteMagnitude),
+                transition: context.transition
+            )
+            context.add(termsText
+                .position(CGPoint(x: availableWidth / 2.0, y: size.height + termsText.size.height / 2.0))
+            )
+            size.height += termsText.size.height
+            size.height += 10.0
+            
+            size.height += scrollEnvironment.insets.bottom
+            
+            return size
+        }
+    }
+}
+
+private final class StarsPurchaseScreenComponent: CombinedComponent {
+    typealias EnvironmentType = ViewControllerComponentContainer.Environment
+    
+    let context: AccountContext
+    let starsContext: StarsContext
+    let options: [StarsTopUpOption]
+    let peerId: EnginePeer.Id?
+    let requiredStars: Int32?
+    let forceDark: Bool
+    let updateInProgress: (Bool) -> Void
+    let present: (ViewController) -> Void
+    let completion: () -> Void
+    
+    init(
+        context: AccountContext,
+        starsContext: StarsContext,
+        options: [StarsTopUpOption],
+        peerId: EnginePeer.Id?,
+        requiredStars: Int32?,
+        forceDark: Bool,
+        updateInProgress: @escaping (Bool) -> Void,
+        present: @escaping (ViewController) -> Void,
+        completion: @escaping () -> Void
+    ) {
+        self.context = context
+        self.starsContext = starsContext
+        self.options = options
+        self.peerId = peerId
+        self.requiredStars = requiredStars
+        self.forceDark = forceDark
+        self.updateInProgress = updateInProgress
+        self.present = present
+        self.completion = completion
+    }
+        
+    static func ==(lhs: StarsPurchaseScreenComponent, rhs: StarsPurchaseScreenComponent) -> Bool {
+        if lhs.context !== rhs.context {
+            return false
+        }
+        if lhs.starsContext !== rhs.starsContext {
+            return false
+        }
+        if lhs.options != rhs.options {
+            return false
+        }
+        if lhs.peerId != rhs.peerId {
+            return false
+        }
+        if lhs.requiredStars != rhs.requiredStars {
+            return false
+        }
+        if lhs.forceDark != rhs.forceDark {
+            return false
+        }
+        return true
+    }
+    
+    final class State: ComponentState {
+        private let context: AccountContext
+        private let updateInProgress: (Bool) -> Void
+        private let present: (ViewController) -> Void
+        private let completion: () -> Void
+        
+        var topContentOffset: CGFloat?
+        var bottomContentOffset: CGFloat?
+        
+        var hasIdleAnimations = true
+        
+        var inProgress = false
+        
+        private(set) var promoConfiguration: PremiumPromoConfiguration?
+        
+        private(set) var products: [StarsProduct]?
+        private(set) var starsState: StarsContext.State?
+                
+        let animationCache: AnimationCache
+        let animationRenderer: MultiAnimationRenderer
+                
+        private var disposable: Disposable?
+        private var paymentDisposable = MetaDisposable()
+        
+        var cachedChevronImage: (UIImage, PresentationTheme)?
+        
+        init(
+            context: AccountContext,
+            starsContext: StarsContext,
+            initialOptions: [StarsTopUpOption],
+            updateInProgress: @escaping (Bool) -> Void,
+            present: @escaping (ViewController) -> Void,
+            completion: @escaping () -> Void
+        ) {
+            self.context = context
+            self.updateInProgress = updateInProgress
+            self.present = present
+            self.completion = completion
+            
+            self.animationCache = context.animationCache
+            self.animationRenderer = context.animationRenderer
+            
+            super.init()
+            
+            let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
+            if let inAppPurchaseManager = context.inAppPurchaseManager {
+                availableProducts = inAppPurchaseManager.availableProducts
+            } else {
+                availableProducts = .single([])
+            }
+            
+            let options: Signal<[StarsTopUpOption], NoError>
+            if !initialOptions.isEmpty {
+                options = .single(initialOptions)
+            } else {
+                options = .single([]) |> then(context.engine.payments.starsTopUpOptions())
+            }
+                                    
+            self.disposable = combineLatest(
+                queue: Queue.mainQueue(),
+                availableProducts,
+                options,
+                starsContext.state
+            ).start(next: { [weak self] availableProducts, options, starsState in
+                guard let self else {
+                    return
+                }
+                var products: [StarsProduct] = []
+                for option in options {
+                    if let product = availableProducts.first(where: { $0.id == option.storeProductId }) {
+                        products.append(StarsProduct(option: option, storeProduct: product))
+                    }
+                }
+
+                self.products = products.sorted(by: { $0.option.count < $1.option.count })
+                self.starsState = starsState
+                
+                self.updated(transition: .immediate)
+            })
+        }
+        
+        deinit {
+            self.disposable?.dispose()
+            self.paymentDisposable.dispose()
+        }
+        
+        func buy(product: StarsProduct) {
+            guard let inAppPurchaseManager = self.context.inAppPurchaseManager, !self.inProgress else {
+                return
+            }
+            
+            self.inProgress = true
+            self.updateInProgress(true)
+            self.updated(transition: .immediate)
+            
+            let (currency, amount) = product.storeProduct.priceCurrencyAndAmount
+            let purpose: AppStoreTransactionPurpose = .stars(count: product.option.count, currency: currency, amount: amount)
+            
+            let _ = (self.context.engine.payments.canPurchasePremium(purpose: purpose)
+            |> deliverOnMainQueue).start(next: { [weak self] available in
+                if let strongSelf = self {
+                    let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
+                    if available {
+                        strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(product.storeProduct, purpose: purpose)
+                        |> deliverOnMainQueue).start(next: { [weak self] status in
+                            if let self, case .purchased = status {
+                                self.inProgress = false
+                                self.updateInProgress(false)
+                                
+                                self.updated(transition: .easeInOut(duration: 0.25))
+                                self.completion()
+                            }
+                        }, error: { [weak self] error in
+                            if let strongSelf = self {
+                                strongSelf.inProgress = false
+                                strongSelf.updateInProgress(false)
+                                strongSelf.updated(transition: .immediate)
+
+                                var errorText: String?
+                                switch error {
+                                    case .generic:
+                                        errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
+                                    case .network:
+                                        errorText = presentationData.strings.Premium_Purchase_ErrorNetwork
+                                    case .notAllowed:
+                                        errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed
+                                    case .cantMakePayments:
+                                        errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments
+                                    case .assignFailed:
+                                        errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
+                                    case .cancelled:
+                                        break
+                                }
+                                
+                                if let errorText = errorText {
+                                    let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
+                                    strongSelf.present(alertController)
+                                }
+                            }
+                        }))
+                    } else {
+                        strongSelf.inProgress = false
+                        strongSelf.updateInProgress(false)
+                        strongSelf.updated(transition: .immediate)
+                    }
+                }
+            })
+        }
+        
+        func updateIsFocused(_ isFocused: Bool) {
+            self.hasIdleAnimations = !isFocused
+            self.updated(transition: .immediate)
+        }
+        
+        var isExpanded = false
+    }
+    
+    func makeState() -> State {
+        return State(context: self.context, starsContext: self.starsContext, initialOptions: self.options, updateInProgress: self.updateInProgress, present: self.present, completion: self.completion)
+    }
+    
+    static var body: Body {
+        let background = Child(Rectangle.self)
+        let scrollContent = Child(ScrollComponent<EnvironmentType>.self)
+        let star = Child(PremiumStarComponent.self)
+        let topPanel = Child(BlurredBackgroundComponent.self)
+        let topSeparator = Child(Rectangle.self)
+        let title = Child(MultilineTextComponent.self)
+        let balanceText = Child(MultilineTextComponent.self)
+        
+        let scrollAction = ActionSlot<CGPoint?>()
+                
+        return { context in
+            let environment = context.environment[EnvironmentType.self].value
+            let state = context.state
+                        
+            let background = background.update(component: Rectangle(color: environment.theme.list.blocksBackgroundColor), environment: {}, availableSize: context.availableSize, transition: context.transition)
+            
+            var starIsVisible = true
+            if let topContentOffset = state.topContentOffset, topContentOffset >= 123.0 {
+                starIsVisible = false
+            }
+
+            let header = star.update(
+                component: PremiumStarComponent(
+                    isIntro: true,
+                    isVisible: starIsVisible,
+                    hasIdleAnimations: state.hasIdleAnimations,
+                    colors: [
+                        UIColor(rgb: 0xea8904),
+                        UIColor(rgb: 0xf09903),
+                        UIColor(rgb: 0xfec209),
+                        UIColor(rgb: 0xfed31a)
+                    ]
+                ),
+                availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0),
+                transition: context.transition
+            )
+            
+            let topPanel = topPanel.update(
+                component: BlurredBackgroundComponent(
+                    color: environment.theme.rootController.navigationBar.blurredBackgroundColor
+                ),
+                availableSize: CGSize(width: context.availableSize.width, height: environment.navigationHeight),
+                transition: context.transition
+            )
+            
+            let topSeparator = topSeparator.update(
+                component: Rectangle(
+                    color: environment.theme.rootController.navigationBar.separatorColor
+                ),
+                availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel),
+                transition: context.transition
+            )
+            
+            //TODO:localize
+            let title = title.update(
+                component: MultilineTextComponent(
+                    text: .plain(NSAttributedString(string: "Get Stars", font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
+                    horizontalAlignment: .center,
+                    truncationType: .end,
+                    maximumNumberOfLines: 1
+                ),
+                availableSize: context.availableSize,
+                transition: context.transition
+            )
+
+            let textColor = environment.theme.list.itemPrimaryTextColor
+            let accentColor = UIColor(rgb: 0x597cf5)
+            
+            let textFont = Font.regular(14.0)
+            let boldTextFont = Font.bold(14.0)
+            let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { _ in
+                return nil
+            })
+            
+            if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
+                state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: UIColor(rgb: 0xf09903))!, environment.theme)
+            }
+            
+            let balanceAttributedString = parseMarkdownIntoAttributedString("Balance:  *  **\(state.starsState?.balance ?? 0)**", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
+            if let range = balanceAttributedString.string.range(of: "*"), let chevronImage = state.cachedChevronImage?.0 {
+                balanceAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: balanceAttributedString.string))
+                balanceAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xf09903), range: NSRange(range, in: balanceAttributedString.string))
+                balanceAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: balanceAttributedString.string))
+            }
+            let balanceText = balanceText.update(
+                component: MultilineTextComponent(
+                    text: .plain(balanceAttributedString),
+                    horizontalAlignment: .left,
+                    maximumNumberOfLines: 0
+                ),
+                availableSize: CGSize(width: 200, height: context.availableSize.height),
+                transition: .immediate
+            )
+              
+            let scrollContent = scrollContent.update(
+                component: ScrollComponent<EnvironmentType>(
+                    content: AnyComponent(StarsPurchaseScreenContentComponent(
+                        context: context.component.context,
+                        options: context.component.options,
+                        peerId: context.component.peerId,
+                        requiredStars: context.component.requiredStars,
+                        forceDark: context.component.forceDark,
+                        products: state.products,
+                        expanded: state.isExpanded,
+                        stateUpdated: { [weak state] transition in
+                            scrollAction.invoke(CGPoint(x: 0.0, y: 176.0))
+                            state?.isExpanded = true
+                            state?.updated(transition: transition)
+                        },
+                        buy: { [weak state] product in
+                            state?.buy(product: product)
+                        }
+                    )),
+                    contentInsets: UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0),
+                    contentOffsetUpdated: { [weak state] topContentOffset, bottomContentOffset in
+                        state?.topContentOffset = topContentOffset
+                        state?.bottomContentOffset = bottomContentOffset
+                        Queue.mainQueue().justDispatch {
+                            state?.updated(transition: .immediate)
+                        }
+                    },
+                    contentOffsetWillCommit: { targetContentOffset in
+                        if targetContentOffset.pointee.y < 100.0 {
+                            targetContentOffset.pointee = CGPoint(x: 0.0, y: 0.0)
+                        } else if targetContentOffset.pointee.y < 176.0 {
+                            targetContentOffset.pointee = CGPoint(x: 0.0, y: 176.0)
+                        }
+                    },
+                    resetScroll: scrollAction
+                ),
+                environment: { environment },
+                availableSize: context.availableSize,
+                transition: context.transition
+            )
+            
+            let topInset: CGFloat = environment.navigationHeight - 56.0
+            
+            context.add(background
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
+            )
+            
+            context.add(scrollContent
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
+            )
+                        
+            let topPanelAlpha: CGFloat
+            let titleOffset: CGFloat
+            let titleScale: CGFloat
+            let titleOffsetDelta = (topInset + 160.0) - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)
+            let titleAlpha: CGFloat
+            
+            if let topContentOffset = state.topContentOffset {
+                topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0
+                let topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0
+                titleOffset = topContentOffset
+                let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta))
+                titleScale = 1.0 - fraction * 0.36
+                
+                titleAlpha = 1.0
+            } else {
+                topPanelAlpha = 0.0
+                titleScale = 1.0
+                titleOffset = 0.0
+                titleAlpha = 1.0
+            }
+            
+            context.add(header
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + header.size.height / 2.0 - 30.0 - titleOffset * titleScale))
+                .scale(titleScale)
+            )
+            
+            context.add(topPanel
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0))
+                .opacity(topPanelAlpha)
+            )
+            context.add(topSeparator
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height))
+                .opacity(topPanelAlpha)
+            )
+            
+            context.add(title
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)))
+                .scale(titleScale)
+                .opacity(titleAlpha)
+            )
+            
+            context.add(balanceText
+                .position(CGPoint(x: context.availableSize.width - 16.0 - balanceText.size.width / 2.0, y: 28.0))
+            )
+                                    
+            return context.availableSize
+        }
+    }
+}
+
+public final class StarsPurchaseScreen: ViewControllerComponentContainer {
+    fileprivate let context: AccountContext
+    fileprivate let starsContext: StarsContext
+    fileprivate let options: [StarsTopUpOption]
+    
+    private var didSetReady = false
+    private let _ready = Promise<Bool>()
+    public override var ready: Promise<Bool> {
+        return self._ready
+    }
+        
+    public init(
+        context: AccountContext,
+        starsContext: StarsContext,
+        options: [StarsTopUpOption],
+        peerId: EnginePeer.Id?,
+        requiredStars: Int32?,
+        modal: Bool = true,
+        forceDark: Bool = false
+    ) {
+        self.context = context
+        self.starsContext = starsContext
+        self.options = options
+            
+        var updateInProgressImpl: ((Bool) -> Void)?
+        var presentImpl: ((ViewController) -> Void)?
+        var completionImpl: (() -> Void)?
+        super.init(context: context, component: StarsPurchaseScreenComponent(
+            context: context,
+            starsContext: starsContext,
+            options: options,
+            peerId: peerId,
+            requiredStars: requiredStars,
+            forceDark: forceDark,
+            updateInProgress: { inProgress in
+                updateInProgressImpl?(inProgress)
+            },
+            present: { c in
+                presentImpl?(c)
+            },
+            completion: {
+                completionImpl?()
+            }
+        ), navigationBarAppearance: .transparent, presentationMode: modal ? .modal : .default, theme: forceDark ? .dark : .default)
+        
+        let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+        
+        if modal {
+            let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed))
+            self.navigationItem.setLeftBarButton(cancelItem, animated: false)
+            self.navigationPresentation = .modal
+        } else {
+            self.navigationPresentation = .modalInLargeLayout
+        }
+        
+        updateInProgressImpl = { [weak self] inProgress in
+            if let strongSelf = self {
+                strongSelf.navigationItem.leftBarButtonItem?.isEnabled = !inProgress
+                strongSelf.view.disablesInteractiveTransitionGestureRecognizer = inProgress
+                strongSelf.view.disablesInteractiveModalDismiss = inProgress
+            }
+        }
+        presentImpl = { [weak self] c in
+            if let self {
+                self.present(c, in: .window(.root))
+            }
+        }
+        completionImpl = { [weak self] in
+            if let self {
+                self.animateSuccess()
+            }
+        }
+    }
+    
+    required public init(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    public override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        self.dismissAllTooltips()
+    }
+    
+    fileprivate func dismissAllTooltips() {
+        self.window?.forEachController({ controller in
+            if let controller = controller as? UndoOverlayController {
+                controller.dismiss()
+            }
+        })
+        self.forEachController({ controller in
+            if let controller = controller as? UndoOverlayController {
+                controller.dismiss()
+            }
+            return true
+        })
+    }
+    
+    @objc private func cancelPressed() {
+        self.dismiss()
+        self.wasDismissed?()
+    }
+    
+    public func animateSuccess() {
+        self.dismiss()
+        self.navigationController?.view.addSubview(ConfettiView(frame: self.view.bounds, customImage: UIImage(bundleImageName: "Peer Info/PremiumIcon")))
+    }
+    
+    public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
+        super.containerLayoutUpdated(layout, transition: transition)
+        
+        if !self.didSetReady {
+            if let view = self.node.hostView.findTaggedView(tag: PremiumStarComponent.View.Tag()) as? PremiumStarComponent.View {
+                self.didSetReady = true
+                self._ready.set(view.ready)
+            }
+        }
+    }
+}
+
+func generateStarsIcon(count: Int) -> UIImage {
+    let image = generateGradientTintedImage(
+        image: UIImage(bundleImageName: "Peer Info/PremiumIcon"),
+        colors: [
+            UIColor(rgb: 0xfed219),
+            UIColor(rgb: 0xf3a103),
+            UIColor(rgb: 0xe78104)
+        ],
+        direction: .diagonal
+    )!
+    
+    let imageSize = CGSize(width: 20.0, height: 20.0)
+    let partImage = generateImage(imageSize, contextGenerator: { size, context in
+        context.clear(CGRect(origin: .zero, size: size))
+        if let cgImage = image.cgImage {
+            context.draw(cgImage, in: CGRect(origin: .zero, size: size), byTiling: false)
+            context.saveGState()
+            context.clip(to: CGRect(origin: .zero, size: size).insetBy(dx: -1.0, dy: -1.0).offsetBy(dx: -2.0, dy: 0.0), mask: cgImage)
+            
+            context.setBlendMode(.clear)
+            context.setFillColor(UIColor.clear.cgColor)
+            context.fill(CGRect(origin: .zero, size: size))
+            context.restoreGState()
+            
+            context.setBlendMode(.clear)
+            context.setFillColor(UIColor.clear.cgColor)
+            context.fill(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width / 2.0, height: size.height - 4.0)))
+        }
+    })!
+    
+    let spacing: CGFloat = (3.0 - UIScreenPixel)
+    let totalWidth = 20.0 + spacing * CGFloat(count - 1)
+    
+    return generateImage(CGSize(width: ceil(totalWidth), height: 20.0), contextGenerator: { size, context in
+        context.clear(CGRect(origin: .zero, size: size))
+        
+        var originX = floorToScreenPixels((size.width - totalWidth) / 2.0)
+        
+        if let cgImage = image.cgImage, let partCGImage = partImage.cgImage {
+            context.draw(cgImage, in: CGRect(origin: CGPoint(x: originX, y: 0.0), size: imageSize), byTiling: false)
+            originX += spacing
+            
+            for _ in 0 ..< count - 1 {
+                context.draw(partCGImage, in: CGRect(origin: CGPoint(x: originX, y: 0.0), size: imageSize), byTiling: false)
+                originX += spacing
+            }
+        }
+    })!
+}
+
+final class StarsIconComponent: CombinedComponent {
+    let count: Int
+    
+    init(
+        count: Int
+    ) {
+        self.count = count
+    }
+    
+    static func ==(lhs: StarsIconComponent, rhs: StarsIconComponent) -> Bool {
+        if lhs.count != rhs.count {
+            return false
+        }
+        return true
+    }
+    
+    static var body: Body {
+        let icon = Child(Image.self)
+        
+        var image: (UIImage, Int)?
+        
+        return { context in
+            if image == nil || image?.1 != context.component.count {
+                image = (generateStarsIcon(count: context.component.count), context.component.count)
+            }
+            
+            let iconSize = CGSize(width: image!.0.size.width, height: 20.0)
+            
+            let icon = icon.update(
+                component: Image(image: image?.0),
+                availableSize: iconSize,
+                transition: context.transition
+            )
+            
+            let iconPosition = CGPoint(x: iconSize.width / 2.0, y: iconSize.height / 2.0)
+            context.add(icon
+                .position(iconPosition)
+            )
+            return iconSize
+        }
+    }
+}
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD
new file mode 100644
index 0000000000..3a16bf04b3
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD
@@ -0,0 +1,45 @@
+load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
+
+swift_library(
+    name = "StarsTransactionsScreen",
+    module_name = "StarsTransactionsScreen",
+    srcs = glob([
+        "Sources/**/*.swift",
+    ]),
+    copts = [
+        "-warnings-as-errors",
+    ],
+    deps = [
+        "//submodules/AsyncDisplayKit",
+        "//submodules/Display",
+        "//submodules/Postbox",
+        "//submodules/TelegramCore",
+        "//submodules/SSignalKit/SwiftSignalKit",
+        "//submodules/ComponentFlow",
+        "//submodules/Components/ViewControllerComponent",
+        "//submodules/Components/ComponentDisplayAdapters",
+        "//submodules/Components/MultilineTextComponent",
+        "//submodules/Components/BalancedTextComponent",
+        "//submodules/TelegramPresentationData",
+        "//submodules/AccountContext",
+        "//submodules/AppBundle",
+        "//submodules/ItemListUI",
+        "//submodules/TelegramStringFormatting",
+        "//submodules/PresentationDataUtils",
+        "//submodules/Components/SheetComponent",
+        "//submodules/UndoUI",
+        "//submodules/TextFormat",
+        "//submodules/TelegramUI/Components/ListSectionComponent",
+        "//submodules/TelegramUI/Components/ListActionItemComponent",
+        "//submodules/TelegramUI/Components/ScrollComponent",
+        "//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
+        "//submodules/Components/BlurredBackgroundComponent",
+        "//submodules/Components/BundleIconComponent",
+        "//submodules/Components/SolidRoundedButtonComponent",
+        "//submodules/TelegramUI/Components/AnimatedTextComponent",
+        "//submodules/AvatarNode",
+    ],
+    visibility = [
+        "//visibility:public",
+    ],
+)
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift
new file mode 100644
index 0000000000..28ac40019e
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift
@@ -0,0 +1,163 @@
+import Foundation
+import UIKit
+import Display
+import AsyncDisplayKit
+import ComponentFlow
+import AccountContext
+import MultilineTextComponent
+import TelegramPresentationData
+import PresentationDataUtils
+import SolidRoundedButtonComponent
+import AnimatedTextComponent
+
+final class StarsBalanceComponent: Component {
+    let theme: PresentationTheme
+    let strings: PresentationStrings
+    let count: Int64
+    let buy: () -> Void
+    
+    init(
+        theme: PresentationTheme,
+        strings: PresentationStrings,
+        count: Int64,
+        buy: @escaping () -> Void
+    ) {
+        self.theme = theme
+        self.strings = strings
+        self.count = count
+        self.buy = buy
+    }
+    
+    static func ==(lhs: StarsBalanceComponent, rhs: StarsBalanceComponent) -> Bool {
+        if lhs.theme !== rhs.theme {
+            return false
+        }
+        if lhs.strings !== rhs.strings {
+            return false
+        }
+        if lhs.count != rhs.count {
+            return false
+        }
+        return true
+    }
+    
+    final class View: UIView {
+        private let icon = UIImageView()
+        private let title = ComponentView<Empty>()
+        private let subtitle = ComponentView<Empty>()
+        private var button = ComponentView<Empty>()
+        
+        private var component: StarsBalanceComponent?
+        
+        override init(frame: CGRect) {
+            super.init(frame: frame)
+            
+            self.icon.image = UIImage(bundleImageName: "Premium/Stars/StarLarge")
+            
+            self.addSubview(self.icon)
+        }
+        
+        required init?(coder: NSCoder) {
+            fatalError("init(coder:) has not been implemented")
+        }
+        
+        func update(component: StarsBalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
+            self.component = component
+            
+            let sideInset: CGFloat = 16.0
+            
+            let size = CGSize(width: availableSize.width, height: 172.0)
+            
+            var animatedTextItems: [AnimatedTextComponent.Item] = []
+            animatedTextItems.append(AnimatedTextComponent.Item(
+                id: 1,
+                isUnbreakable: true,
+                content: .number(Int(component.count), minDigits: 1)
+            ))
+            
+            let titleSize = self.title.update(
+                transition: .easeInOut(duration: 0.2),
+                component: AnyComponent(
+                    AnimatedTextComponent(
+                        font: Font.with(size: 48.0, design: .round, weight: .semibold),
+                        color: component.theme.list.itemPrimaryTextColor,
+                        items: animatedTextItems
+                    )
+//                    MultilineTextComponent(
+//                        text: .plain(NSAttributedString(string: "\(component.count)", font: Font.with(size: 48.0, design: .round, weight: .semibold), textColor: component.theme.list.itemPrimaryTextColor)),
+//                        horizontalAlignment: .center
+//                    )
+                ),
+                environment: {},
+                containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
+            )
+            if let titleView = self.title.view {
+                if titleView.superview == nil {
+                    self.addSubview(titleView)
+                }
+                if let icon = self.icon.image {
+                    let spacing: CGFloat = 3.0
+                    let totalWidth = titleSize.width + icon.size.width + spacing
+                    let origin = floorToScreenPixels((availableSize.width - totalWidth) / 2.0)
+                    let titleFrame = CGRect(origin: CGPoint(x: origin + icon.size.width + spacing, y: 13.0), size: titleSize)
+                    titleView.frame = titleFrame
+                    
+                    self.icon.frame = CGRect(origin: CGPoint(x: origin, y: 18.0), size: icon.size)
+                }
+            }
+            
+            let subtitleSize = self.subtitle.update(
+                transition: .immediate,
+                component: AnyComponent(
+                    MultilineTextComponent(
+                        text: .plain(NSAttributedString(string: "your balance", font: Font.regular(17.0), textColor: component.theme.list.itemSecondaryTextColor)),
+                        horizontalAlignment: .center
+                    )
+                ),
+                environment: {},
+                containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
+            )
+            if let subtitleView = self.subtitle.view {
+                if subtitleView.superview == nil {
+                    self.addSubview(subtitleView)
+                }
+                let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - subtitleSize.width) / 2.0), y: 70.0), size: subtitleSize)
+                subtitleView.frame = subtitleFrame
+            }
+            
+            let buttonSize = self.button.update(
+                transition: .immediate,
+                component: AnyComponent(
+                    SolidRoundedButtonComponent(
+                        title: "Buy More Stars",
+                        theme: SolidRoundedButtonComponent.Theme(theme: component.theme),
+                        height: 50.0,
+                        cornerRadius: 11.0,
+                        action: { [weak self] in
+                            self?.component?.buy()
+                        }
+                    )
+                ),
+                environment: {},
+                containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
+            )
+            if let buttonView = self.button.view {
+                if buttonView.superview == nil {
+                    self.addSubview(buttonView)
+                }
+                let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: size.height - buttonSize.height - sideInset), size: buttonSize)
+                buttonView.frame = buttonFrame
+            }
+            
+            return size
+        }
+    }
+    
+    func makeView() -> View {
+        return View(frame: CGRect())
+    }
+    
+    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
+        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
+    }
+}
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift
new file mode 100644
index 0000000000..b51cac1047
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift
@@ -0,0 +1,553 @@
+import Foundation
+import UIKit
+import Display
+import AsyncDisplayKit
+import ComponentFlow
+import SwiftSignalKit
+import ViewControllerComponent
+import ComponentDisplayAdapters
+import TelegramPresentationData
+import AccountContext
+import TelegramCore
+import MultilineTextComponent
+import ListActionItemComponent
+import TelegramStringFormatting
+import AvatarNode
+import BundleIconComponent
+
+final class StarsTransactionsListPanelComponent: Component {
+    typealias EnvironmentType = StarsTransactionsPanelEnvironment
+    
+    final class Item: Equatable {
+        let transaction: StarsContext.State.Transaction
+        
+        init(
+            transaction: StarsContext.State.Transaction
+        ) {
+            self.transaction = transaction
+        }
+        
+        static func ==(lhs: Item, rhs: Item) -> Bool {
+            if lhs.transaction != rhs.transaction {
+                return false
+            }
+            return true
+        }
+    }
+    
+    final class Items: Equatable {
+        let items: [Item]
+        
+        init(items: [Item]) {
+            self.items = items
+        }
+        
+        static func ==(lhs: Items, rhs: Items) -> Bool {
+            if lhs === rhs {
+                return true
+            }
+            return lhs.items == rhs.items
+        }
+    }
+    
+    let context: AccountContext
+    let items: Items?
+    let action: (StarsContext.State.Transaction) -> Void
+
+    init(
+        context: AccountContext,
+        items: Items?,
+        action: @escaping (StarsContext.State.Transaction) -> Void
+    ) {
+        self.context = context
+        self.items = items
+        self.action = action
+    }
+    
+    static func ==(lhs: StarsTransactionsListPanelComponent, rhs: StarsTransactionsListPanelComponent) -> Bool {
+        if lhs.context !== rhs.context {
+            return false
+        }
+        if lhs.items != rhs.items {
+            return false
+        }
+        return true
+    }
+    
+    private struct ItemLayout: Equatable {
+        let containerInsets: UIEdgeInsets
+        let containerWidth: CGFloat
+        let itemHeight: CGFloat
+        let itemCount: Int
+        
+        let contentHeight: CGFloat
+        
+        init(
+            containerInsets: UIEdgeInsets,
+            containerWidth: CGFloat,
+            itemHeight: CGFloat,
+            itemCount: Int
+        ) {
+            self.containerInsets = containerInsets
+            self.containerWidth = containerWidth
+            self.itemHeight = itemHeight
+            self.itemCount = itemCount
+            
+            self.contentHeight = containerInsets.top + containerInsets.bottom + CGFloat(itemCount) * itemHeight
+        }
+        
+        func visibleItems(for rect: CGRect) -> Range<Int>? {
+            let offsetRect = rect.offsetBy(dx: -self.containerInsets.left, dy: -self.containerInsets.top)
+            var minVisibleRow = Int(floor((offsetRect.minY) / (self.itemHeight)))
+            minVisibleRow = max(0, minVisibleRow)
+            let maxVisibleRow = Int(ceil((offsetRect.maxY) / (self.itemHeight)))
+            
+            let minVisibleIndex = minVisibleRow
+            let maxVisibleIndex = maxVisibleRow
+            
+            if maxVisibleIndex >= minVisibleIndex {
+                return minVisibleIndex ..< (maxVisibleIndex + 1)
+            } else {
+                return nil
+            }
+        }
+        
+        func itemFrame(for index: Int) -> CGRect {
+            return CGRect(origin: CGPoint(x: 0.0, y: self.containerInsets.top + CGFloat(index) * self.itemHeight), size: CGSize(width: self.containerWidth, height: self.itemHeight))
+        }
+    }
+    
+    private final class ScrollViewImpl: UIScrollView {
+        override func touchesShouldCancel(in view: UIView) -> Bool {
+            return true
+        }
+    }
+    
+    class View: UIView, UIScrollViewDelegate {
+        private let scrollView: ScrollViewImpl
+        
+        private let measureItem = ComponentView<Empty>()
+        private var visibleItems: [String: ComponentView<Empty>] = [:]
+        private var separatorViews: [String: UIView] = [:]
+        
+        private var ignoreScrolling: Bool = false
+        
+        private var component: StarsTransactionsListPanelComponent?
+        private var environment: StarsTransactionsPanelEnvironment?
+        private var itemLayout: ItemLayout?
+        
+        override init(frame: CGRect) {
+            self.scrollView = ScrollViewImpl()
+            
+            super.init(frame: frame)
+            
+            self.scrollView.delaysContentTouches = true
+            self.scrollView.canCancelContentTouches = true
+            self.scrollView.clipsToBounds = false
+            if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
+                self.scrollView.contentInsetAdjustmentBehavior = .never
+            }
+            if #available(iOS 13.0, *) {
+                self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
+            }
+            self.scrollView.showsVerticalScrollIndicator = true
+            self.scrollView.showsHorizontalScrollIndicator = false
+            self.scrollView.alwaysBounceHorizontal = false
+            self.scrollView.scrollsToTop = false
+            self.scrollView.delegate = self
+            self.scrollView.clipsToBounds = true
+            self.addSubview(self.scrollView)
+        }
+        
+        required init?(coder: NSCoder) {
+            fatalError("init(coder:) has not been implemented")
+        }
+        
+        func scrollViewDidScroll(_ scrollView: UIScrollView) {
+            if !self.ignoreScrolling {
+                self.updateScrolling(transition: .immediate)
+            }
+        }
+        
+        func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+            cancelContextGestures(view: scrollView)
+        }
+        
+        private func updateScrolling(transition: Transition) {
+            guard let component = self.component, let environment = self.environment, let items = component.items, let itemLayout = self.itemLayout else {
+                return
+            }
+            
+            let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -100.0)
+                        
+            var validIds = Set<String>()
+            if let visibleItems = itemLayout.visibleItems(for: visibleBounds) {
+                for index in visibleItems.lowerBound ..< visibleItems.upperBound {
+                    if index >= items.items.count {
+                        continue
+                    }
+                    let item = items.items[index]
+                    let id = item.transaction.id
+                    validIds.insert(id)
+                    
+                    var itemTransition = transition
+                    let itemView: ComponentView<Empty>
+                    let separatorView: UIView
+                    if let current = self.visibleItems[id], let currentSeparator = self.separatorViews[id] {
+                        itemView = current
+                        separatorView = currentSeparator
+                    } else {
+                        itemTransition = .immediate
+                        itemView = ComponentView()
+                        self.visibleItems[id] = itemView
+                        
+                        separatorView = UIView()
+                        self.separatorViews[id] = separatorView
+                        self.addSubview(separatorView)
+                    }
+                    
+                    separatorView.backgroundColor = environment.theme.list.itemBlocksSeparatorColor
+                                  
+                    let fontBaseDisplaySize = 17.0
+                    
+                    let itemTitle: String
+                    let itemSubtitle: String
+                    let itemLabel: NSAttributedString
+                    switch item.transaction.peer {
+                    case let .peer(peer):
+                        itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
+                        itemLabel = NSAttributedString(string: "- \(item.transaction.count * -1)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemDestructiveColor)
+                    case .appStore:
+                        itemTitle = "In-App Purchase"
+                        itemLabel = NSAttributedString(string: "+ \(item.transaction.count)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemDisclosureActions.constructive.fillColor)
+                    case .playMarket:
+                        itemTitle = "Play Market"
+                        itemLabel = NSAttributedString(string: "+ \(item.transaction.count)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemDisclosureActions.constructive.fillColor)
+                    case .fragment:
+                        itemTitle = "Fragment"
+                        itemLabel = NSAttributedString(string: "+ \(item.transaction.count)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemDisclosureActions.constructive.fillColor)
+                    }
+                    itemSubtitle = stringForMediumCompactDate(timestamp: item.transaction.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat)
+                    
+                    let _ = itemView.update(
+                        transition: itemTransition,
+                        component: AnyComponent(ListActionItemComponent(
+                            theme: environment.theme,
+                            title: AnyComponent(VStack([
+                                AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
+                                    text: .plain(NSAttributedString(
+                                        string: itemTitle,
+                                        font: Font.semibold(fontBaseDisplaySize),
+                                        textColor: environment.theme.list.itemPrimaryTextColor
+                                    )),
+                                    maximumNumberOfLines: 0
+                                ))),
+                                AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
+                                    text: .plain(NSAttributedString(
+                                        string: itemSubtitle,
+                                        font: Font.regular(floor(fontBaseDisplaySize * 14.0 / 17.0)),
+                                        textColor: environment.theme.list.itemSecondaryTextColor
+                                    )),
+                                    maximumNumberOfLines: 0,
+                                    lineSpacing: 0.18
+                                )))
+                            ], alignment: .left, spacing: 2.0)),
+                            contentInsets: UIEdgeInsets(top: 11.0, left: 0.0, bottom: 11.0, right: 0.0),
+                            leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(AvatarComponent(context: component.context, theme: environment.theme, peer: item.transaction.peer)))),
+                            icon: nil,
+                            accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(LabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
+                            action: { [weak self] _ in
+                                guard let self, let component = self.component else {
+                                    return
+                                }
+                                component.action(item.transaction)
+                            }
+                        )),
+                        environment: {},
+                        containerSize: CGSize(width: itemLayout.containerWidth, height: itemLayout.itemHeight)
+                    )
+                    let itemFrame = itemLayout.itemFrame(for: index)
+                    if let itemComponentView = itemView.view {
+                        if itemComponentView.superview == nil {
+                            self.scrollView.addSubview(itemComponentView)
+                        }
+                        itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
+                    }
+                    let sideInset: CGFloat = 60.0
+                    itemTransition.setFrame(view: separatorView, frame: CGRect(x: sideInset, y: itemFrame.maxY, width: itemFrame.width - sideInset, height: UIScreenPixel))
+                }
+            }
+            
+            var removeIds: [String] = []
+            for (id, itemView) in self.visibleItems {
+                if !validIds.contains(id) {
+                    removeIds.append(id)
+                    if let itemComponentView = itemView.view {
+                        transition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in
+                            itemComponentView?.removeFromSuperview()
+                        })
+                    }
+                }
+            }
+            for (id, separatorView) in self.separatorViews {
+                if !validIds.contains(id) {
+                    transition.setAlpha(view: separatorView, alpha: 0.0, completion: { [weak separatorView] _ in
+                        separatorView?.removeFromSuperview()
+                    })
+                }
+            }
+            for id in removeIds {
+                self.visibleItems.removeValue(forKey: id)
+            }
+        }
+        
+        func update(component: StarsTransactionsListPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StarsTransactionsPanelEnvironment>, transition: Transition) -> CGSize {
+            self.component = component
+            
+            let environment = environment[StarsTransactionsPanelEnvironment.self].value
+            self.environment = environment
+            
+            let fontBaseDisplaySize = 17.0
+            let measureItemSize = self.measureItem.update(
+                transition: .immediate,
+                component: AnyComponent(ListActionItemComponent(
+                    theme: environment.theme,
+                    title: AnyComponent(VStack([
+                        AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
+                            text: .plain(NSAttributedString(
+                                string: "ABC",
+                                font: Font.regular(fontBaseDisplaySize),
+                                textColor: environment.theme.list.itemPrimaryTextColor
+                            )),
+                            maximumNumberOfLines: 0
+                        ))),
+                        AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
+                            text: .plain(NSAttributedString(
+                                string: "abc",
+                                font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)),
+                                textColor: environment.theme.list.itemSecondaryTextColor
+                            )),
+                            maximumNumberOfLines: 0,
+                            lineSpacing: 0.18
+                        )))
+                    ], alignment: .left, spacing: 2.0)),
+                    leftIcon: nil,
+                    icon: nil,
+                    accessory: nil,
+                    action: { _ in }
+                )),
+                environment: {},
+                containerSize: CGSize(width: availableSize.width, height: 1000.0)
+            )
+            
+            let itemLayout = ItemLayout(
+                containerInsets: environment.containerInsets,
+                containerWidth: availableSize.width,
+                itemHeight: measureItemSize.height,
+                itemCount: component.items?.items.count ?? 0
+            )
+            self.itemLayout = itemLayout
+            
+            self.ignoreScrolling = true
+            let contentOffset = self.scrollView.bounds.minY
+            transition.setPosition(view: self.scrollView, position: CGRect(origin: CGPoint(), size: availableSize).center)
+            var scrollBounds = self.scrollView.bounds
+            scrollBounds.size = availableSize
+            if !environment.isScrollable {
+                scrollBounds.origin = CGPoint()
+            }
+            transition.setBounds(view: self.scrollView, bounds: scrollBounds)
+            self.scrollView.isScrollEnabled = environment.isScrollable
+            let contentSize = CGSize(width: availableSize.width, height: itemLayout.contentHeight)
+            if self.scrollView.contentSize != contentSize {
+                self.scrollView.contentSize = contentSize
+            }
+            self.scrollView.scrollIndicatorInsets = environment.containerInsets
+            if !transition.animation.isImmediate && self.scrollView.bounds.minY != contentOffset {
+                let deltaOffset = self.scrollView.bounds.minY - contentOffset
+                transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: -deltaOffset), to: CGPoint(), additive: true)
+            }
+            self.ignoreScrolling = false
+            self.updateScrolling(transition: transition)
+            
+            return availableSize
+        }
+    }
+    
+    func makeView() -> View {
+        return View(frame: CGRect())
+    }
+    
+    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StarsTransactionsPanelEnvironment>, transition: Transition) -> CGSize {
+        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
+    }
+}
+
+func cancelContextGestures(view: UIView) {
+    if let gestureRecognizers = view.gestureRecognizers {
+        for gesture in gestureRecognizers {
+            if let gesture = gesture as? ContextGesture {
+                gesture.cancel()
+            }
+        }
+    }
+    for subview in view.subviews {
+        cancelContextGestures(view: subview)
+    }
+}
+
+private final class AvatarComponent: Component {
+    let context: AccountContext
+    let theme: PresentationTheme
+    let peer: StarsContext.State.Transaction.Peer
+
+    init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer) {
+        self.context = context
+        self.theme = theme
+        self.peer = peer
+    }
+
+    static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool {
+        if lhs.context !== rhs.context {
+            return false
+        }
+        if lhs.theme !== rhs.theme {
+            return false
+        }
+        if lhs.peer != rhs.peer {
+            return false
+        }
+        return true
+    }
+
+    final class View: UIView {
+        private let avatarNode: AvatarNode
+        private let backgroundView = UIImageView()
+        private let iconView = UIImageView()
+        
+        private var component: AvatarComponent?
+        private weak var state: EmptyComponentState?
+        
+        override init(frame: CGRect) {
+            self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 16.0))
+            
+            super.init(frame: frame)
+            
+            self.iconView.contentMode = .center
+            self.iconView.image = UIImage(bundleImageName: "Premium/Stars/TopUp")
+            
+            self.addSubnode(self.avatarNode)
+            self.addSubview(self.backgroundView)
+            self.addSubview(self.iconView)
+        }
+        
+        required init?(coder: NSCoder) {
+            fatalError("init(coder:) has not been implemented")
+        }
+        
+        func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
+            self.component = component
+            self.state = state
+            
+            let size = CGSize(width: 40.0, height: 40.0)
+
+            let gradientImage = generateGradientFilledCircleImage(diameter: size.width, colors: [UIColor(rgb: 0xf67447).cgColor, UIColor(rgb: 0xfdbe1c).cgColor], direction: .mirroredDiagonal)
+            
+            switch component.peer {
+            case let .peer(peer):
+                self.avatarNode.setPeer(
+                    context: component.context,
+                    theme: component.theme,
+                    peer: peer,
+                    synchronousLoad: true
+                )
+                self.backgroundView.isHidden = true
+                self.iconView.isHidden = true
+                self.avatarNode.isHidden = false
+            case .appStore:
+                self.backgroundView.image = gradientImage
+                self.backgroundView.isHidden = false
+                self.iconView.isHidden = false
+                self.avatarNode.isHidden = true
+            case .playMarket:
+                self.backgroundView.image = gradientImage
+                self.backgroundView.isHidden = false
+                self.iconView.isHidden = false
+                self.avatarNode.isHidden = true
+            case .fragment:
+                self.backgroundView.image = gradientImage
+                self.backgroundView.isHidden = false
+                self.iconView.isHidden = false
+                self.avatarNode.isHidden = true
+            }
+            
+            self.avatarNode.frame = CGRect(origin: .zero, size: size)
+            self.iconView.frame = CGRect(origin: .zero, size: size)
+            self.backgroundView.frame = CGRect(origin: .zero, size: size)
+
+            return size
+        }
+    }
+
+    func makeView() -> View {
+        return View(frame: CGRect())
+    }
+
+    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
+        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
+    }
+}
+
+private final class LabelComponent: CombinedComponent {
+    let text: NSAttributedString
+    
+    init(
+        text: NSAttributedString
+    ) {
+        self.text = text
+    }
+    
+    static func ==(lhs: LabelComponent, rhs: LabelComponent) -> Bool {
+        if lhs.text != rhs.text {
+            return false
+        }
+        return true
+    }
+    
+    static var body: Body {
+        let text = Child(MultilineTextComponent.self)
+        let icon = Child(BundleIconComponent.self)
+
+        return { context in
+            let component = context.component
+        
+            let text = text.update(
+                component: MultilineTextComponent(text: .plain(component.text)),
+                availableSize: CGSize(width: 100.0, height: 40.0),
+                transition: context.transition
+            )
+            
+            let iconSize = CGSize(width: 20.0, height: 20.0)
+            let icon = icon.update(
+                component: BundleIconComponent(
+                    name: "Premium/Stars/Star",
+                    tintColor: nil
+                ),
+                availableSize: iconSize,
+                transition: context.transition
+            )
+            
+            let spacing: CGFloat = 3.0
+            let totalWidth = text.size.width + spacing + iconSize.width
+            let size = CGSize(width: totalWidth, height: iconSize.height)
+            
+            context.add(text
+                .position(CGPoint(x: text.size.width / 2.0, y: size.height / 2.0))
+            )
+            context.add(icon
+                .position(CGPoint(x: totalWidth - iconSize.width / 2.0, y: size.height / 2.0))
+            )
+            return size
+        }
+    }
+}
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift
new file mode 100644
index 0000000000..e295255123
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift
@@ -0,0 +1,795 @@
+import Foundation
+import UIKit
+import Display
+import ComponentFlow
+import ComponentDisplayAdapters
+import TelegramPresentationData
+
+final class StarsTransactionsPanelContainerEnvironment: Equatable {
+    let isScrollable: Bool
+    
+    init(
+        isScrollable: Bool
+    ) {
+        self.isScrollable = isScrollable
+    }
+
+    static func ==(lhs: StarsTransactionsPanelContainerEnvironment, rhs: StarsTransactionsPanelContainerEnvironment) -> Bool {
+        if lhs.isScrollable != rhs.isScrollable {
+            return false
+        }
+        return true
+    }
+}
+
+final class StarsTransactionsPanelEnvironment: Equatable {
+    let theme: PresentationTheme
+    let strings: PresentationStrings
+    let dateTimeFormat: PresentationDateTimeFormat
+    let containerInsets: UIEdgeInsets
+    let isScrollable: Bool
+    
+    init(
+        theme: PresentationTheme,
+        strings: PresentationStrings,
+        dateTimeFormat: PresentationDateTimeFormat,
+        containerInsets: UIEdgeInsets,
+        isScrollable: Bool
+    ) {
+        self.theme = theme
+        self.strings = strings
+        self.dateTimeFormat = dateTimeFormat
+        self.containerInsets = containerInsets
+        self.isScrollable = isScrollable
+    }
+
+    static func ==(lhs: StarsTransactionsPanelEnvironment, rhs: StarsTransactionsPanelEnvironment) -> Bool {
+        if lhs.theme !== rhs.theme {
+            return false
+        }
+        if lhs.strings !== rhs.strings {
+            return false
+        }
+        if lhs.dateTimeFormat != rhs.dateTimeFormat {
+            return false
+        }
+        if lhs.containerInsets != rhs.containerInsets {
+            return false
+        }
+        if lhs.isScrollable != rhs.isScrollable {
+            return false
+        }
+        return true
+    }
+}
+
+private final class StarsTransactionsHeaderItemComponent: CombinedComponent {
+    let theme: PresentationTheme
+    let title: String
+    let activityFraction: CGFloat
+    
+    init(
+        theme: PresentationTheme,
+        title: String,
+        activityFraction: CGFloat
+    ) {
+        self.theme = theme
+        self.title = title
+        self.activityFraction = activityFraction
+    }
+    
+    static func ==(lhs: StarsTransactionsHeaderItemComponent, rhs: StarsTransactionsHeaderItemComponent) -> Bool {
+        if lhs.theme !== rhs.theme {
+            return false
+        }
+        if lhs.title != rhs.title {
+            return false
+        }
+        if lhs.activityFraction != rhs.activityFraction {
+            return false
+        }
+        return true
+    }
+    
+    static var body: Body {
+        let activeText = Child(Text.self)
+        let inactiveText = Child(Text.self)
+        
+        return { context in
+            let activeText = activeText.update(
+                component: Text(text: context.component.title, font: Font.medium(14.0), color: context.component.theme.list.itemAccentColor),
+                availableSize: context.availableSize,
+                transition: .immediate
+            )
+            let inactiveText = inactiveText.update(
+                component: Text(text: context.component.title, font: Font.medium(14.0), color: context.component.theme.list.itemSecondaryTextColor),
+                availableSize: context.availableSize,
+                transition: .immediate
+            )
+            
+            context.add(activeText
+                .position(CGPoint(x: activeText.size.width * 0.5, y: activeText.size.height * 0.5))
+                .opacity(context.component.activityFraction)
+            )
+            context.add(inactiveText
+                .position(CGPoint(x: inactiveText.size.width * 0.5, y: inactiveText.size.height * 0.5))
+                .opacity(1.0 - context.component.activityFraction)
+            )
+            
+            return activeText.size
+        }
+    }
+}
+
+private extension CGFloat {
+    func interpolate(with other: CGFloat, fraction: CGFloat) -> CGFloat {
+        let invT = 1.0 - fraction
+        let result = other * fraction + self * invT
+        return result
+    }
+}
+
+private extension CGPoint {
+    func interpolate(with other: CGPoint, fraction: CGFloat) -> CGPoint {
+        return CGPoint(x: self.x.interpolate(with: other.x, fraction: fraction), y: self.y.interpolate(with: other.y, fraction: fraction))
+    }
+}
+
+private extension CGSize {
+    func interpolate(with other: CGSize, fraction: CGFloat) -> CGSize {
+        return CGSize(width: self.width.interpolate(with: other.width, fraction: fraction), height: self.height.interpolate(with: other.height, fraction: fraction))
+    }
+}
+
+private extension CGRect {
+    func interpolate(with other: CGRect, fraction: CGFloat) -> CGRect {
+        return CGRect(origin: self.origin.interpolate(with: other.origin, fraction: fraction), size: self.size.interpolate(with: other.size, fraction: fraction))
+    }
+}
+
+private final class StarsTransactionsHeaderComponent: Component {
+    struct Item: Equatable {
+        let id: AnyHashable
+        let title: String
+
+        init(
+            id: AnyHashable,
+            title: String
+        ) {
+            self.id = id
+            self.title = title
+        }
+    }
+
+    let theme: PresentationTheme
+    let items: [Item]
+    let activeIndex: Int
+    let transitionFraction: CGFloat
+    let switchToPanel: (AnyHashable) -> Void
+    
+    init(
+        theme: PresentationTheme,
+        items: [Item],
+        activeIndex: Int,
+        transitionFraction: CGFloat,
+        switchToPanel: @escaping (AnyHashable) -> Void
+    ) {
+        self.theme = theme
+        self.items = items
+        self.activeIndex = activeIndex
+        self.transitionFraction = transitionFraction
+        self.switchToPanel = switchToPanel
+    }
+    
+    static func ==(lhs: StarsTransactionsHeaderComponent, rhs: StarsTransactionsHeaderComponent) -> Bool {
+        if lhs.theme !== rhs.theme {
+            return false
+        }
+        if lhs.items != rhs.items {
+            return false
+        }
+        if lhs.activeIndex != rhs.activeIndex {
+            return false
+        }
+        if lhs.transitionFraction != rhs.transitionFraction {
+            return false
+        }
+        return true
+    }
+    
+    class View: UIView {
+        private var component: StarsTransactionsHeaderComponent?
+        
+        private var visibleItems: [AnyHashable: ComponentView<Empty>] = [:]
+        private let activeItemLayer: SimpleLayer
+        
+        override init(frame: CGRect) {
+            self.activeItemLayer = SimpleLayer()
+            self.activeItemLayer.cornerRadius = 2.0
+            self.activeItemLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+            
+            super.init(frame: frame)
+            
+            self.layer.addSublayer(self.activeItemLayer)
+            
+            self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
+        }
+        
+        required init?(coder: NSCoder) {
+            fatalError("init(coder:) has not been implemented")
+        }
+        
+        @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
+            if case .ended = recognizer.state {
+                let point = recognizer.location(in: self)
+                var closestId: (CGFloat, AnyHashable)?
+                if self.bounds.contains(point) {
+                    for (id, item) in self.visibleItems {
+                        if let itemView = item.view {
+                            let distance: CGFloat = min(abs(point.x - itemView.frame.minX), abs(point.x - itemView.frame.maxX))
+                            if let closestIdValue = closestId {
+                                if distance < closestIdValue.0 {
+                                    closestId = (distance, id)
+                                }
+                            } else {
+                                closestId = (distance, id)
+                            }
+                        }
+                    }
+                }
+                if let closestId = closestId, let component = self.component {
+                    component.switchToPanel(closestId.1)
+                }
+            }
+        }
+        
+        func update(component: StarsTransactionsHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
+            let themeUpdated = self.component?.theme !== component.theme
+            
+            self.component = component
+            
+            var validIds = Set<AnyHashable>()
+            for i in 0 ..< component.items.count {
+                let item = component.items[i]
+                validIds.insert(item.id)
+                
+                let itemView: ComponentView<Empty>
+                var itemTransition = transition
+                if let current = self.visibleItems[item.id] {
+                    itemView = current
+                } else {
+                    itemTransition = .immediate
+                    itemView = ComponentView()
+                    self.visibleItems[item.id] = itemView
+                }
+                
+                let activeIndex: CGFloat = CGFloat(component.activeIndex) - component.transitionFraction
+                let activityDistance: CGFloat = abs(activeIndex - CGFloat(i))
+                
+                let activityFraction: CGFloat
+                if activityDistance < 1.0 {
+                    activityFraction = 1.0 - activityDistance
+                } else {
+                    activityFraction = 0.0
+                }
+                
+                let itemSize = itemView.update(
+                    transition: itemTransition,
+                    component: AnyComponent(StarsTransactionsHeaderItemComponent(
+                        theme: component.theme,
+                        title: item.title,
+                        activityFraction: activityFraction
+                    )),
+                    environment: {},
+                    containerSize: availableSize
+                )
+                
+                let itemHorizontalSpace = availableSize.width / CGFloat(component.items.count)
+                let itemX: CGFloat
+                if component.items.count == 1 {
+                    itemX = 37.0
+                } else {
+                    itemX = itemHorizontalSpace * CGFloat(i) + floor((itemHorizontalSpace - itemSize.width) / 2.0)
+                }
+                
+                let itemFrame = CGRect(origin: CGPoint(x: itemX, y: floor((availableSize.height - itemSize.height) / 2.0)), size: itemSize)
+                if let itemComponentView = itemView.view {
+                    if itemComponentView.superview == nil {
+                        self.addSubview(itemComponentView)
+                        itemComponentView.isUserInteractionEnabled = false
+                    }
+                    itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
+                }
+            }
+            
+            if component.activeIndex < component.items.count {
+                let activeView = self.visibleItems[component.items[component.activeIndex].id]?.view
+                let nextIndex: Int
+                if component.transitionFraction > 0.0 {
+                    nextIndex = max(0, component.activeIndex - 1)
+                } else {
+                    nextIndex = min(component.items.count - 1, component.activeIndex + 1)
+                }
+                let nextView = self.visibleItems[component.items[nextIndex].id]?.view
+                if let activeView = activeView, let nextView = nextView {
+                    let mergedFrame = activeView.frame.interpolate(with: nextView.frame, fraction: abs(component.transitionFraction))
+                    transition.setFrame(layer: self.activeItemLayer, frame: CGRect(origin: CGPoint(x: mergedFrame.minX, y: availableSize.height - 3.0), size: CGSize(width: mergedFrame.width, height: 3.0)))
+                }
+            }
+            
+            if themeUpdated {
+                self.activeItemLayer.backgroundColor = component.theme.list.itemAccentColor.cgColor
+            }
+            
+            var removeIds: [AnyHashable] = []
+            for (id, itemView) in self.visibleItems {
+                if !validIds.contains(id) {
+                    removeIds.append(id)
+                    if let itemComponentView = itemView.view {
+                        itemComponentView.removeFromSuperview()
+                    }
+                }
+            }
+            for id in removeIds {
+                self.visibleItems.removeValue(forKey: id)
+            }
+            
+            return availableSize
+        }
+    }
+    
+    func makeView() -> View {
+        return View(frame: CGRect())
+    }
+    
+    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
+        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
+    }
+}
+
+final class StarsTransactionsPanelContainerComponent: Component {
+    typealias EnvironmentType = StarsTransactionsPanelContainerEnvironment
+    
+    struct Item: Equatable {
+        let id: AnyHashable
+        let title: String
+        let panel: AnyComponent<StarsTransactionsPanelEnvironment>
+
+        init(
+            id: AnyHashable,
+            title: String,
+            panel: AnyComponent<StarsTransactionsPanelEnvironment>
+        ) {
+            self.id = id
+            self.title = title
+            self.panel = panel
+        }
+    }
+
+    let theme: PresentationTheme
+    let strings: PresentationStrings
+    let dateTimeFormat: PresentationDateTimeFormat
+    let insets: UIEdgeInsets
+    let items: [Item]
+    let currentPanelUpdated: (AnyHashable, Transition) -> Void
+    
+    init(
+        theme: PresentationTheme,
+        strings: PresentationStrings,
+        dateTimeFormat: PresentationDateTimeFormat,
+        insets: UIEdgeInsets,
+        items: [Item],
+        currentPanelUpdated: @escaping (AnyHashable, Transition) -> Void
+    ) {
+        self.theme = theme
+        self.strings = strings
+        self.dateTimeFormat = dateTimeFormat
+        self.insets = insets
+        self.items = items
+        self.currentPanelUpdated = currentPanelUpdated
+    }
+    
+    static func ==(lhs: StarsTransactionsPanelContainerComponent, rhs: StarsTransactionsPanelContainerComponent) -> Bool {
+        if lhs.theme !== rhs.theme {
+            return false
+        }
+        if lhs.strings !== rhs.strings {
+            return false
+        }
+        if lhs.dateTimeFormat != rhs.dateTimeFormat {
+            return false
+        }
+        if lhs.insets != rhs.insets {
+            return false
+        }
+        if lhs.items != rhs.items {
+            return false
+        }
+        return true
+    }
+    
+    class View: UIView, UIGestureRecognizerDelegate {
+        private let topPanelBackgroundView: UIView
+        private let topPanelMergedBackgroundView: UIView
+        private let topPanelSeparatorLayer: SimpleLayer
+        private let header = ComponentView<Empty>()
+        
+        private var component: StarsTransactionsPanelContainerComponent?
+        private weak var state: EmptyComponentState?
+        
+        private let panelsBackgroundLayer: SimpleLayer
+        private var visiblePanels: [AnyHashable: ComponentView<StarsTransactionsPanelEnvironment>] = [:]
+        private var actualVisibleIds = Set<AnyHashable>()
+        private var currentId: AnyHashable?
+        private var transitionFraction: CGFloat = 0.0
+        private var animatingTransition: Bool = false
+        
+        override init(frame: CGRect) {
+            self.topPanelBackgroundView = UIView()
+            
+            self.topPanelMergedBackgroundView = UIView()
+            self.topPanelMergedBackgroundView.alpha = 0.0
+            
+            self.topPanelSeparatorLayer = SimpleLayer()
+            
+            self.panelsBackgroundLayer = SimpleLayer()
+            
+            super.init(frame: frame)
+            
+            self.layer.addSublayer(self.panelsBackgroundLayer)
+            self.addSubview(self.topPanelBackgroundView)
+            self.addSubview(self.topPanelMergedBackgroundView)
+            self.layer.addSublayer(self.topPanelSeparatorLayer)
+            
+            let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
+                guard let self, let component = self.component, let currentId = self.currentId else {
+                    return []
+                }
+                guard let index = component.items.firstIndex(where: { $0.id == currentId }) else {
+                    return []
+                }
+                
+                /*if strongSelf.tabsContainerNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.tabsContainerNode.view)) {
+                    return []
+                }*/
+                
+                if index == 0 {
+                    return .left
+                }
+                return [.left, .right]
+            })
+            panRecognizer.delegate = self
+            panRecognizer.delaysTouchesBegan = false
+            panRecognizer.cancelsTouchesInView = true
+            self.addGestureRecognizer(panRecognizer)
+        }
+        
+        required init?(coder: NSCoder) {
+            fatalError("init(coder:) has not been implemented")
+        }
+        
+        var currentPanelView: UIView? {
+            guard let currentId = self.currentId, let panel = self.visiblePanels[currentId] else {
+                return nil
+            }
+            return panel.view
+        }
+        
+        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+            return false
+        }
+        
+        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+            if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
+                return false
+            }
+            if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
+                return true
+            }
+            return false
+        }
+        
+        @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
+            switch recognizer.state {
+            case .began:
+                func cancelContextGestures(view: UIView) {
+                    if let gestureRecognizers = view.gestureRecognizers {
+                        for gesture in gestureRecognizers {
+                            if let gesture = gesture as? ContextGesture {
+                                gesture.cancel()
+                            }
+                        }
+                    }
+                    for subview in view.subviews {
+                        cancelContextGestures(view: subview)
+                    }
+                }
+                
+                cancelContextGestures(view: self)
+                
+                //self.animatingTransition = true
+            case .changed:
+                guard let component = self.component, let currentId = self.currentId else {
+                    return
+                }
+                guard let index = component.items.firstIndex(where: { $0.id == currentId }) else {
+                    return
+                }
+                
+                let translation = recognizer.translation(in: self)
+                var transitionFraction = translation.x / self.bounds.width
+                if index <= 0 {
+                    transitionFraction = min(0.0, transitionFraction)
+                }
+                if index >= component.items.count - 1 {
+                    transitionFraction = max(0.0, transitionFraction)
+                }
+                self.transitionFraction = transitionFraction
+                self.state?.updated(transition: .immediate)
+            case .cancelled, .ended:
+                guard let component = self.component, let currentId = self.currentId else {
+                    return
+                }
+                guard let index = component.items.firstIndex(where: { $0.id == currentId }) else {
+                    return
+                }
+                
+                let translation = recognizer.translation(in: self)
+                let velocity = recognizer.velocity(in: self)
+                var directionIsToRight: Bool?
+                if abs(velocity.x) > 10.0 {
+                    directionIsToRight = velocity.x < 0.0
+                } else {
+                    if abs(translation.x) > self.bounds.width / 2.0 {
+                        directionIsToRight = translation.x > self.bounds.width / 2.0
+                    }
+                }
+                if let directionIsToRight = directionIsToRight {
+                    var updatedIndex = index
+                    if directionIsToRight {
+                        updatedIndex = min(updatedIndex + 1, component.items.count - 1)
+                    } else {
+                        updatedIndex = max(updatedIndex - 1, 0)
+                    }
+                    self.currentId = component.items[updatedIndex].id
+                }
+                self.transitionFraction = 0.0
+                
+                let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
+                if let currentId = self.currentId {
+                    self.state?.updated(transition: transition)
+                    component.currentPanelUpdated(currentId, transition)
+                }
+                
+                self.animatingTransition = false
+                //self.currentPaneUpdated?(false)
+                
+                //self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil))
+            default:
+                break
+            }
+        }
+        
+        func updateNavigationMergeFactor(value: CGFloat, transition: Transition) {
+            transition.setAlpha(view: self.topPanelMergedBackgroundView, alpha: value)
+            transition.setAlpha(view: self.topPanelBackgroundView, alpha: 1.0 - value)
+        }
+        
+        func update(component: StarsTransactionsPanelContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StarsTransactionsPanelContainerEnvironment>, transition: Transition) -> CGSize {
+            let environment = environment[StarsTransactionsPanelContainerEnvironment.self].value
+            
+            let themeUpdated = self.component?.theme !== component.theme
+            
+            self.component = component
+            self.state = state
+            
+            if themeUpdated {
+                self.panelsBackgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor
+                self.topPanelSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
+                self.topPanelBackgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
+                self.topPanelMergedBackgroundView.backgroundColor = component.theme.rootController.navigationBar.blurredBackgroundColor
+            }
+            
+            let topPanelCoverHeight: CGFloat = 10.0
+            
+            let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -topPanelCoverHeight), size: CGSize(width: availableSize.width, height: 44.0))
+            transition.setFrame(view: self.topPanelBackgroundView, frame: topPanelFrame)
+            transition.setFrame(view: self.topPanelMergedBackgroundView, frame: topPanelFrame)
+            
+            transition.setFrame(layer: self.panelsBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY)))
+            
+            transition.setFrame(layer: self.topPanelSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
+            
+            if let currentIdValue = self.currentId, !component.items.contains(where: { $0.id == currentIdValue }) {
+                self.currentId = nil
+            }
+            if self.currentId == nil {
+                self.currentId = component.items.first?.id
+            }
+            
+            var visibleIds = Set<AnyHashable>()
+            var currentIndex: Int?
+            if let currentId = self.currentId {
+                visibleIds.insert(currentId)
+                
+                if let index = component.items.firstIndex(where: { $0.id == currentId }) {
+                    currentIndex = index
+                    if index != 0 {
+                        visibleIds.insert(component.items[index - 1].id)
+                    }
+                    if index != component.items.count - 1 {
+                        visibleIds.insert(component.items[index + 1].id)
+                    }
+                }
+            }
+            
+            let _ = self.header.update(
+                transition: transition,
+                component: AnyComponent(StarsTransactionsHeaderComponent(
+                    theme: component.theme,
+                    items: component.items.map { item -> StarsTransactionsHeaderComponent.Item in
+                        return StarsTransactionsHeaderComponent.Item(
+                            id: item.id,
+                            title: item.title
+                        )
+                    },
+                    activeIndex: currentIndex ?? 0,
+                    transitionFraction: self.transitionFraction,
+                    switchToPanel: { [weak self] id in
+                        guard let self, let component = self.component else {
+                            return
+                        }
+                        if component.items.contains(where: { $0.id == id }) {
+                            self.currentId = id
+                            let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
+                            self.state?.updated(transition: transition)
+                            component.currentPanelUpdated(id, transition)
+                        }
+                    }
+                )),
+                environment: {},
+                containerSize: topPanelFrame.size
+            )
+            if let headerView = self.header.view {
+                if headerView.superview == nil {
+                    self.addSubview(headerView)
+                }
+                transition.setFrame(view: headerView, frame: topPanelFrame)
+            }
+            
+            let childEnvironment = StarsTransactionsPanelEnvironment(
+                theme: component.theme,
+                strings: component.strings,
+                dateTimeFormat: component.dateTimeFormat,
+                containerInsets: UIEdgeInsets(top: 0.0, left: component.insets.left, bottom: component.insets.bottom, right: component.insets.right),
+                isScrollable: environment.isScrollable
+            )
+            
+            let centralPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY))
+            
+            if self.animatingTransition {
+                visibleIds = visibleIds.filter({ self.visiblePanels[$0] != nil })
+            }
+            
+            self.actualVisibleIds = visibleIds
+            
+            for (id, _) in self.visiblePanels {
+                visibleIds.insert(id)
+            }
+            
+            var validIds = Set<AnyHashable>()
+            if let currentIndex {
+                var anyAnchorOffset: CGFloat = 0.0
+                for (id, panel) in self.visiblePanels {
+                    guard let itemIndex = component.items.firstIndex(where: { $0.id == id }), let panelView = panel.view else {
+                        continue
+                    }
+                    var itemFrame = centralPanelFrame.offsetBy(dx: self.transitionFraction * availableSize.width, dy: 0.0)
+                    if itemIndex < currentIndex {
+                        itemFrame.origin.x -= itemFrame.width
+                    } else if itemIndex > currentIndex {
+                        itemFrame.origin.x += itemFrame.width
+                    }
+                    
+                    anyAnchorOffset = itemFrame.minX - panelView.frame.minX
+                    
+                    break
+                }
+                
+                for id in visibleIds {
+                    guard let itemIndex = component.items.firstIndex(where: { $0.id == id }) else {
+                        continue
+                    }
+                    let panelItem = component.items[itemIndex]
+                    
+                    var itemFrame = centralPanelFrame.offsetBy(dx: self.transitionFraction * availableSize.width, dy: 0.0)
+                    if itemIndex < currentIndex {
+                        itemFrame.origin.x -= itemFrame.width
+                    } else if itemIndex > currentIndex {
+                        itemFrame.origin.x += itemFrame.width
+                    }
+                        
+                    validIds.insert(panelItem.id)
+                    
+                    let panel: ComponentView<StarsTransactionsPanelEnvironment>
+                    var panelTransition = transition
+                    var animateInIfNeeded = false
+                    if let current = self.visiblePanels[panelItem.id] {
+                        panel = current
+                        
+                        if let panelView = panel.view, !panelView.bounds.isEmpty {
+                            var wasHidden = false
+                            if abs(panelView.frame.minX - availableSize.width) < .ulpOfOne || abs(panelView.frame.maxX - 0.0) < .ulpOfOne {
+                                wasHidden = true
+                            }
+                            var isHidden = false
+                            if abs(itemFrame.minX - availableSize.width) < .ulpOfOne || abs(itemFrame.maxX - 0.0) < .ulpOfOne {
+                                isHidden = true
+                            }
+                            if wasHidden && isHidden {
+                                panelTransition = .immediate
+                            }
+                        }
+                    } else {
+                        panelTransition = .immediate
+                        animateInIfNeeded = true
+                        
+                        panel = ComponentView()
+                        self.visiblePanels[panelItem.id] = panel
+                    }
+                    let _ = panel.update(
+                        transition: panelTransition,
+                        component: panelItem.panel,
+                        environment: {
+                            childEnvironment
+                        },
+                        containerSize: centralPanelFrame.size
+                    )
+                    if let panelView = panel.view {
+                        if panelView.superview == nil {
+                            self.insertSubview(panelView, belowSubview: self.topPanelBackgroundView)
+                        }
+                        
+                        panelTransition.setFrame(view: panelView, frame: itemFrame, completion: { [weak self] _ in
+                            guard let self else {
+                                return
+                            }
+                            if !self.actualVisibleIds.contains(id) {
+                                if let panel = self.visiblePanels[id] {
+                                    self.visiblePanels.removeValue(forKey: id)
+                                    panel.view?.removeFromSuperview()
+                                }
+                            }
+                        })
+                        if animateInIfNeeded && anyAnchorOffset != 0.0 {
+                            transition.animatePosition(view: panelView, from: CGPoint(x: -anyAnchorOffset, y: 0.0), to: CGPoint(), additive: true)
+                        }
+                    }
+                }
+            }
+            
+            var removeIds: [AnyHashable] = []
+            for (id, panel) in self.visiblePanels {
+                if !validIds.contains(id) {
+                    removeIds.append(id)
+                    if let panelView = panel.view {
+                        panelView.removeFromSuperview()
+                    }
+                }
+            }
+            for id in removeIds {
+                self.visiblePanels.removeValue(forKey: id)
+            }
+            
+            return availableSize
+        }
+    }
+    
+    func makeView() -> View {
+        return View(frame: CGRect())
+    }
+    
+    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StarsTransactionsPanelContainerEnvironment>, transition: Transition) -> CGSize {
+        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
+    }
+}
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift
new file mode 100644
index 0000000000..5009a08b8d
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift
@@ -0,0 +1,694 @@
+import Foundation
+import UIKit
+import Display
+import AsyncDisplayKit
+import ComponentFlow
+import SwiftSignalKit
+import ViewControllerComponent
+import ComponentDisplayAdapters
+import TelegramPresentationData
+import AccountContext
+import TelegramCore
+import Postbox
+import MultilineTextComponent
+import BalancedTextComponent
+import Markdown
+import PremiumStarComponent
+import ListSectionComponent
+import TextFormat
+
+final class StarsTransactionsScreenComponent: Component {
+    typealias EnvironmentType = ViewControllerComponentContainer.Environment
+    
+    let context: AccountContext
+    let starsContext: StarsContext
+    let buy: () -> Void
+    
+    init(
+        context: AccountContext,
+        starsContext: StarsContext,
+        buy: @escaping () -> Void
+    ) {
+        self.context = context
+        self.starsContext = starsContext
+        self.buy = buy
+    }
+    
+    static func ==(lhs: StarsTransactionsScreenComponent, rhs: StarsTransactionsScreenComponent) -> Bool {
+        if lhs.context !== rhs.context {
+            return false
+        }
+        if lhs.starsContext !== rhs.starsContext {
+            return false
+        }
+        return true
+    }
+    
+    private final class ScrollViewImpl: UIScrollView {
+        override func touchesShouldCancel(in view: UIView) -> Bool {
+            return true
+        }
+        
+        override var contentOffset: CGPoint {
+            set(value) {
+                var value = value
+                if value.y > self.contentSize.height - self.bounds.height {
+                    value.y = max(0.0, self.contentSize.height - self.bounds.height)
+                    self.bounces = false
+                } else {
+                    self.bounces = true
+                }
+                super.contentOffset = value
+            } get {
+                return super.contentOffset
+            }
+        }
+    }
+    
+    class View: UIView, UIScrollViewDelegate {
+        private let scrollView: ScrollViewImpl
+        
+        private var currentSelectedPanelId: AnyHashable?
+       
+        private let navigationBackgroundView: BlurredBackgroundView
+        private let navigationSeparatorLayer: SimpleLayer
+        private let navigationSeparatorLayerContainer: SimpleLayer
+        
+        private let headerView = ComponentView<Empty>()
+        private let headerOffsetContainer: UIView
+        
+        private let scrollContainerView: UIView
+        
+        private let overscroll = ComponentView<Empty>()
+        private let fade = ComponentView<Empty>()
+        private let starView = ComponentView<Empty>()
+        private let titleView = ComponentView<Empty>()
+        private let descriptionView = ComponentView<Empty>()
+        
+        private let balanceView = ComponentView<Empty>()
+        
+        private let topBalanceView = ComponentView<Empty>()
+                
+        private let panelContainer = ComponentView<StarsTransactionsPanelContainerEnvironment>()
+                                
+        private var component: StarsTransactionsScreenComponent?
+        private weak var state: EmptyComponentState?
+        private var navigationMetrics: (navigationHeight: CGFloat, statusBarHeight: CGFloat)?
+        private var controller: (() -> ViewController?)?
+        
+        private var enableVelocityTracking: Bool = false
+        private var previousVelocityM1: CGFloat = 0.0
+        private var previousVelocity: CGFloat = 0.0
+        
+        private var ignoreScrolling: Bool = false
+        
+        private var stateDisposable: Disposable?
+        private var starsState: StarsContext.State?
+        
+        override init(frame: CGRect) {
+            self.headerOffsetContainer = UIView()
+            self.headerOffsetContainer.isUserInteractionEnabled = false
+            
+            self.navigationBackgroundView = BlurredBackgroundView(color: nil, enableBlur: true)
+            self.navigationBackgroundView.alpha = 0.0
+            
+            self.navigationSeparatorLayer = SimpleLayer()
+            self.navigationSeparatorLayer.opacity = 0.0
+            self.navigationSeparatorLayerContainer = SimpleLayer()
+            self.navigationSeparatorLayerContainer.opacity = 0.0
+            
+            self.scrollContainerView = UIView()
+            self.scrollView = ScrollViewImpl()
+                                    
+            super.init(frame: frame)
+            
+            self.scrollView.delaysContentTouches = true
+            self.scrollView.canCancelContentTouches = true
+            self.scrollView.clipsToBounds = false
+            if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
+                self.scrollView.contentInsetAdjustmentBehavior = .never
+            }
+            if #available(iOS 13.0, *) {
+                self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
+            }
+            self.scrollView.showsVerticalScrollIndicator = false
+            self.scrollView.showsHorizontalScrollIndicator = false
+            self.scrollView.alwaysBounceHorizontal = false
+            self.scrollView.scrollsToTop = false
+            self.scrollView.delegate = self
+            self.scrollView.clipsToBounds = true
+            self.addSubview(self.scrollView)
+            
+            self.scrollView.addSubview(self.scrollContainerView)
+                        
+            self.addSubview(self.navigationBackgroundView)
+            
+            self.navigationSeparatorLayerContainer.addSublayer(self.navigationSeparatorLayer)
+            self.layer.addSublayer(self.navigationSeparatorLayerContainer)
+            
+            self.addSubview(self.headerOffsetContainer)
+        }
+        
+        required init?(coder: NSCoder) {
+            fatalError("init(coder:) has not been implemented")
+        }
+        
+        deinit {
+            self.stateDisposable?.dispose()
+        }
+        
+        func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+            self.enableVelocityTracking = true
+        }
+        
+        func scrollViewDidScroll(_ scrollView: UIScrollView) {
+            if !self.ignoreScrolling {
+                if self.enableVelocityTracking {
+                    self.previousVelocityM1 = self.previousVelocity
+                    if let value = (scrollView.value(forKey: (["_", "verticalVelocity"] as [String]).joined()) as? NSNumber)?.doubleValue {
+                        self.previousVelocity = CGFloat(value)
+                    }
+                }
+                
+                self.updateScrolling(transition: .immediate)
+            }
+        }
+        
+        func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
+            guard let _ = self.navigationMetrics else {
+                return
+            }
+            
+            let paneAreaExpansionDistance: CGFloat = 32.0
+            let paneAreaExpansionFinalPoint: CGFloat = scrollView.contentSize.height - scrollView.bounds.height
+            if targetContentOffset.pointee.y > paneAreaExpansionFinalPoint - paneAreaExpansionDistance && targetContentOffset.pointee.y < paneAreaExpansionFinalPoint {
+                targetContentOffset.pointee.y = paneAreaExpansionFinalPoint
+                self.enableVelocityTracking = false
+                self.previousVelocity = 0.0
+                self.previousVelocityM1 = 0.0
+            }
+        }
+                
+        private func updateScrolling(transition: Transition) {
+            let scrollBounds = self.scrollView.bounds
+            
+            let isLockedAtPanels = scrollBounds.maxY == self.scrollView.contentSize.height
+            
+            if let navigationMetrics = self.navigationMetrics {
+                let topInset: CGFloat = navigationMetrics.navigationHeight - 56.0
+                
+                let titleOffset: CGFloat
+                let titleScale: CGFloat
+                let titleOffsetDelta = (topInset + 160.0) - (navigationMetrics.statusBarHeight + (navigationMetrics.navigationHeight - navigationMetrics.statusBarHeight) / 2.0)
+                
+                var topContentOffset = self.scrollView.contentOffset.y
+                
+                let navigationBackgroundAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0
+                topContentOffset = topContentOffset + max(0.0, min(1.0, topContentOffset / titleOffsetDelta)) * 10.0
+                titleOffset = topContentOffset
+                let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta))
+                titleScale = 1.0 - fraction * 0.36
+                
+                let headerTransition: Transition = .immediate
+                
+                if let starView = self.starView.view {
+                    let starPosition = CGPoint(x: self.scrollView.frame.width / 2.0, y: topInset + starView.bounds.height / 2.0 - 30.0 - titleOffset * titleScale)
+                    
+                    headerTransition.setPosition(view: starView, position: starPosition)
+                    headerTransition.setScale(view: starView, scale: titleScale)
+                }
+                
+                if let titleView = self.titleView.view {
+                    let titlePosition = CGPoint(x: scrollBounds.width / 2.0, y: max(topInset + 160.0 - titleOffset, navigationMetrics.statusBarHeight + (navigationMetrics.navigationHeight - navigationMetrics.statusBarHeight) / 2.0))
+                    
+                    headerTransition.setPosition(view: titleView, position: titlePosition)
+                    headerTransition.setScale(view: titleView, scale: titleScale)
+                }
+                
+                let animatedTransition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
+                animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha)
+                animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha)
+                
+                let expansionDistance: CGFloat = 32.0
+                var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance
+                expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor))
+                
+                transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor)
+                if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View {
+                    panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition)
+                }
+                
+                if let topBalanceView = self.topBalanceView.view {
+                    topBalanceView.alpha = 1.0 - expansionDistanceFactor
+                }
+            }
+            
+            
+//            if let headerView = self.headerView.view, let navigationMetrics = self.navigationMetrics {
+//                var headerOffset: CGFloat = scrollBounds.minY
+//                
+//                let minY = navigationMetrics.statusBarHeight + floor((navigationMetrics.navigationHeight - navigationMetrics.statusBarHeight) / 2.0)
+//                
+//                let minOffset = headerView.center.y - minY
+//                
+//                headerOffset = min(headerOffset, minOffset)
+//                
+//                let animatedTransition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
+//                let navigationBackgroundAlpha: CGFloat = abs(headerOffset - minOffset) < 4.0 ? 1.0 : 0.0
+//                
+//                animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha)
+//                animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha)
+//                                
+//                let expansionDistance: CGFloat = 32.0
+//                var expansionDistanceFactor: CGFloat = abs(scrollBounds.maxY - self.scrollView.contentSize.height) / expansionDistance
+//                expansionDistanceFactor = max(0.0, min(1.0, expansionDistanceFactor))
+//                
+//                transition.setAlpha(layer: self.navigationSeparatorLayer, alpha: expansionDistanceFactor)
+//                if let panelContainerView = self.panelContainer.view as? StarsTransactionsPanelContainerComponent.View {
+//                    panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition)
+//                }
+//                
+//                var offsetFraction: CGFloat = abs(headerOffset - minOffset) / 60.0
+//                offsetFraction = min(1.0, max(0.0, offsetFraction))
+//                transition.setScale(view: headerView, scale: 1.0 * offsetFraction + 0.8 * (1.0 - offsetFraction))
+//                
+//                transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size))
+//            }
+            
+            let _ = self.panelContainer.updateEnvironment(
+                transition: transition,
+                environment: {
+                    StarsTransactionsPanelContainerEnvironment(isScrollable: isLockedAtPanels)
+                }
+            )
+        }
+        
+        private var previousBalance: Int64?
+        
+        private var isUpdating = false
+        func update(component: StarsTransactionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
+            self.isUpdating = true
+            defer {
+                self.isUpdating = false
+            }
+            
+            self.component = component
+            self.state = state
+            
+            var balanceUpdated = false
+            if let starsState = self.starsState {
+                if let previousBalance, starsState.balance != previousBalance {
+                    balanceUpdated = true
+                }
+                self.previousBalance = starsState.balance
+            }
+            
+            let environment = environment[ViewControllerComponentContainer.Environment.self].value
+            
+            if self.stateDisposable == nil {
+                self.stateDisposable = (component.starsContext.state
+                |> deliverOnMainQueue).start(next: { [weak self] state in
+                    guard let self else {
+                        return
+                    }
+                    self.starsState = state
+                    if !self.isUpdating {
+                        self.state?.updated()
+                    }
+                })
+            }
+            
+            var wasLockedAtPanels = false
+            if let panelContainerView = self.panelContainer.view, let navigationMetrics = self.navigationMetrics {
+                if self.scrollView.bounds.minY > 0.0 && abs(self.scrollView.bounds.minY - (panelContainerView.frame.minY - navigationMetrics.navigationHeight)) <= UIScreenPixel {
+                    wasLockedAtPanels = true
+                }
+            }
+            
+            self.controller = environment.controller
+            
+            self.navigationMetrics = (environment.navigationHeight, environment.statusBarHeight)
+            
+            self.navigationSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
+            
+            let navigationFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight))
+            self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
+            self.navigationBackgroundView.update(size: navigationFrame.size, transition: transition.containedViewLayoutTransition)
+            transition.setFrame(view: self.navigationBackgroundView, frame: navigationFrame)
+            
+            let navigationSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationFrame.maxY), size: CGSize(width: availableSize.width, height: UIScreenPixel))
+            
+            transition.setFrame(layer: self.navigationSeparatorLayerContainer, frame: navigationSeparatorFrame)
+            transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(), size: navigationSeparatorFrame.size))
+            
+            self.backgroundColor = environment.theme.list.blocksBackgroundColor
+            
+            var contentHeight: CGFloat = 0.0
+                        
+            let sideInsets: CGFloat = environment.safeInsets.left + environment.safeInsets.right + 16 * 2.0
+            let bottomInset: CGFloat = environment.safeInsets.bottom
+             
+            contentHeight += environment.statusBarHeight
+            
+            let starTransition: Transition = .immediate
+            
+            var topBackgroundColor = environment.theme.list.plainBackgroundColor
+            let bottomBackgroundColor = environment.theme.list.blocksBackgroundColor
+            if environment.theme.overallDarkAppearance {
+                topBackgroundColor = bottomBackgroundColor
+            }
+            
+            let overscrollSize = self.overscroll.update(
+                transition: .immediate,
+                component: AnyComponent(Rectangle(color: topBackgroundColor)),
+                environment: {},
+                containerSize: CGSize(width: availableSize.width, height: 1000.0)
+            )
+            let overscrollFrame = CGRect(origin: CGPoint(x: 0.0, y: -overscrollSize.height), size: overscrollSize)
+            if let overscrollView = self.overscroll.view {
+                if overscrollView.superview == nil {
+                    self.scrollView.addSubview(overscrollView)
+                }
+                starTransition.setFrame(view: overscrollView, frame: overscrollFrame)
+            }
+            
+            let fadeSize = self.fade.update(
+                transition: .immediate,
+                component: AnyComponent(RoundedRectangle(
+                    colors: [
+                        topBackgroundColor,
+                        bottomBackgroundColor
+                    ],
+                    cornerRadius: 0.0,
+                    gradientDirection: .vertical
+                )),
+                environment: {},
+                containerSize: CGSize(width: availableSize.width, height: 1000.0)
+            )
+            let fadeFrame = CGRect(origin: CGPoint(x: 0.0, y: -fadeSize.height), size: fadeSize)
+            if let fadeView = self.fade.view {
+                if fadeView.superview == nil {
+                    self.scrollView.addSubview(fadeView)
+                }
+                starTransition.setFrame(view: fadeView, frame: fadeFrame)
+            }
+                    
+            let starSize = self.starView.update(
+                transition: .immediate,
+                component: AnyComponent(PremiumStarComponent(
+                    isIntro: true,
+                    isVisible: true,
+                    hasIdleAnimations: true,
+                    colors: [
+                        UIColor(rgb: 0xea8904),
+                        UIColor(rgb: 0xf09903),
+                        UIColor(rgb: 0xfec209),
+                        UIColor(rgb: 0xfed31a)
+                    ]
+                )),
+                environment: {},
+                containerSize: CGSize(width: min(414.0, availableSize.width), height: 220.0)
+            )
+            let starFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: starSize)
+            if let starView = self.starView.view {
+                if starView.superview == nil {
+                    self.insertSubview(starView, aboveSubview: self.scrollView)
+                }
+                starTransition.setFrame(view: starView, frame: starFrame)
+            }
+                       
+            let titleSize = self.titleView.update(
+                transition: .immediate,
+                component: AnyComponent(
+                    MultilineTextComponent(
+                        text: .plain(NSAttributedString(string: "Telegram Stars", font: Font.bold(28.0), textColor: environment.theme.list.itemPrimaryTextColor)),
+                        horizontalAlignment: .center,
+                        truncationType: .end,
+                        maximumNumberOfLines: 1
+                    )
+                ),
+                environment: {},
+                containerSize: availableSize
+            )
+            if let titleView = self.titleView.view {
+                if titleView.superview == nil {
+                    self.addSubview(titleView)
+                }
+                starTransition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleSize))
+            }
+            
+            let textFont = Font.regular(14.0)
+            let boldTextFont = Font.semibold(14.0)
+            let textColor = environment.theme.actionSheet.primaryTextColor
+            let linkColor = environment.theme.actionSheet.controlAccentColor
+            let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
+                return (TelegramTextAttributes.URL, contents)
+            })
+            let balanceAttributedString = parseMarkdownIntoAttributedString(" Balance\n >  **\(starsState?.balance ?? 0)**", attributes: markdownAttributes, textAlignment: .right).mutableCopy() as! NSMutableAttributedString
+            if let range = balanceAttributedString.string.range(of: ">"), let chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: UIColor(rgb: 0xf09903)) {
+                balanceAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: balanceAttributedString.string))
+                balanceAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xf09903), range: NSRange(range, in: balanceAttributedString.string))
+                balanceAttributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: balanceAttributedString.string))
+            }
+            let topBalanceSize = self.topBalanceView.update(
+                transition: .immediate,
+                component: AnyComponent(MultilineTextComponent(
+                    text: .plain(balanceAttributedString),
+                    horizontalAlignment: .right,
+                    maximumNumberOfLines: 0,
+                    lineSpacing: 0.1
+                )),
+                environment: {},
+                containerSize: CGSize(width: 120.0, height: 100.0)
+            )
+            if let topBalanceView = self.topBalanceView.view {
+                if topBalanceView.superview == nil {
+                    topBalanceView.alpha = 0.0
+                    self.addSubview(topBalanceView)
+                }
+                starTransition.setFrame(view: topBalanceView, frame: CGRect(origin: CGPoint(x: availableSize.width - topBalanceSize.width - 16.0, y: 56.0), size: topBalanceSize))
+            }
+
+            contentHeight += 181.0
+            
+            let descriptionSize = self.descriptionView.update(
+                transition: .immediate,
+                component: AnyComponent(
+                    BalancedTextComponent(
+                        text: .plain(NSAttributedString(string: "Buy Stars to unlock content and services in miniapps on Telegram.", font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)),
+                        horizontalAlignment: .center,
+                        maximumNumberOfLines: 0,
+                        lineSpacing: 0.2
+                    )
+                ),
+                environment: {},
+                containerSize: CGSize(width: availableSize.width - sideInsets - 8.0, height: 240.0)
+            )
+            let descriptionFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - descriptionSize.width) / 2.0), y: contentHeight), size: descriptionSize)
+            if let descriptionView = self.descriptionView.view {
+                if descriptionView.superview == nil {
+                    self.scrollView.addSubview(descriptionView)
+                }
+                
+                starTransition.setFrame(view: descriptionView, frame: descriptionFrame)
+            }
+    
+            contentHeight += descriptionSize.height
+            contentHeight += 29.0
+            
+            let balanceSize = self.balanceView.update(
+                transition: .immediate,
+                component: AnyComponent(ListSectionComponent(
+                    theme: environment.theme,
+                    header: nil,
+                    footer: nil,
+                    items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(
+                        StarsBalanceComponent(
+                            theme: environment.theme,
+                            strings: environment.strings,
+                            count: self.starsState?.balance ?? 0,
+                            buy: { [weak self] in
+                                guard let self, let component = self.component else {
+                                    return
+                                }
+                                component.buy()
+                            }
+                        )
+                    ))]
+                )),
+                environment: {},
+                containerSize: CGSize(width: availableSize.width - sideInsets, height: availableSize.height)
+            )
+            let balanceFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - balanceSize.width) / 2.0), y: contentHeight), size: balanceSize)
+            if let balanceView = self.balanceView.view {
+                if balanceView.superview == nil {
+                    self.scrollView.addSubview(balanceView)
+                }
+                starTransition.setFrame(view: balanceView, frame: balanceFrame)
+            }
+            
+            contentHeight += balanceSize.height
+            contentHeight += 44.0
+            
+            //TODO: localize
+            let transactions = self.starsState?.transactions ?? []
+            let allItems = StarsTransactionsListPanelComponent.Items(
+                items: transactions.map { StarsTransactionsListPanelComponent.Item(transaction: $0) }
+            )
+            let incomingItems = StarsTransactionsListPanelComponent.Items(
+                items: transactions.filter { $0.count > 0 }.map { StarsTransactionsListPanelComponent.Item(transaction: $0) }
+            )
+            let outgoingItems = StarsTransactionsListPanelComponent.Items(
+                items: transactions.filter { $0.count < 0 }.map { StarsTransactionsListPanelComponent.Item(transaction: $0) }
+            )
+            
+            var panelItems: [StarsTransactionsPanelContainerComponent.Item] = []
+            panelItems.append(StarsTransactionsPanelContainerComponent.Item(
+                id: "all",
+                title: "All Transactions",
+                panel: AnyComponent(StarsTransactionsListPanelComponent(
+                    context: component.context,
+                    items: allItems,
+                    action: { _ in
+                    }
+                ))
+            ))
+            
+            panelItems.append(StarsTransactionsPanelContainerComponent.Item(
+                id: "incoming",
+                title: "Incoming",
+                panel: AnyComponent(StarsTransactionsListPanelComponent(
+                    context: component.context,
+                    items: incomingItems,
+                    action: { _ in
+                    }
+                ))
+            ))
+            
+            panelItems.append(StarsTransactionsPanelContainerComponent.Item(
+                id: "outgoing",
+                title: "Outgoing",
+                panel: AnyComponent(StarsTransactionsListPanelComponent(
+                    context: component.context,
+                    items: outgoingItems,
+                    action: { _ in
+                    }
+                ))
+            ))
+            
+            var panelTransition = transition
+            if balanceUpdated {
+                panelTransition = .easeInOut(duration: 0.25)
+            }
+            
+            if !panelItems.isEmpty {
+                let panelContainerSize = self.panelContainer.update(
+                    transition: panelTransition,
+                    component: AnyComponent(StarsTransactionsPanelContainerComponent(
+                        theme: environment.theme,
+                        strings: environment.strings,
+                        dateTimeFormat: environment.dateTimeFormat,
+                        insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: bottomInset, right: environment.safeInsets.right),
+                        items: panelItems,
+                        currentPanelUpdated: { [weak self] id, transition in
+                            guard let self else {
+                                return
+                            }
+                            self.currentSelectedPanelId = id
+                            self.state?.updated(transition: transition)
+                        }
+                    )),
+                    environment: {
+                        StarsTransactionsPanelContainerEnvironment(isScrollable: wasLockedAtPanels)
+                    },
+                    containerSize: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight)
+                )
+                if let panelContainerView = self.panelContainer.view {
+                    if panelContainerView.superview == nil {
+                        self.scrollContainerView.addSubview(panelContainerView)
+                    }
+                    transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: panelContainerSize))
+                }
+                contentHeight += panelContainerSize.height
+            } else {
+                self.panelContainer.view?.removeFromSuperview()
+            }
+            
+            self.ignoreScrolling = true
+            
+            let contentOffset = self.scrollView.bounds.minY
+            transition.setPosition(view: self.scrollView, position: CGRect(origin: CGPoint(), size: availableSize).center)
+            let contentSize = CGSize(width: availableSize.width, height: contentHeight)
+            if self.scrollView.contentSize != contentSize {
+                self.scrollView.contentSize = contentSize
+            }
+            transition.setFrame(view: self.scrollContainerView, frame: CGRect(origin: CGPoint(), size: contentSize))
+            
+            var scrollViewBounds = self.scrollView.bounds
+            scrollViewBounds.size = availableSize
+            if wasLockedAtPanels, let panelContainerView = self.panelContainer.view {
+                scrollViewBounds.origin.y = panelContainerView.frame.minY - environment.navigationHeight
+            }
+            transition.setBounds(view: self.scrollView, bounds: scrollViewBounds)
+            
+            if !wasLockedAtPanels && !transition.animation.isImmediate && self.scrollView.bounds.minY != contentOffset {
+                let deltaOffset = self.scrollView.bounds.minY - contentOffset
+                transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: -deltaOffset), to: CGPoint(), additive: true)
+            }
+            
+            self.ignoreScrolling = false
+            
+            self.updateScrolling(transition: transition)
+            
+            return availableSize
+        }
+    }
+    
+    func makeView() -> View {
+        return View(frame: CGRect())
+    }
+    
+    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
+        return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
+    }
+}
+
+public final class StarsTransactionsScreen: ViewControllerComponentContainer {
+    private let context: AccountContext
+    
+    private let options = Promise<[StarsTopUpOption]>()
+    
+    public init(context: AccountContext, starsContext: StarsContext, forceDark: Bool = false) {
+        self.context = context
+        
+        var buyImpl: (() -> Void)?
+        super.init(context: context, component: StarsTransactionsScreenComponent(context: context, starsContext: starsContext, buy: {
+            buyImpl?()
+        }), navigationBarAppearance: .transparent)
+        
+        self.options.set(.single([]) |> then(context.engine.payments.starsTopUpOptions()))
+        
+        buyImpl = { [weak self] in
+            guard let self else {
+                return
+            }
+            let _ = (self.options.get()
+                     |> take(1)
+                     |> deliverOnMainQueue).start(next: { [weak self] options in
+                guard let self else {
+                    return
+                }
+                let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, peerId: nil, requiredStars: nil)
+                self.push(controller)
+            })
+        }
+    }
+    
+    required public init(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override public func viewDidLoad() {
+        super.viewDidLoad()
+    }
+}
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD
new file mode 100644
index 0000000000..87f4b01dd8
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD
@@ -0,0 +1,39 @@
+load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
+
+swift_library(
+    name = "StarsTransferScreen",
+    module_name = "StarsTransferScreen",
+    srcs = glob([
+        "Sources/**/*.swift",
+    ]),
+    copts = [
+        "-warnings-as-errors",
+    ],
+    deps = [
+        "//submodules/AsyncDisplayKit",
+        "//submodules/Display",
+        "//submodules/Postbox",
+        "//submodules/TelegramCore",
+        "//submodules/SSignalKit/SwiftSignalKit",
+        "//submodules/ComponentFlow",
+        "//submodules/Components/ViewControllerComponent",
+        "//submodules/Components/ComponentDisplayAdapters",
+        "//submodules/Components/MultilineTextComponent",
+        "//submodules/Components/BalancedTextComponent",
+        "//submodules/TelegramPresentationData",
+        "//submodules/AccountContext",
+        "//submodules/AppBundle",
+        "//submodules/ItemListUI",
+        "//submodules/TelegramStringFormatting",
+        "//submodules/PresentationDataUtils",
+        "//submodules/Components/SheetComponent",
+        "//submodules/UndoUI",
+        "//submodules/TelegramUI/Components/ButtonComponent",
+        "//submodules/TelegramUI/Components/ListSectionComponent",
+        "//submodules/TelegramUI/Components/ListActionItemComponent",
+        "//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
+    ],
+    visibility = [
+        "//visibility:public",
+    ],
+)
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift
new file mode 100644
index 0000000000..6981b5290e
--- /dev/null
+++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift
@@ -0,0 +1,450 @@
+import Foundation
+import UIKit
+import Display
+import ComponentFlow
+import SwiftSignalKit
+import TelegramCore
+import Markdown
+import TextFormat
+import TelegramPresentationData
+import ViewControllerComponent
+import SheetComponent
+import BalancedTextComponent
+import MultilineTextComponent
+import ItemListUI
+import UndoUI
+import AccountContext
+import PremiumStarComponent
+import ButtonComponent
+
+private final class SheetContent: CombinedComponent {
+    typealias EnvironmentType = ViewControllerComponentContainer.Environment
+    
+    let context: AccountContext
+    let invoice: TelegramMediaInvoice
+    let source: BotPaymentInvoiceSource
+    let inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>
+    
+    init(
+        context: AccountContext,
+        invoice: TelegramMediaInvoice,
+        source: BotPaymentInvoiceSource,
+        inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>
+    ) {
+        self.context = context
+        self.invoice = invoice
+        self.source = source
+        self.inputData = inputData
+    }
+    
+    static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
+        if lhs.context !== rhs.context {
+            return false
+        }
+        if lhs.invoice != rhs.invoice {
+            return false
+        }
+        return true
+    }
+    
+    final class State: ComponentState {
+        var cachedCloseImage: (UIImage, PresentationTheme)?
+        var cachedChevronImage: (UIImage, PresentationTheme)?
+        var cachedStarImage: (UIImage, PresentationTheme)?
+        
+        private let context: AccountContext
+        private let source: BotPaymentInvoiceSource
+        
+        var peer: EnginePeer?
+        var peerDisposable: Disposable?
+        var balance: Int64?
+        var form: BotPaymentForm?
+        
+        var inProgress = false
+        
+        init(
+            context: AccountContext,
+            source: BotPaymentInvoiceSource,
+            inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>
+        ) {
+            self.context = context
+            self.source = source
+            
+            super.init()
+            
+            self.peerDisposable = (inputData
+            |> deliverOnMainQueue).start(next: { [weak self] inputData in
+                guard let self else {
+                    return
+                }
+                self.balance = inputData?.0.balance ?? 0
+                self.form = inputData?.1
+                self.peer = inputData?.2
+                self.updated(transition: .immediate)
+            })
+        }
+        
+        deinit {
+            self.peerDisposable?.dispose()
+        }
+        
+        func buy(completion: @escaping () -> Void) {
+            guard let form else {
+                return
+            }
+            self.inProgress = true
+            self.updated()
+            
+            let _ = (self.context.engine.payments.sendStarsPaymentForm(formId: form.id, source: self.source)
+            |> deliverOnMainQueue).start(next: { _ in
+                completion()
+            })
+        }
+    }
+    
+    func makeState() -> State {
+        return State(context: self.context, source: self.source, inputData: self.inputData)
+    }
+        
+    static var body: Body {
+        let background = Child(RoundedRectangle.self)
+        let star = Child(GiftAvatarComponent.self)
+        let closeButton = Child(Button.self)
+        let title = Child(Text.self)
+        let text = Child(BalancedTextComponent.self)
+        let balanceText = Child(MultilineTextComponent.self)
+        let button = Child(ButtonComponent.self)
+        
+        return { context in
+            let environment = context.environment[EnvironmentType.self]
+            let component = context.component
+            let state = context.state
+            
+            let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
+            let theme = presentationData.theme
+//            let strings = presentationData.strings
+            
+//            let sideInset: CGFloat = 16.0 + environment.safeInsets.left
+            
+            var contentSize = CGSize(width: context.availableSize.width, height: 18.0)
+                        
+            let background = background.update(
+                component: RoundedRectangle(color: theme.list.blocksBackgroundColor, cornerRadius: 8.0),
+                availableSize: CGSize(width: context.availableSize.width, height: 1000.0),
+                transition: .immediate
+            )
+            context.add(background
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0))
+            )
+            
+            if let peer = state.peer {
+                let star = star.update(
+                    component: GiftAvatarComponent(
+                        context: context.component.context,
+                        theme: environment.theme,
+                        peers: [peer],
+                        isVisible: true,
+                        hasIdleAnimations: true,
+                        hasScaleAnimation: false,
+                        color: UIColor(rgb: 0xf7ab04),
+                        offset: 40.0
+                    ),
+                    availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0),
+                    transition: context.transition
+                )
+                
+                context.add(star
+                    .position(CGPoint(x: context.availableSize.width / 2.0, y: 0.0 + star.size.height / 2.0 - 30.0))
+                )
+            }
+            
+            let closeImage: UIImage
+            if let (image, cacheTheme) = state.cachedCloseImage, theme === cacheTheme {
+                closeImage = image
+            } else {
+                closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)!
+                state.cachedCloseImage = (closeImage, theme)
+            }
+            let closeButton = closeButton.update(
+                component: Button(
+                    content: AnyComponent(Image(image: closeImage)),
+                    action: {
+//                        component.dismiss()
+                    }
+                ),
+                availableSize: CGSize(width: 30.0, height: 30.0),
+                transition: .immediate
+            )
+            context.add(closeButton
+                .position(CGPoint(x: context.availableSize.width - closeButton.size.width, y: 28.0))
+            )
+            
+            let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
+            
+            
+            contentSize.height += 130.0
+            let title = title.update(
+                component: Text(text: "Confirm Your Purchase", font: Font.bold(24.0), color: theme.list.itemPrimaryTextColor),
+                availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
+                transition: .immediate
+            )
+            context.add(title
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
+            )
+            contentSize.height += title.size.height
+            contentSize.height += 13.0
+                        
+            let textFont = Font.regular(15.0)
+            let boldTextFont = Font.semibold(15.0)
+            let textColor = theme.actionSheet.primaryTextColor
+            let linkColor = theme.actionSheet.controlAccentColor
+            let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
+                return (TelegramTextAttributes.URL, contents)
+            })
+            
+            let amount = component.invoice.totalAmount
+            let text = text.update(
+                component: BalancedTextComponent(
+                    text: .markdown(text: "Do you want to buy **\(component.invoice.title)** in **\(state.peer?.compactDisplayTitle ?? "levlam_bot")** for **\(amount) Stars**?", attributes: markdownAttributes),
+                    horizontalAlignment: .center,
+                    maximumNumberOfLines: 0,
+                    lineSpacing: 0.2
+                ),
+                availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
+                transition: .immediate
+            )
+            context.add(text
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))
+            )
+            contentSize.height += text.size.height
+            contentSize.height += 24.0
+            
+            if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== theme {
+                state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: UIColor(rgb: 0xf09903))!, theme)
+            }
+            
+            let balanceAttributedString = parseMarkdownIntoAttributedString("Balance\n >  **\(state.balance ?? 0)**", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
+            if let range = balanceAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
+                balanceAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: balanceAttributedString.string))
+                balanceAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xf09903), range: NSRange(range, in: balanceAttributedString.string))
+                balanceAttributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: balanceAttributedString.string))
+            }
+            let balanceText = balanceText.update(
+                component: MultilineTextComponent(
+                    text: .plain(balanceAttributedString),
+                    horizontalAlignment: .left,
+                    maximumNumberOfLines: 0,
+                    lineSpacing: 0.2
+                ),
+                availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
+                transition: .immediate
+            )
+            context.add(balanceText
+                .position(CGPoint(x: 16.0 + balanceText.size.width / 2.0, y: 29.0))
+            )
+            
+            if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme {
+                state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, theme)
+            }
+            
+            let buttonAttributedString = NSMutableAttributedString(string: "Confirm and Pay   >  \(amount)", font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center)
+            if let range = buttonAttributedString.string.range(of: ">"), let starImage = state.cachedStarImage?.0 {
+                buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
+                buttonAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: buttonAttributedString.string))
+                buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string))
+            }
+            
+            let controller = environment.controller() as? StarsTransferScreen
+            
+            let accountContext = component.context
+            let botTitle = state.peer?.compactDisplayTitle ?? ""
+            
+            let invoice = component.invoice
+            let button = button.update(
+                component: ButtonComponent(
+                    background: ButtonComponent.Background(
+                        color: theme.list.itemCheckColors.fillColor,
+                        foreground: theme.list.itemCheckColors.foregroundColor,
+                        pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
+                        cornerRadius: 10.0
+                    ),
+                    content: AnyComponentWithIdentity(
+                        id: AnyHashable(0),
+                        component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
+                    ),
+                    isEnabled: true,
+                    displaysProgress: state.inProgress,
+                    action: { [weak state, weak controller] in
+                        state?.buy(completion: { [weak controller] in
+                            let presentationData = accountContext.sharedContext.currentPresentationData.with { $0 }
+                            let resultController = UndoOverlayController(
+                                presentationData: presentationData,
+                                content: .image(image: UIImage(bundleImageName: "Premium/Stars/Star")!, title: "Purchase Completed", text: "You acquired **\(invoice.title)** in **\(botTitle)** for **\(invoice.totalAmount) Stars**.", round: false, undoText: nil),
+                                elevatedLayout: true,
+                                action: { _ in return true})
+                            controller?.present(resultController, in: .window(.root))
+
+                            controller?.dismissAnimated()
+                        })
+                    }
+                ),
+                availableSize: CGSize(width: 361.0, height: 50),
+                transition: .immediate
+            )
+            context.add(button
+                .clipsToBounds(true)
+                .cornerRadius(10.0)
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
+            )
+            contentSize.height += button.size.height
+            contentSize.height += 48.0
+            
+            return contentSize
+        }
+    }
+}
+
+private final class StarsTransferSheetComponent: CombinedComponent {
+    typealias EnvironmentType = ViewControllerComponentContainer.Environment
+    
+    private let context: AccountContext
+    private let invoice: TelegramMediaInvoice
+    private let source: BotPaymentInvoiceSource
+    private let inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>
+    
+    init(
+        context: AccountContext,
+        invoice: TelegramMediaInvoice,
+        source: BotPaymentInvoiceSource,
+        inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>
+    ) {
+        self.context = context
+        self.invoice = invoice
+        self.source = source
+        self.inputData = inputData
+    }
+    
+    static func ==(lhs: StarsTransferSheetComponent, rhs: StarsTransferSheetComponent) -> Bool {
+        if lhs.context !== rhs.context {
+            return false
+        }
+        if lhs.invoice != rhs.invoice {
+            return false
+        }
+        return true
+    }
+    
+    static var body: Body {
+        let sheet = Child(SheetComponent<(EnvironmentType)>.self)
+        let animateOut = StoredActionSlot(Action<Void>.self)
+        
+        return { context in
+            let environment = context.environment[EnvironmentType.self]
+            
+            let controller = environment.controller
+            
+            let sheet = sheet.update(
+                component: SheetComponent<EnvironmentType>(
+                    content: AnyComponent<EnvironmentType>(SheetContent(
+                        context: context.component.context,
+                        invoice: context.component.invoice,
+                        source: context.component.source,
+                        inputData: context.component.inputData
+                    )),
+                    backgroundColor: .blur(.light),
+                    followContentSizeChanges: true,
+                    animateOut: animateOut
+                ),
+                environment: {
+                    environment
+                    SheetComponentEnvironment(
+                        isDisplaying: environment.value.isVisible,
+                        isCentered: environment.metrics.widthClass == .regular,
+                        hasInputHeight: !environment.inputHeight.isZero,
+                        regularMetricsSize: CGSize(width: 430.0, height: 900.0),
+                        dismiss: { animated in
+                            if animated {
+                                animateOut.invoke(Action { _ in
+                                    if let controller = controller() {
+                                        controller.dismiss(completion: nil)
+                                    }
+                                })
+                            } else {
+                                if let controller = controller() {
+                                    controller.dismiss(completion: nil)
+                                }
+                            }
+                        }
+                    )
+                },
+                availableSize: context.availableSize,
+                transition: context.transition
+            )
+            
+            context.add(sheet
+                .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
+            )
+            
+            return context.availableSize
+        }
+    }
+}
+
+public final class StarsTransferScreen: ViewControllerComponentContainer {
+    private let context: AccountContext
+        
+    public init(
+        context: AccountContext,
+        invoice: TelegramMediaInvoice,
+        source: BotPaymentInvoiceSource,
+        inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>
+    ) {
+        self.context = context
+                
+        super.init(
+            context: context,
+            component: StarsTransferSheetComponent(
+                context: context,
+                invoice: invoice,
+                source: source,
+                inputData: inputData
+            ),
+            navigationBarAppearance: .none,
+            statusBarStyle: .ignore,
+            theme: .default
+        )
+        
+        self.navigationPresentation = .flatModal
+    }
+    
+    required public init(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    public func dismissAnimated() {
+        if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
+            view.dismissAnimated()
+        }
+    }
+}
+
+private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
+    return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
+        context.clear(CGRect(origin: CGPoint(), size: size))
+        
+        context.setFillColor(backgroundColor.cgColor)
+        context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
+        
+        context.setLineWidth(2.0)
+        context.setLineCap(.round)
+        context.setStrokeColor(foregroundColor.cgColor)
+        
+        context.move(to: CGPoint(x: 10.0, y: 10.0))
+        context.addLine(to: CGPoint(x: 20.0, y: 20.0))
+        context.strokePath()
+        
+        context.move(to: CGPoint(x: 20.0, y: 10.0))
+        context.addLine(to: CGPoint(x: 10.0, y: 20.0))
+        context.strokePath()
+    })
+}
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png b/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png
deleted file mode 100644
index b06e45609d..0000000000
Binary files a/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png and /dev/null differ
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Mock2.png b/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Mock2.png
deleted file mode 100644
index 431954074b..0000000000
Binary files a/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Mock2.png and /dev/null differ
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Contents.json
new file mode 100644
index 0000000000..6e965652df
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Contents.json
@@ -0,0 +1,9 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties" : {
+    "provides-namespace" : true
+  }
+}
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/Contents.json
new file mode 100644
index 0000000000..d4ec011d84
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+  "images" : [
+    {
+      "filename" : "transactionstar_20 (2).pdf",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/transactionstar_20 (2).pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/transactionstar_20 (2).pdf
new file mode 100644
index 0000000000..aefce12699
Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Star.imageset/transactionstar_20 (2).pdf differ
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json
similarity index 75%
rename from submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json
rename to submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json
index 5f6829d46c..0b71e559ab 100644
--- a/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json
+++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/Contents.json
@@ -1,7 +1,7 @@
 {
   "images" : [
     {
-      "filename" : "Mock2.png",
+      "filename" : "balancestar_48.pdf",
       "idiom" : "universal"
     }
   ],
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/balancestar_48.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/balancestar_48.pdf
new file mode 100644
index 0000000000..f55d110a3f
Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/StarLarge.imageset/balancestar_48.pdf differ
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/TopUp.imageset/Contents.json
similarity index 76%
rename from submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/Contents.json
rename to submodules/TelegramUI/Images.xcassets/Premium/Stars/TopUp.imageset/Contents.json
index ba6d692fc9..d9b19f790f 100644
--- a/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/Contents.json
+++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/TopUp.imageset/Contents.json
@@ -1,7 +1,7 @@
 {
   "images" : [
     {
-      "filename" : "mock.png",
+      "filename" : "topbalance.pdf",
       "idiom" : "universal"
     }
   ],
diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/TopUp.imageset/topbalance.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/TopUp.imageset/topbalance.pdf
new file mode 100644
index 0000000000..46857c5e60
Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/TopUp.imageset/topbalance.pdf differ
diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift
index 081f682a92..f10907c0fe 100644
--- a/submodules/TelegramUI/Sources/ChatController.swift
+++ b/submodules/TelegramUI/Sources/ChatController.swift
@@ -2913,22 +2913,45 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
                             |> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
                                 return .single(nil)
                             })
-                            strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .message(messageId), inputData: inputData, completed: { currencyValue, receiptMessageId in
-                                guard let strongSelf = self else {
-                                    return
+                            if invoice.currency == "XTR" {
+                                let statePromise = Promise<StarsContext.State?>()
+                                statePromise.set(strongSelf.context.engine.payments.peerStarsState(peerId: strongSelf.context.account.peerId))
+                                let starsInputData = combineLatest(
+                                    inputData.get(),
+                                    statePromise.get()
+                                )
+                                |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in
+                                    if let data, let state {
+                                        return (state, data.form, data.botPeer)
+                                    } else {
+                                        return nil
+                                    }
                                 }
-                                strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in
-                                    guard let strongSelf = self, let receiptMessageId = receiptMessageId else {
+                                let _ = (starsInputData |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
+                                    guard let strongSelf = self else {
+                                        return
+                                    }
+                                    let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, invoice: invoice, source: .message(messageId), inputData: starsInputData)
+                                    strongSelf.push(controller)
+                                })
+                            } else {
+                                strongSelf.present(BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .message(messageId), inputData: inputData, completed: { currencyValue, receiptMessageId in
+                                    guard let strongSelf = self else {
+                                        return
+                                    }
+                                    strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in
+                                        guard let strongSelf = self, let receiptMessageId = receiptMessageId else {
+                                            return false
+                                        }
+                                        
+                                        if case .info = action {
+                                            strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
+                                            return true
+                                        }
                                         return false
-                                    }
-
-                                    if case .info = action {
-                                        strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
-                                        return true
-                                    }
-                                    return false
-                                }), in: .current)
-                            }), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
+                                    }), in: .current)
+                                }), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
+                            }
                         }
                     }
                 }
diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift
index d452f72665..03ea02cfba 100644
--- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift
+++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift
@@ -820,25 +820,45 @@ func openResolvedUrlImpl(
                 if let navigationController = navigationController {
                     let inputData = Promise<BotCheckoutController.InputData?>()
                     inputData.set(BotCheckoutController.InputData.fetch(context: context, source: .slug(slug))
-                                  |> map(Optional.init)
-                                  |> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
+                    |> map(Optional.init)
+                    |> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
                         return .single(nil)
                     })
-                    let checkoutController = BotCheckoutController(context: context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in
-                        /*strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in
-                         guard let strongSelf = self, let receiptMessageId = receiptMessageId else {
-                         return false
-                         }
-                         
-                         if case .info = action {
-                         strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
-                         return true
-                         }
-                         return false
-                         }), in: .current)*/
-                    })
-                    checkoutController.navigationPresentation = .modal
-                    navigationController.pushViewController(checkoutController)
+                    if invoice.currency == "XTR" {
+                        let statePromise = Promise<StarsContext.State?>()
+                        statePromise.set(context.engine.payments.peerStarsState(peerId: context.account.peerId))
+                        let starsInputData = combineLatest(
+                            inputData.get(),
+                            statePromise.get()
+                        )
+                        |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in
+                            if let data, let state {
+                                return (state, data.form, data.botPeer)
+                            } else {
+                                return nil
+                            }
+                        }
+                        let _ = (starsInputData |> take(1) |> deliverOnMainQueue).start(next: { _ in
+                            let controller = context.sharedContext.makeStarsTransferScreen(context: context, invoice: invoice, source: .slug(slug), inputData: starsInputData)
+                            navigationController.pushViewController(controller)
+                        })
+                    } else {
+                        let checkoutController = BotCheckoutController(context: context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in
+                            /*strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .paymentSent(currencyValue: currencyValue, itemTitle: invoice.title), elevatedLayout: false, action: { action in
+                             guard let strongSelf = self, let receiptMessageId = receiptMessageId else {
+                             return false
+                             }
+                             
+                             if case .info = action {
+                             strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: receiptMessageId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
+                             return true
+                             }
+                             return false
+                             }), in: .current)*/
+                        })
+                        checkoutController.navigationPresentation = .modal
+                        navigationController.pushViewController(checkoutController)
+                    }
                 }
             } else {
                 present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Chat_ErrorInvoiceNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift
index 2b38c97041..879bb5e97e 100644
--- a/submodules/TelegramUI/Sources/SharedAccountContext.swift
+++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift
@@ -64,6 +64,9 @@ import TelegramNotices
 import BotSettingsScreen
 import CameraScreen
 import BirthdayPickerScreen
+import StarsTransactionsScreen
+import StarsPurchaseScreen
+import StarsTransferScreen
 
 private final class AccountUserInterfaceInUseContext {
     let subscribers = Bag<(Bool) -> Void>()
@@ -2609,6 +2612,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
     public func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController {
         return messageStatsController(context: context, updatedPresentationData: updatedPresentationData, subject: .story(peerId: peerId, id: storyId, item: storyItem, fromStory: fromStory))
     }
+    
+    public func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController {
+        return StarsTransactionsScreen(context: context, starsContext: starsContext)
+    }
+    
+    public func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [StarsTopUpOption], peerId: EnginePeer.Id?, requiredStars: Int32?) -> ViewController {
+        return StarsPurchaseScreen(context: context, starsContext: starsContext, options: options, peerId: peerId, requiredStars: requiredStars, modal: true)
+    }
+    
+    public func makeStarsTransferScreen(context: AccountContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>) -> ViewController {
+        return StarsTransferScreen(context: context, invoice: invoice, source: source, inputData: inputData)
+    }
 }
 
 private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift
index 83aa107c7c..75d2429024 100644
--- a/submodules/WebUI/Sources/WebAppController.swift
+++ b/submodules/WebUI/Sources/WebAppController.swift
@@ -857,14 +857,28 @@ public final class WebAppController: ViewController, AttachmentContainable {
                             return .single(nil)
                         }
                         |> deliverOnMainQueue).start(next: { [weak self] invoice in
-                            if let strongSelf = self, let invoice = invoice {
+                            if let strongSelf = self, let invoice, let navigationController = strongSelf.controller?.getNavigationController() {
                                 let inputData = Promise<BotCheckoutController.InputData?>()
                                 inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .slug(slug))
                                 |> map(Optional.init)
                                 |> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
                                     return .single(nil)
                                 })
-                                if let navigationController = strongSelf.controller?.getNavigationController() {
+                                if invoice.currency == "XTR" {
+                                    let starsInputData = combineLatest(
+                                        inputData.get(),
+                                        strongSelf.context.engine.payments.peerStarsState(peerId: strongSelf.context.account.peerId)
+                                    )
+                                    |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?)? in
+                                        if let data, let state {
+                                            return (state, data.form, data.botPeer)
+                                        } else {
+                                            return nil
+                                        }
+                                    }
+                                    let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, invoice: invoice, source: .slug(slug), inputData: starsInputData)
+                                    navigationController.pushViewController(controller)
+                                } else {
                                     let checkoutController = BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in
                                         self?.sendInvoiceClosedEvent(slug: slug, result: .paid)
                                     }, cancelled: { [weak self] in