diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings
index d4a7ab246d..5fb20b0aef 100644
--- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings
+++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings
@@ -12266,6 +12266,8 @@ Sorry for the inconvenience.";
 "Stars.Intro.Transaction.FragmentTopUp.Subtitle" = "via Fragment";
 "Stars.Intro.Transaction.FragmentWithdrawal.Title" = "Withdrawal";
 "Stars.Intro.Transaction.FragmentWithdrawal.Subtitle" = "via Fragment";
+"Stars.Intro.Transaction.TelegramAds.Title" = "Withdrawal";
+"Stars.Intro.Transaction.TelegramAds.Subtitle" = "via Telegram Ads";
 "Stars.Intro.Transaction.Unsupported.Title" = "Unsupported";
 "Stars.Intro.Transaction.Refund" = "Refund";
 
@@ -12310,9 +12312,19 @@ Sorry for the inconvenience.";
 "Stars.Transaction.FragmentTopUp.Subtitle" = "Fragment";
 "Stars.Transaction.FragmentWithdrawal.Title" = "Stars Withdrawal";
 "Stars.Transaction.FragmentWithdrawal.Subtitle" = "Fragment";
+"Stars.Transaction.TelegramAds.Title" = "Stars Withdrawal";
+"Stars.Transaction.TelegramAds.Subtitle" = "Telegram Ads";
 "Stars.Transaction.Unsupported.Title" = "Unsupported";
 "Stars.Transaction.Refund" = "Refund";
 
+"Stars.Transaction.Photos_1" = "%@ Photo";
+"Stars.Transaction.Photos_any" = "%@ Photos";
+"Stars.Transaction.Videos_1" = "%@ Video";
+"Stars.Transaction.Videos_any" = "%@ Videos";
+"Stars.Transaction.SinglePhoto" = "Photo";
+"Stars.Transaction.SingleVideo" = "Video";
+"Stars.Transaction.MediaAnd" = "%1$@ and %2$@";
+
 "Stars.Transfer.Title" = "Confirm Your Purchase";
 "Stars.Transfer.Info" = "Do you want to buy **%1$@** in **%2$@** for **%3$@**?";
 "Stars.Transfer.Info.Stars_1" = "%@ Star";
@@ -12329,6 +12341,14 @@ Sorry for the inconvenience.";
 
 "Stars.Transfer.Unavailable" = "Sorry, no star purchases are available from your country.";
 
+"Stars.Transfer.Photos_1" = "%@ photo";
+"Stars.Transfer.Photos_any" = "%@ photos";
+"Stars.Transfer.Videos_1" = "%@ video";
+"Stars.Transfer.Videos_any" = "%@ videos";
+"Stars.Transfer.SinglePhoto" = "photo";
+"Stars.Transfer.SingleVideo" = "video";
+"Stars.Transfer.MediaAnd" = "%1$@ and %2$@";
+
 "Settings.Stars" = "Your Stars";
 
 "Chat.MessageEffectMenu.TitleAddEffect" = "Add an animated effect";
diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift
index a19a5a0d82..0ed6b3c479 100644
--- a/submodules/AccountContext/Sources/ChatController.swift
+++ b/submodules/AccountContext/Sources/ChatController.swift
@@ -1063,6 +1063,7 @@ public enum FileMediaResourceMediaStatus: Equatable {
 }
 
 public protocol ChatMessageItemNodeProtocol: ListViewItemNode {
+    func makeProgress() -> Promise<Bool>?
     func targetReactionView(value: MessageReaction.Reaction) -> UIView?
     func targetForStoryTransition(id: StoryId) -> UIView?
     func contentFrame() -> CGRect
diff --git a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift
index a8428853f7..5c7fb53fd9 100644
--- a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift
+++ b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift
@@ -67,7 +67,7 @@ public func messageFileMediaResourceStatus(context: AccountContext, file: Telegr
                         }
                 }
             } else if let pendingStatus = pendingStatus {
-                mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress))
+                mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress.progress))
             } else {
                 mediaStatus = .fetchStatus(EngineMediaResource.FetchStatus(resourceStatus))
             }
@@ -104,7 +104,7 @@ public func messageImageMediaResourceStatus(context: AccountContext, image: Tele
         |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in
             let mediaStatus: FileMediaResourceMediaStatus
             if let pendingStatus = pendingStatus {
-                mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress))
+                mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress.progress))
             } else {
                 mediaStatus = .fetchStatus(EngineMediaResource.FetchStatus(resourceStatus))
             }
diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift
index 855ef16b3a..fc876e6e81 100644
--- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift
@@ -1077,7 +1077,7 @@ private final class DemoSheetContent: CombinedComponent {
                         )
                     )
                 )
-                //TODO:localize
+                
                 availableItems[.messageEffects] = DemoPagerComponent.Item(
                     AnyComponentWithIdentity(
                         id: PremiumDemoScreen.Subject.messageEffects,
@@ -1193,8 +1193,7 @@ private final class DemoSheetContent: CombinedComponent {
             case .folderTags:
                 text = strings.Premium_FolderTagsStandaloneInfo
             case .messageEffects:
-                //TODO:localize
-                text = "Add over 500 animated effects to private messages."
+                text = strings.Premium_MessageEffectsInfo
             default:
                 text = ""
             }
diff --git a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift
index f148dc67b6..6a225c18dc 100644
--- a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift
+++ b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift
@@ -254,8 +254,8 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
                         itemTitle = item.presentationData.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title
                         itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle
                     case .ads:
-                        itemTitle = "Withdrawal"
-                        itemSubtitle = "via Telegram Ads"
+                        itemTitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Title
+                        itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Subtitle
                     case .unsupported:
                         itemTitle = item.presentationData.strings.Stars_Intro_Transaction_Unsupported_Title
                         itemSubtitle = nil
diff --git a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift
index fd2ee56776..00f94cf884 100644
--- a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift
+++ b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift
@@ -153,7 +153,13 @@ private func areResourcesEqual(_ lhs: MediaResource, _ rhs: MediaResource) -> Bo
 }
 
 private func findMediaResource(media: Media, previousMedia: Media?, resource: MediaResource) -> TelegramMediaResource? {
-    if let image = media as? TelegramMediaImage {
+    if let paidContent = media as? TelegramMediaPaidContent {
+        for case let .full(fullMedia) in paidContent.extendedMedia {
+            if let resource = findMediaResource(media: fullMedia, previousMedia: previousMedia, resource: resource) {
+                return resource
+            }
+        }
+    } else if let image = media as? TelegramMediaImage {
         for representation in image.representations {
             if let updatedResource = representation.resource as? CloudPhotoSizeMediaResource, let previousResource = resource as? CloudPhotoSizeMediaResource {
                 if updatedResource.photoId == previousResource.photoId && updatedResource.sizeSpec == previousResource.sizeSpec {
diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift
index 966aef3218..1a2a9c3295 100644
--- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift
+++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift
@@ -23,8 +23,18 @@ struct PendingMessageUploadedContentAndReuploadInfo {
     let cacheReferenceKey: CachedSentMediaReferenceKey?
 }
 
+struct PendingMessageUploadedContentProgress {
+    let progress: Float
+    let mediaProgress: [MediaId: Float]
+    
+    init(progress: Float, mediaProgress: [MediaId: Float] = [:]) {
+        self.progress = progress
+        self.mediaProgress = mediaProgress
+    }
+}
+
 enum PendingMessageUploadedContentResult {
-    case progress(Float)
+    case progress(PendingMessageUploadedContentProgress)
     case content(PendingMessageUploadedContentAndReuploadInfo)
 }
 
@@ -90,7 +100,7 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po
     } else if let media = media.first as? TelegramMediaStory {
         return .signal(postbox.transaction { transaction -> PendingMessageUploadedContentResult in
             guard let inputPeer = transaction.getPeer(media.storyId.peerId).flatMap(apiInputPeer) else {
-                return .progress(0.0)
+                return .progress(PendingMessageUploadedContentProgress(progress: 0.0))
             }
             return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaStory(peer: inputPeer, id: media.storyId.id), ""), reuploadInfo: nil, cacheReferenceKey: nil))
         }
@@ -121,7 +131,12 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po
 func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, passFetchProgress: Bool, forceNoBigParts: Bool, peerId: PeerId, media: Media, text: String, autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute], mediaReference: AnyMediaReference?) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? {
     if let paidContent = media as? TelegramMediaPaidContent {
         var signals: [Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>] = []
+        var mediaIds: [MediaId] = []
         for case let .full(media) in paidContent.extendedMedia {
+            guard let id = media.id else {
+                continue
+            }
+            mediaIds.append(id)
             if let image = media as? TelegramMediaImage {
                 signals.append(uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, forceReupload: forceReupload, isGrouped: false, peerId: peerId, image: image, messageId: messageId, text: "", attributes: [], autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, auxiliaryMethods: auxiliaryMethods))
             } else if let file = media as? TelegramMediaFile {
@@ -132,13 +147,16 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
         |> map { results -> PendingMessageUploadedContentResult in
             var currentProgress: Float = 0.0
             var media: [Api.InputMedia] = []
-            for result in results {
+            var mediaProgress: [MediaId: Float] = [:]
+            for (mediaId, result) in zip(mediaIds, results) {
                 switch result {
                 case let .progress(progress):
-                    currentProgress += progress
+                    currentProgress += progress.progress
+                    mediaProgress[mediaId] = progress.progress
                 case let .content(content):
                     if case let .media(resultMedia, _) = content.content {
                         media.append(resultMedia)
+                        mediaProgress[mediaId] = 1.0
                     }
                 }
             }
@@ -153,7 +171,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
                     cacheReferenceKey: nil
                 ))
             } else {
-                return .progress(normalizedProgress)
+                return .progress(PendingMessageUploadedContentProgress(progress: normalizedProgress, mediaProgress: mediaProgress))
             }
         }
     }
@@ -464,7 +482,7 @@ if "".isEmpty {
                             flags |= 1 << 1
                         }
                     }
-                    return .single(.progress(1.0))
+                    return .single(.progress(PendingMessageUploadedContentProgress(progress: 1.0)))
                     |> then(
                         .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: nil)))
                     )
@@ -500,13 +518,43 @@ if "".isEmpty {
                                         storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: nil, psaType: nil, flags: [])
                                     }
                                     var updatedAttributes = currentMessage.attributes
-                                    if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){
-                                        let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute
-                                        updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia]))
-                                    } else {
-                                        updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: []))
+                                    
+                                    var markTransformedMedia = true
+                                    var updatedMedia = currentMessage.media
+                                    if let paidContent = updatedMedia.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent {
+                                        var extendedMedia = paidContent.extendedMedia
+                                        if let index = extendedMedia.firstIndex(where: { media in
+                                            if case let .full(fullMedia) = media, fullMedia.id == id {
+                                                return true
+                                            } else {
+                                                return false
+                                            }
+                                        }) {
+                                            extendedMedia[index] = .full(media: media)
+                                        }
+                                        updatedMedia = [TelegramMediaPaidContent(amount: paidContent.amount, extendedMedia: extendedMedia)]
+                                        
+                                        if extendedMedia.contains(where: { media in
+                                            if case .preview = media {
+                                                return true
+                                            } else {
+                                                return false
+                                            }
+                                        }) {
+                                            markTransformedMedia = false
+                                        }
                                     }
-                                    return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media))
+                                    
+                                    if markTransformedMedia {
+                                        if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){
+                                            let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute
+                                            updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia]))
+                                        } else {
+                                            updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: []))
+                                        }
+                                    }
+                                    
+                                    return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: updatedMedia))
                                 })
                             }
                             return .done(media)
@@ -526,7 +574,7 @@ if "".isEmpty {
         |> mapToSignal { transformResult -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
             switch transformResult {
             case .pending:
-                return .single(.progress(0.0))
+                return .single(.progress(PendingMessageUploadedContentProgress(progress: 0.0)))
             case let .done(transformedMedia):
                 let transformedImage = (transformedMedia as? TelegramMediaImage) ?? image
                 guard let largestRepresentation = largestImageRepresentation(transformedImage.representations) else {
@@ -543,7 +591,7 @@ if "".isEmpty {
                 |> mapToSignal { next -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
                     switch next {
                         case let .progress(progress):
-                            return .single(.progress(progress))
+                            return .single(.progress(PendingMessageUploadedContentProgress(progress: progress)))
                         case let .inputFile(file):
                             var flags: Int32 = 0
                             var ttlSeconds: Int32?
@@ -770,7 +818,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
                         }
                     }
                     
-                    return .single(.progress(1.0))
+                    return .single(.progress(PendingMessageUploadedContentProgress(progress: 1.0)))
                     |> then(
                         .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)))
                     )
@@ -844,13 +892,43 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
                                     storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: nil, psaType: nil, flags: [])
                                 }
                                 var updatedAttributes = currentMessage.attributes
-                                if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){
-                                    let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute
-                                    updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia]))
-                                } else {
-                                    updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: []))
+                                
+                                var markTransformedMedia = true
+                                var updatedMedia = currentMessage.media
+                                if let paidContent = updatedMedia.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent {
+                                    var extendedMedia = paidContent.extendedMedia
+                                    if let index = extendedMedia.firstIndex(where: { media in
+                                        if case let .full(fullMedia) = media, fullMedia.id == id {
+                                            return true
+                                        } else {
+                                            return false
+                                        }
+                                    }) {
+                                        extendedMedia[index] = .full(media: media)
+                                    }
+                                    updatedMedia = [TelegramMediaPaidContent(amount: paidContent.amount, extendedMedia: extendedMedia)]
+                                    
+                                    if extendedMedia.contains(where: { media in
+                                        if case .preview = media {
+                                            return true
+                                        } else {
+                                            return false
+                                        }
+                                    }) {
+                                        markTransformedMedia = false
+                                    }
                                 }
-                                return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media))
+                                
+                                if markTransformedMedia {
+                                    if let index = updatedAttributes.firstIndex(where: { $0 is OutgoingMessageInfoAttribute }){
+                                        let attribute = updatedAttributes[index] as! OutgoingMessageInfoAttribute
+                                        updatedAttributes[index] = attribute.withUpdatedFlags(attribute.flags.union([.transformedMedia]))
+                                    } else {
+                                        updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: Int64.random(in: Int64.min ... Int64.max), flags: [.transformedMedia], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: []))
+                                    }
+                                }
+                                
+                                return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: updatedMedia))
                             })
                         }
                         return .done(media)
@@ -901,7 +979,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
         |> mapToSignal { content, fileAndThumbnailResult, resourceStatus -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
             guard let content = content else {
                 if let resourceStatus = resourceStatus, case let .Fetching(_, progress) = resourceStatus {
-                    return .single(.progress(progress * 0.33))
+                    return .single(.progress(PendingMessageUploadedContentProgress(progress: progress * 0.33)))
                 }
                 return .complete()
             }
@@ -911,7 +989,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
                     if passFetchProgress {
                         progress = 0.33 + progress * 0.67
                     }
-                    return .single(.progress(progress))
+                    return .single(.progress(PendingMessageUploadedContentProgress(progress: progress)))
                 case let .inputFile(inputFile):
                     if case let .done(file, thumbnail) = fileAndThumbnailResult {
                         var flags: Int32 = 0
diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift
index fecd4acc85..1a131673d1 100644
--- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift
+++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift
@@ -54,7 +54,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
     let uploadedMedia: Signal<PendingMessageUploadedContentResult?, NoError>
     switch media {
     case .keep:
-        uploadedMedia = .single(.progress(0.0))
+        uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.0)))
         |> then(.single(nil))
     case let .update(media):
         let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
@@ -69,14 +69,14 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
             return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: attributes, mediaReference: nil)
         }
         if let uploadSignal = generateUploadSignal(forceReupload) {
-            uploadedMedia = .single(.progress(0.027))
+            uploadedMedia = .single(.progress(PendingMessageUploadedContentProgress(progress: 0.027)))
             |> then(uploadSignal)
             |> map { result -> PendingMessageUploadedContentResult? in
                 switch result {
-                    case let .progress(value):
-                        return .progress(max(value, 0.027))
-                    case let .content(content):
-                        return .content(content)
+                case let .progress(value):
+                    return .progress(PendingMessageUploadedContentProgress(progress: max(value.progress, 0.027)))
+                case let .content(content):
+                    return .content(content)
                 }
             }
             |> `catch` { _ -> Signal<PendingMessageUploadedContentResult?, NoError> in
@@ -92,10 +92,10 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
         var pendingMediaContent: PendingMessageUploadedContent?
         if let uploadedMediaResult = uploadedMediaResult {
             switch uploadedMediaResult {
-                case let .progress(value):
-                    return .single(.progress(value))
-                case let .content(content):
-                    pendingMediaContent = content.content
+            case let .progress(value):
+                return .single(.progress(value.progress))
+            case let .content(content):
+                pendingMediaContent = content.content
             }
         }
         return postbox.transaction { transaction -> (Peer?, Message?, SimpleDictionary<PeerId, Peer>) in
diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift
index 7046de43f7..9a0baa37cf 100644
--- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift
+++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift
@@ -206,7 +206,7 @@ public func standaloneSendEnqueueMessages(
             switch result.result {
             case let .progress(value):
                 allDone = false
-                progressSum += value
+                progressSum += value.progress
             case let .content(content):
                 allResults.append((content, result.media))
             }
diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift
index cfcc2c8923..ff6734e87f 100644
--- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift
+++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift
@@ -6,8 +6,23 @@ import MtProtoKit
 
 
 public struct PendingMessageStatus: Equatable {
+    public struct Progress: Equatable {
+        public let progress: Float
+        public let mediaProgress: [MediaId: Float]
+        
+        public init(progress: Float, mediaProgress: [MediaId: Float] = [:]) {
+            self.progress = progress
+            self.mediaProgress = mediaProgress
+        }
+        
+        init(_ contentProgress: PendingMessageUploadedContentProgress) {
+            self.progress = contentProgress.progress
+            self.mediaProgress = contentProgress.mediaProgress
+        }
+    }
+    
     public let isRunning: Bool
-    public let progress: Float
+    public let progress: Progress
 }
 
 private enum PendingMessageState {
@@ -364,7 +379,7 @@ public final class PendingMessageManager {
                 self.messageContexts[id] = messageContext
             }
             
-            let status = PendingMessageStatus(isRunning: false, progress: 0.0)
+            let status = PendingMessageStatus(isRunning: false, progress: PendingMessageStatus.Progress(progress: 0.0))
             if status != messageContext.status {
                 messageContext.status = status
                 for subscriber in messageContext.statusSubscribers.copyItems() {
@@ -618,7 +633,7 @@ public final class PendingMessageManager {
                 switch next {
                     case let .progress(progress):
                         if let current = strongSelf.messageContexts[messageId] {
-                            let status = PendingMessageStatus(isRunning: true, progress: progress)
+                            let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress: progress))
                             current.status = status
                             for subscriber in current.statusSubscribers.copyItems() {
                                 subscriber(current.status, current.error)
@@ -636,7 +651,7 @@ public final class PendingMessageManager {
     private func beginUploadingMessage(messageContext: PendingMessageContext, id: MessageId, threadId: Int64?, groupId: Int64?, uploadSignal: Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>) {
         messageContext.state = .uploading(groupId: groupId)
         
-        let status = PendingMessageStatus(isRunning: true, progress: 0.0)
+        let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress: 0.0))
         messageContext.status = status
         for subscriber in messageContext.statusSubscribers.copyItems() {
             subscriber(messageContext.status, messageContext.error)
@@ -678,7 +693,7 @@ public final class PendingMessageManager {
                 switch next {
                     case let .progress(progress):
                         if let current = strongSelf.messageContexts[id] {
-                            let status = PendingMessageStatus(isRunning: true, progress: progress)
+                            let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress))
                             current.status = status
                             for subscriber in current.statusSubscribers.copyItems() {
                                 subscriber(current.status, current.error)
@@ -712,7 +727,7 @@ public final class PendingMessageManager {
             if case let .waitingForUploadToStart(groupId, uploadSignal) = context.state {
                 if self.canBeginUploadingMessage(id: contextId, type: context.contentType ?? .media) {
                     context.state = .uploading(groupId: groupId)
-                    let status = PendingMessageStatus(isRunning: true, progress: 0.0)
+                    let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress: 0.0))
                     context.status = status
                     for subscriber in context.statusSubscribers.copyItems() {
                         subscriber(context.status, context.error)
@@ -734,7 +749,7 @@ public final class PendingMessageManager {
                             switch next {
                                 case let .progress(progress):
                                     if let current = strongSelf.messageContexts[contextId] {
-                                        let status = PendingMessageStatus(isRunning: true, progress: progress)
+                                        let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress))
                                         current.status = status
                                         for subscriber in current.statusSubscribers.copyItems() {
                                             subscriber(context.status, context.error)
diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift
index 91a7c0efdf..38c07f640b 100644
--- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift
+++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift
@@ -1093,7 +1093,7 @@ func _internal_uploadStoryImpl(
         |> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
             switch result {
             case let .progress(progress):
-                return .single(.progress(progress))
+                return .single(.progress(progress.progress))
             case let .content(content):
                 return postbox.transaction { transaction -> Signal<StoryUploadResult, NoError> in
                     let privacyRules = apiInputPrivacyRules(privacy: privacy, transaction: transaction)
@@ -1278,7 +1278,7 @@ func _internal_editStory(account: Account, peerId: PeerId, id: Int32, media: Eng
     return contentSignal
     |> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
         if let result = result, case let .progress(progress) = result {
-            return .single(.progress(progress))
+            return .single(.progress(progress.progress))
         }
         
         let inputMedia: Api.InputMedia?
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
index 6274b32845..44d2791a2d 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
@@ -5911,6 +5911,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
         item.controllerInteraction.openMessageContextMenu(item.message, true, self, subFrame, nil, nil)
     }
     
+    override public func makeProgress() -> Promise<Bool>? {
+        return self.unlockButtonNode?.makeProgress()
+    }
+    
     override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
         if let result = self.reactionButtonsNode?.reactionTargetView(value: value) {
             return result
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift
index 02ee9cf6c8..59b4284e66 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift
@@ -1490,7 +1490,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
             reactionButtonsNode.offset(value: value, animationCurve: animationCurve, duration: duration)
         }
     }
-    
+        
     override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
         if let result = self.reactionButtonsNode?.reactionTargetView(value: value) {
             return result
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift
index 6e5ebdf780..bc11d74c99 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift
@@ -473,7 +473,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
                 updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(context: item.context, file: updatedFile, message: EngineMessage(item.message), isRecentActions: item.associatedData.isRecentActions), item.context.account.pendingMessageManager.pendingMessageStatus(item.message.id) |> map { $0.0 })
                 |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in
                     if let pendingStatus = pendingStatus {
-                        var progress = pendingStatus.progress
+                        var progress = pendingStatus.progress.progress
                         if pendingStatus.isRunning {
                             progress = max(progress, 0.27)
                         }
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift
index 1ec0b22b75..15b32c25b3 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift
@@ -1475,7 +1475,11 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
                                 updatedStatusSignal = combineLatest(chatMessagePhotoStatus(context: context, messageId: message.id, photoReference: .message(message: MessageReference(message), media: image)), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 })
                                 |> map { resourceStatus, pendingStatus -> (MediaResourceStatus, MediaResourceStatus?) in
                                     if let pendingStatus = pendingStatus {
-                                        let adjustedProgress = max(pendingStatus.progress, 0.027)
+                                        var progress: Float = pendingStatus.progress.progress
+                                        if let id = media.id, let mediaProgress = pendingStatus.progress.mediaProgress[id] {
+                                            progress = mediaProgress
+                                        }
+                                        let adjustedProgress = max(progress, 0.027)
                                         return (.Fetching(isActive: pendingStatus.isRunning, progress: adjustedProgress), resourceStatus)
                                     } else {
                                         return (resourceStatus, nil)
@@ -1491,7 +1495,11 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
                             updatedStatusSignal = combineLatest(messageMediaFileStatus(context: context, messageId: message.id, file: file, adjustForVideoThumbnail: true), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 })
                                 |> map { resourceStatus, pendingStatus -> (MediaResourceStatus, MediaResourceStatus?) in
                                     if let pendingStatus = pendingStatus {
-                                        let adjustedProgress = max(pendingStatus.progress, 0.027)
+                                        var progress: Float = pendingStatus.progress.progress
+                                        if let id = media.id, let mediaProgress = pendingStatus.progress.mediaProgress[id] {
+                                            progress = mediaProgress
+                                        }
+                                        let adjustedProgress = max(progress, 0.027)
                                         return (.Fetching(isActive: pendingStatus.isRunning, progress: adjustedProgress), resourceStatus)
                                     } else {
                                         return (resourceStatus, nil)
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift
index dd9121a8cd..a473d69ea6 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift
@@ -847,7 +847,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
                         item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes)
                     }
                 case .payment:
-                    item.controllerInteraction.openCheckoutOrReceipt(item.message.id)
+                    item.controllerInteraction.openCheckoutOrReceipt(item.message.id, nil)
                 case let .urlAuth(url, buttonId):
                     item.controllerInteraction.requestMessageActionUrlAuth(url, .message(id: item.message.id, buttonId: buttonId))
                 case .setupPoll:
@@ -881,6 +881,10 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
     open func openMessageContextMenu() {
     }
     
+    open func makeProgress() -> Promise<Bool>? {
+        return nil
+    }
+    
     open func targetReactionView(value: MessageReaction.Reaction) -> UIView? {
         return nil
     }
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift
index 68dccc6c03..57b30db954 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift
@@ -56,7 +56,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
                 case .automaticPlayback:
                     openChatMessageMode = .automaticPlayback
             }
-            let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode, mediaIndex: self.mediaIndex))
+            
+            let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode, mediaIndex: self.mediaIndex, progress: self.itemNode?.makeProgress()))
         }
         
         self.interactiveImageNode.activateAgeRestrictedMedia = { [weak self] in
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift
index 6293c100a2..f64d02df9a 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift
@@ -239,9 +239,12 @@ public class ChatMessageStarsMediaInfoNode: ASDisplayNode {
             } else {
                 text = NSMutableAttributedString(string: "Purchased", font: textFont, textColor: .white)
             }
+            
+            var offset: CGFloat = 0.0
             if let range = text.string.range(of: "⭐️") {
                 text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: NSRange(range, in: text.string))
                 text.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: text.string))
+                offset -= 1.0
             }
             
             let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: arguments.constrainedSize.width, height: arguments.constrainedSize.height), alignment: .natural, cutout: nil, insets: .zero))
@@ -278,7 +281,7 @@ public class ChatMessageStarsMediaInfoNode: ASDisplayNode {
                 
                 node.contentBackgroundNode.frame = CGRect(origin: .zero, size: size)
                 
-                let textFrame = CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((size.height - textLayout.size.height) / 2.0) + UIScreenPixel), size: textLayout.size)
+                let textFrame = CGRect(origin: CGPoint(x: padding + offset, y: floorToScreenPixels((size.height - textLayout.size.height) / 2.0) + UIScreenPixel), size: textLayout.size)
                 textNode.textNode.frame = textFrame
                                  
                 return node
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift
index 8f97770655..f0481caa95 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift
@@ -16,6 +16,7 @@ import AnimationCache
 import MultiAnimationRenderer
 import ComponentFlow
 import ChatControllerInteraction
+import HierarchyTrackingLayer
 
 public class ChatMessageUnlockMediaNode: ASDisplayNode {
     public class Arguments {
@@ -63,11 +64,14 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode {
     private let contentNode: HighlightTrackingButtonNode
     private let backgroundNode: NavigationBackgroundNode
     private var textNode: TextNodeWithEntities?
+    private var loadingView: LoadingEffectView?
     
     private var pressed = { }
     
     private var absolutePosition: (CGRect, CGSize)?
     
+    private var currentProgressDisposable: Disposable?
+    
     override public init() {
         self.contentNode = HighlightTrackingButtonNode()
         
@@ -83,10 +87,41 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode {
         self.contentNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
     }
     
+    deinit {
+        self.currentProgressDisposable?.dispose()
+    }
+    
     @objc private func buttonPressed() {
         self.pressed()
     }
     
+    public func makeProgress() -> Promise<Bool> {
+        let progress = Promise<Bool>()
+        self.currentProgressDisposable?.dispose()
+        self.currentProgressDisposable = (progress.get()
+        |> distinctUntilChanged
+        |> deliverOnMainQueue).start(next: { [weak self] hasProgress in
+            guard let self, let loadingView = self.loadingView else {
+                return
+            }
+            let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
+            if hasProgress {
+                if loadingView.superview == nil {
+                    loadingView.alpha = 0.0
+                    self.view.addSubview(loadingView)
+                    transition.updateAlpha(layer: loadingView.layer, alpha: 1.0)
+                }
+            } else if loadingView.superview != nil {
+                transition.updateAlpha(layer: loadingView.layer, alpha: 0.0, beginWithCurrentState: true, completion: { finished in
+                    if finished {
+                        loadingView.removeFromSuperview()
+                    }
+                })
+            }
+        })
+        return progress
+    }
+    
     public class func asyncLayout(_ maybeNode: ChatMessageUnlockMediaNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode) {
         let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode)
     
@@ -139,8 +174,171 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode {
                 node.backgroundNode.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate)
                 node.contentNode.frame = CGRect(origin: CGPoint(), size: size)
                 
+                let loadingView: LoadingEffectView
+                if let current = node.loadingView {
+                    loadingView = current
+                } else {
+                    loadingView = LoadingEffectView()
+                    node.loadingView = loadingView
+                }
+                loadingView.frame = CGRect(origin: .zero, size: size)
+                loadingView.update(color: UIColor.white, rect: CGRect(origin: .zero, size: size))
+                
                 return node
             })
         }
     }
 }
+
+private let shadowImage: UIImage? = {
+    UIImage(named: "Stories/PanelGradient")
+}()
+
+public final class LoadingEffectView: UIView {
+    let hierarchyTrackingLayer: HierarchyTrackingLayer
+    
+    private let maskContentsView: UIView
+    private let maskHighlightNode: LinkHighlightingNode
+    
+    private let maskBorderContentsView: UIView
+    private let maskBorderHighlightNode: LinkHighlightingNode
+    
+    private let backgroundView: UIImageView
+    private let borderBackgroundView: UIImageView
+    
+    private var duration: Double
+    private var gradientWidth: CGFloat
+    
+    private var inHierarchy = false
+    private var size: CGSize?
+    
+    override public init(frame: CGRect) {
+        self.hierarchyTrackingLayer = HierarchyTrackingLayer()
+        
+        self.maskContentsView = UIView()
+        self.maskHighlightNode = LinkHighlightingNode(color: .black)
+        self.maskHighlightNode.useModernPathCalculation = true
+        
+        self.maskBorderContentsView = UIView()
+        self.maskBorderHighlightNode = LinkHighlightingNode(color: .black)
+        self.maskBorderHighlightNode.borderOnly = true
+        self.maskBorderHighlightNode.useModernPathCalculation = true
+        
+        self.maskBorderContentsView.addSubview(self.maskBorderHighlightNode.view)
+        
+        self.backgroundView = UIImageView()
+        self.borderBackgroundView = UIImageView()
+        
+        self.gradientWidth = 120.0
+        self.duration = 1.0
+        
+        super.init(frame: frame)
+        
+        self.isUserInteractionEnabled = false
+        
+        self.maskContentsView.mask = self.maskHighlightNode.view
+        self.maskContentsView.addSubview(self.backgroundView)
+        self.addSubview(self.maskContentsView)
+        
+        self.maskBorderContentsView.mask = self.maskBorderHighlightNode.view
+        self.maskBorderContentsView.addSubview(self.borderBackgroundView)
+        self.addSubview(self.maskBorderContentsView)
+        
+        let generateGradient: (CGFloat) -> UIImage? = { baseAlpha in
+            return generateImage(CGSize(width: self.gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
+                context.clear(CGRect(origin: CGPoint(), size: size))
+                
+                let foregroundColor = UIColor(white: 1.0, alpha: min(1.0, baseAlpha * 4.0))
+                
+                if let shadowImage {
+                    UIGraphicsPushContext(context)
+                    
+                    for i in 0 ..< 2 {
+                        let shadowFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (size.width * 0.5), y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height))
+                        
+                        context.saveGState()
+                        context.translateBy(x: shadowFrame.midX, y: shadowFrame.midY)
+                        context.rotate(by: CGFloat(i == 0 ? 1.0 : -1.0) * CGFloat.pi * 0.5)
+                        let adjustedRect = CGRect(origin: CGPoint(x: -shadowFrame.height * 0.5, y: -shadowFrame.width * 0.5), size: CGSize(width: shadowFrame.height, height: shadowFrame.width))
+                        
+                        context.clip(to: adjustedRect, mask: shadowImage.cgImage!)
+                        context.setFillColor(foregroundColor.cgColor)
+                        context.fill(adjustedRect)
+                        
+                        context.restoreGState()
+                    }
+                    
+                    UIGraphicsPopContext()
+                }
+            })?.withRenderingMode(.alwaysTemplate)
+        }
+        
+        self.backgroundView.image = generateGradient(0.5)
+        self.borderBackgroundView.image = generateGradient(1.0)
+        
+        self.layer.addSublayer(self.hierarchyTrackingLayer)
+        self.hierarchyTrackingLayer.isInHierarchyUpdated = { [weak self] inHierarchy in
+            guard let self, let size = self.size else {
+                return
+            }
+            self.inHierarchy = inHierarchy
+            self.updateAnimations(size: size)
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    private func updateAnimations(size: CGSize) {
+        if self.inHierarchy {
+            if self.backgroundView.layer.animation(forKey: "shimmer") != nil {
+                return
+            }
+            let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.0) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
+            animation.repeatCount = Float.infinity
+            self.backgroundView.layer.add(animation, forKey: "shimmer")
+            self.borderBackgroundView.layer.add(animation, forKey: "shimmer")
+        } else {
+            self.backgroundView.layer.removeAllAnimations()
+            self.borderBackgroundView.layer.removeAllAnimations()
+        }
+    }
+    
+    public func update(color: UIColor, rect: CGRect) {
+        let maskFrame = CGRect(origin: CGPoint(), size: rect.size).insetBy(dx: -4.0, dy: -4.0)
+        
+        self.gradientWidth = 260.0
+        self.duration = 1.2
+        
+        self.maskContentsView.backgroundColor = .clear
+        self.maskBorderContentsView.backgroundColor = color.withAlphaComponent(0.12)
+        
+//        self.backgroundView.alpha = 0.25
+        self.backgroundView.tintColor = color
+        
+        self.borderBackgroundView.tintColor = color
+    
+        self.maskContentsView.frame = maskFrame
+        self.maskBorderContentsView.frame = maskFrame
+        
+        let rectsSet: [CGRect] = [rect]
+                
+        self.maskHighlightNode.outerRadius = rect.height / 2.0
+        self.maskHighlightNode.updateRects(rectsSet)
+        self.maskHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
+        
+        self.maskBorderHighlightNode.outerRadius = rect.height / 2.0
+        self.maskBorderHighlightNode.updateRects(rectsSet)
+        self.maskBorderHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
+        
+        if self.size != maskFrame.size {
+            self.size = maskFrame.size
+            
+            self.backgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height))
+            self.borderBackgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height))
+            
+            self.updateAnimations(size: maskFrame.size)
+        }
+    }
+}
diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift
index a67caf2d7e..7bdc5a1bde 100644
--- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift
@@ -557,7 +557,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
                         break
                 }
             }
-        }, openCheckoutOrReceipt: { _ in
+        }, openCheckoutOrReceipt: { _, _ in
         }, openSearch: {
         }, setupReply: { _ in
         }, canSetupReply: { _ in
diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift
index 9d15c612ec..5cd1237b3f 100644
--- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift
@@ -428,7 +428,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
             return nil
         }, chatControllerNode: {
             return nil
-        }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
+        }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in
         }, canSetupReply: { _ in
             return .none
         }, canSendMessages: {
diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift
index 710517d8fb..22852dd3d1 100644
--- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift
+++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift
@@ -207,7 +207,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
     public let presentGlobalOverlayController: (ViewController, Any?) -> Void
     public let callPeer: (PeerId, Bool) -> Void
     public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void
-    public let openCheckoutOrReceipt: (MessageId) -> Void
+    public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void
     public let openSearch: () -> Void
     public let setupReply: (MessageId) -> Void
     public let canSetupReply: (Message) -> ChatControllerInteractionSwipeAction
@@ -336,7 +336,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
         presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
         callPeer: @escaping (PeerId, Bool) -> Void,
         longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void,
-        openCheckoutOrReceipt: @escaping (MessageId) -> Void,
+        openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void,
         openSearch: @escaping () -> Void,
         setupReply: @escaping (MessageId) -> Void,
         canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction,
diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
index 19c963e87f..cd3e327d0b 100644
--- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
+++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
@@ -3295,7 +3295,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
             default:
                 break
             }
-        }, openCheckoutOrReceipt: { _ in
+        }, openCheckoutOrReceipt: { _, _ in
         }, openSearch: {
         }, setupReply: { _ in
         }, canSetupReply: { _ in
diff --git a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift
index 7e8f00cd2e..545bfc8409 100644
--- a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift
+++ b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift
@@ -245,12 +245,18 @@ public final class StarsAvatarComponent: Component {
                 self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
                 iconOffset = 2.0
             case .ads:
-                self.backgroundView.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1b1f24))
+                self.backgroundView.image = generateGradientFilledCircleImage(
+                    diameter: size.width,
+                    colors: [
+                        UIColor(rgb: 0xffa85c).cgColor,
+                        UIColor(rgb: 0xffcd6a).cgColor
+                    ],
+                    direction: .mirroredDiagonal
+                )
                 self.backgroundView.isHidden = false
                 self.iconView.isHidden = false
                 self.avatarNode.isHidden = true
-                self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
-                iconOffset = 2.0
+                self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white)
             case .premiumBot:
                 iconInset = 7.0
                 self.backgroundView.image = generateGradientFilledCircleImage(
diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift
index 02746eb60d..c29b55363e 100644
--- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift
+++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift
@@ -720,12 +720,15 @@ public final class StarsImageComponent: Component {
                         iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
                         iconOffset = 5.0
                     case .ads:
-                        iconBackgroundView.image = generateFilledCircleImage(
+                        iconBackgroundView.image = generateGradientFilledCircleImage(
                             diameter: imageSize.width,
-                            color: UIColor(rgb: 0x1b1f24)
+                            colors: [
+                                UIColor(rgb: 0xffa85c).cgColor,
+                                UIColor(rgb: 0xffcd6a).cgColor
+                            ],
+                            direction: .mirroredDiagonal
                         )
-                        iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
-                        iconOffset = 5.0
+                        iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white)
                     case .premiumBot:
                         iconInset = 15.0
                         iconBackgroundView.image = generateGradientFilledCircleImage(
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift
index 8bf58a689c..b73fa35bdb 100644
--- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift
+++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift
@@ -216,17 +216,16 @@ private final class StarsTransactionSheetContent: CombinedComponent {
                         via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle
                     }
                 case .ads:
-                    //TODO:localize
-                    titleText = "Stars Withdrawal"
-                    via = "Telegram Ads"
+                    titleText = strings.Stars_Transaction_TelegramAds_Title
+                    via = strings.Stars_Transaction_TelegramAds_Subtitle
                 case .unsupported:
                     titleText = strings.Stars_Transaction_Unsupported_Title
                     via = nil
                 }
                 if !transaction.media.isEmpty {
                     var description: String = ""
-                    var photoCount: Int = 0
-                    var videoCount: Int = 0
+                    var photoCount: Int32 = 0
+                    var videoCount: Int32 = 0
                     for media in transaction.media {
                         if let _ = media as? TelegramMediaFile {
                             videoCount += 1
@@ -234,33 +233,22 @@ private final class StarsTransactionSheetContent: CombinedComponent {
                             photoCount += 1
                         }
                     }
-                    //TODO:localize
                     if photoCount > 0 && videoCount > 0 {
-                        if photoCount > 1 {
-                            description += "\(photoCount) photos**"
-                        } else {
-                            description += "**\(photoCount) photo**"
-                        }
-                        description += " and "
-                        if videoCount > 1 {
-                            description += "**\(videoCount) videos**"
-                        } else {
-                            description += "**\(videoCount) video**"
-                        }
+                        description += strings.Stars_Transaction_MediaAnd(strings.Stars_Transaction_Photos(photoCount), strings.Stars_Transaction_Videos(videoCount)).string
                     } else if photoCount > 0 {
                         if photoCount > 1 {
-                            description += "**\(photoCount) photos**"
+                            description += strings.Stars_Transaction_Photos(photoCount)
                         } else {
-                            description += "**Photo**"
+                            description += strings.Stars_Transaction_SinglePhoto
                         }
                     } else if videoCount > 0 {
                         if videoCount > 1 {
-                            description += "**\(videoCount) videos**"
+                            description += strings.Stars_Transaction_Videos(videoCount)
                         } else {
-                            description += "**Video**"
+                            description += strings.Stars_Transaction_SingleVideo
                         }
                     }
-                    descriptionText = description.replacingOccurrences(of: "**", with: "")
+                    descriptionText = description
                 } else {
                     descriptionText = transaction.description ?? ""
                 }
@@ -416,7 +404,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
             }
             
             if let messageId {
-                //TODO:localize
                 let peerName: String
                 if case let .transaction(_, parentPeer) = component.subject {
                     if parentPeer.id == component.context.account.peerId {
@@ -432,7 +419,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
                     peerName = ""
                 }
                 tableItems.append(.init(
-                    id: "via",
+                    id: "media",
                     title: strings.Stars_Transaction_Media,
                     component: AnyComponent(
                         Button(
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift
index 85056b50d2..2e8c2b6441 100644
--- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift
+++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift
@@ -209,8 +209,7 @@ final class StarsTransactionsListPanelComponent: Component {
                     switch item.peer {
                     case let .peer(peer):
                         if !item.media.isEmpty {
-                            //TODO:localize
-                            itemTitle = "Media Purchase"
+                            itemTitle = environment.strings.Stars_Intro_Transaction_MediaPurchase
                             itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
                         } else if let title = item.title {
                             itemTitle = title
@@ -237,8 +236,8 @@ final class StarsTransactionsListPanelComponent: Component {
                         itemTitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Title
                         itemSubtitle = environment.strings.Stars_Intro_Transaction_PremiumBotTopUp_Subtitle
                     case .ads:
-                        itemTitle = "Withdrawal"
-                        itemSubtitle = "via Telegram Ads"
+                        itemTitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Title
+                        itemSubtitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Subtitle
                     case .unsupported:
                         itemTitle = environment.strings.Stars_Intro_Transaction_Unsupported_Title
                         itemSubtitle = nil
diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift
index a6e731d185..6d81c79dd7 100644
--- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift
+++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift
@@ -335,10 +335,9 @@ private final class SheetContent: CombinedComponent {
             let amount = component.invoice.totalAmount
             let infoText: String
             if !component.extendedMedia.isEmpty {
-                //TODO:localize
                 var description: String = ""
-                var photoCount: Int = 0
-                var videoCount: Int = 0
+                var photoCount: Int32 = 0
+                var videoCount: Int32 = 0
                 for media in component.extendedMedia {
                     if case let .preview(_, _, videoDuration) = media, videoDuration != nil {
                         videoCount += 1
@@ -347,28 +346,18 @@ private final class SheetContent: CombinedComponent {
                     }
                 }
                 if photoCount > 0 && videoCount > 0 {
-                    if photoCount > 1 {
-                        description += "**\(photoCount) photos**"
-                    } else {
-                        description += "**\(photoCount) photo**"
-                    }
-                    description += " and "
-                    if videoCount > 1 {
-                        description += "**\(videoCount) videos**"
-                    } else {
-                        description += "**\(videoCount) video**"
-                    }
+                    description = strings.Stars_Transfer_MediaAnd("**\(strings.Stars_Transfer_Photos(photoCount))**", "**\(strings.Stars_Transfer_Videos(videoCount))**").string
                 } else if photoCount > 0 {
                     if photoCount > 1 {
-                        description += "**\(photoCount) photos**"
+                        description += "**\(strings.Stars_Transfer_Photos(photoCount))**"
                     } else {
-                        description += "**photo**"
+                        description += "**\(strings.Stars_Transfer_SinglePhoto)**"
                     }
                 } else if videoCount > 0 {
                     if videoCount > 1 {
-                        description += "**\(videoCount) videos**"
+                        description += "**\(strings.Stars_Transfer_Videos(videoCount))**"
                     } else {
-                        description += "**video**"
+                        description += "**\(strings.Stars_Transfer_SingleVideo)**"
                     }
                 }
                 infoText = strings.Stars_Transfer_UnlockInfo(
diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift
index a28d7590b5..6f040f3f40 100644
--- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift
+++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift
@@ -96,6 +96,8 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
                         return true
                     } else if let file = media as? TelegramMediaFile, file.isVideo {
                         return true
+                    } else if media is TelegramMediaPaidContent {
+                        return true
                     }
                     return false
                 })
diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift
index 431ad1407f..04a5bd6d9c 100644
--- a/submodules/TelegramUI/Sources/ChatController.swift
+++ b/submodules/TelegramUI/Sources/ChatController.swift
@@ -905,7 +905,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
                     switch extendedMedia {
                         case .preview:
                             if displayVoiceMessageDiscardAlert() {
-                                strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id)
+                                strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, params)
                                 return true
                             } else {
                                 return false
@@ -917,7 +917,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
                     switch extendedMedia {
                         case .preview:
                             if displayVoiceMessageDiscardAlert() {
-                                strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id)
+                                strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id, nil)
                                 return true
                             } else {
                                 return false
@@ -2586,7 +2586,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
             if let self {
                 self.openLinkLongTap(action, params: params)
             }
-        }, openCheckoutOrReceipt: { [weak self] messageId in
+        }, openCheckoutOrReceipt: { [weak self] messageId, params in
             guard let strongSelf = self else {
                 return
             }
@@ -2610,6 +2610,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
                 
                 for media in message.media {
                     if let paidContent = media as? TelegramMediaPaidContent {
+                        let progressSignal = Signal<Never, NoError> { _ in
+                            params?.progress?.set(.single(true))
+                            return ActionDisposable {
+                                params?.progress?.set(.single(false))
+                            }
+                        }
+                        |> runOn(Queue.mainQueue())
+                        |> delay(0.25, queue: Queue.mainQueue())
+                        let progressDisposable = progressSignal.startStrict()
+                        
                         strongSelf.chatDisplayNode.dismissInput()
                         let inputData = Promise<BotCheckoutController.InputData?>()
                         inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .message(message.id))
@@ -2636,6 +2646,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
                                 let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: paidContent.amount, startParam: "", extendedMedia: .preview(dimensions:dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: nil), flags: [], version: 0)
                                 let controller = strongSelf.context.sharedContext.makeStarsTransferScreen(context: strongSelf.context, starsContext: starsContext, invoice: invoice, source: .message(messageId), extendedMedia: paidContent.extendedMedia, inputData: starsInputData, completion: { _ in })
                                 strongSelf.push(controller)
+                                
+                                progressDisposable.dispose()
                             })
                         }
                     } else if let invoice = media as? TelegramMediaInvoice {
diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift
index 77f9463ec8..9163d271b5 100644
--- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift
+++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift
@@ -955,7 +955,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
                             controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes)
                         }
                     case .payment:
-                        controllerInteraction.openCheckoutOrReceipt(message.id)
+                        controllerInteraction.openCheckoutOrReceipt(message.id, nil)
                     case let .urlAuth(url, buttonId):
                         controllerInteraction.requestMessageActionUrlAuth(url, .message(id: message.id, buttonId: buttonId))
                     case .setupPoll:
diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift
index 3dfc566c2b..e810e8711c 100644
--- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift
+++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift
@@ -118,7 +118,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
         }, presentGlobalOverlayController: { _, _ in
         }, callPeer: { _, _ in
         }, longTap: { _, _ in
-        }, openCheckoutOrReceipt: { _ in
+        }, openCheckoutOrReceipt: { _, _ in
         }, openSearch: {
         }, setupReply: { _ in
         }, canSetupReply: { _ in
diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift
index b18bf299ed..d8ce813d31 100644
--- a/submodules/TelegramUI/Sources/SharedAccountContext.swift
+++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift
@@ -1722,7 +1722,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
             return nil
         }, chatControllerNode: {
             return nil
-        }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
+        }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _, _ in }, openSearch: { }, setupReply: { _ in
         }, canSetupReply: { _ in
             return .none
         }, canSendMessages: {
diff --git a/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift b/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift
index 5d5e7e15d4..51b7b85aac 100644
--- a/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift
+++ b/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift
@@ -8,6 +8,24 @@ import PhotoResources
 import ImageCompression
 
 public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, media: AnyMediaReference, opportunistic: Bool) -> Signal<AnyMediaReference?, NoError> {
+    if let paidContent = media.media as? TelegramMediaPaidContent {
+        var signals: [Signal<AnyMediaReference?, NoError>] = []
+        for case let .full(fullMedia) in paidContent.extendedMedia {
+            signals.append(transformOutgoingMessageMedia(postbox: postbox, network: network, media: media.withUpdatedMedia(fullMedia), opportunistic: opportunistic))
+        }
+        return combineLatest(signals)
+        |> mapToSignal { results -> Signal<AnyMediaReference?, NoError> in
+            let mediaResults = results.compactMap { $0?.media }
+            if mediaResults.count == signals.count {
+                return .single(media.withUpdatedMedia(TelegramMediaPaidContent(amount: paidContent.amount, extendedMedia: mediaResults.map { .full(media: $0) })))
+            } else if opportunistic {
+                return .single(nil)
+            } else {
+                return .complete()
+            }
+        }
+    }
+    
     switch media.media {
         case let file as TelegramMediaFile:
             let signal = Signal<MediaResourceData, NoError> { subscriber in