diff --git a/src/api/gramjs/apiBuilders/gifts.ts b/src/api/gramjs/apiBuilders/gifts.ts
index 8d48ab823..39c39e2a2 100644
--- a/src/api/gramjs/apiBuilders/gifts.ts
+++ b/src/api/gramjs/apiBuilders/gifts.ts
@@ -158,7 +158,7 @@ export function buildApiStarGiftAttribute(attribute: GramJs.TypeStarGiftAttribut
export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId: string): ApiSavedStarGift {
const {
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, upgradeStars, transferStars, canUpgrade,
- savedId, canExportAt, pinnedToTop, canResellAt, canTransferAt,
+ savedId, canExportAt, pinnedToTop, canResellAt, canTransferAt, prepaidUpgradeHash,
} = userStarGift;
const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId
@@ -183,6 +183,7 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId
canResellAt,
canTransferAt,
isPinned: pinnedToTop,
+ prepaidUpgradeHash,
};
}
diff --git a/src/api/gramjs/apiBuilders/messageActions.ts b/src/api/gramjs/apiBuilders/messageActions.ts
index a2d96ff0e..946b1099c 100644
--- a/src/api/gramjs/apiBuilders/messageActions.ts
+++ b/src/api/gramjs/apiBuilders/messageActions.ts
@@ -390,8 +390,8 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
}
if (action instanceof GramJs.MessageActionStarGift) {
const {
- nameHidden, saved, converted, upgraded, refunded, canUpgrade, gift, message, convertStars, upgradeMsgId,
- upgradeStars, fromId, peer, savedId,
+ nameHidden, saved, converted, upgraded, refunded, canUpgrade, prepaidUpgrade, gift, message, convertStars,
+ upgradeMsgId, giftMsgId, upgradeStars, fromId, peer, savedId, prepaidUpgradeHash,
} = action;
const starGift = buildApiStarGift(gift);
@@ -406,20 +406,23 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
isUpgraded: upgraded,
isRefunded: refunded,
canUpgrade,
+ isPrepaidUpgrade: prepaidUpgrade,
gift: starGift,
message: message && buildApiFormattedText(message),
starsToConvert: toJSNumber(convertStars),
upgradeMsgId,
+ giftMsgId,
alreadyPaidUpgradeStars: toJSNumber(upgradeStars),
fromId: fromId && getApiChatIdFromMtpPeer(fromId),
peerId: peer && getApiChatIdFromMtpPeer(peer),
savedId: savedId !== undefined ? buildApiPeerId(savedId, 'user') : undefined,
+ prepaidUpgradeHash,
};
}
if (action instanceof GramJs.MessageActionStarGiftUnique) {
const {
upgrade, transferred, saved, refunded, gift, canExportAt, transferStars, fromId, peer, savedId,
- resaleAmount,
+ resaleAmount, prepaidUpgrade,
} = action;
const starGift = buildApiStarGift(gift);
@@ -432,6 +435,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
isTransferred: transferred,
isSaved: saved,
isRefunded: refunded,
+ isPrepaidUpgrade: prepaidUpgrade,
gift: starGift,
canExportAt,
transferStars: toJSNumber(transferStars),
diff --git a/src/api/gramjs/apiBuilders/payments.ts b/src/api/gramjs/apiBuilders/payments.ts
index 8618e7358..ff8a2937f 100644
--- a/src/api/gramjs/apiBuilders/payments.ts
+++ b/src/api/gramjs/apiBuilders/payments.ts
@@ -553,7 +553,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
const {
date, id, peer, amount, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction,
subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages,
- stargiftResale, postsSearch,
+ stargiftResale, postsSearch, stargiftPrepaidUpgrade,
} = transaction;
if (photo) {
@@ -593,6 +593,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
isGiftResale: stargiftResale,
paidMessages,
isPostsSearch: postsSearch,
+ isPrepaidUpgrade: stargiftPrepaidUpgrade,
};
}
diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts
index fcf227868..394dca363 100644
--- a/src/api/gramjs/gramjsBuilders/index.ts
+++ b/src/api/gramjs/gramjsBuilders/index.ts
@@ -781,6 +781,13 @@ export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
});
}
+ case 'stargiftPrepaidUpgrade': {
+ return new GramJs.InputInvoiceStarGiftPrepaidUpgrade({
+ peer: buildInputPeer(invoice.peer.id, invoice.peer.accessHash),
+ hash: invoice.hash,
+ });
+ }
+
case 'giveaway':
default: {
const purpose = buildInputStorePaymentPurpose(invoice.purpose);
diff --git a/src/api/types/messageActions.ts b/src/api/types/messageActions.ts
index 811abd917..42d904260 100644
--- a/src/api/types/messageActions.ts
+++ b/src/api/types/messageActions.ts
@@ -242,14 +242,17 @@ export interface ApiMessageActionStarGift extends ActionMediaType {
isUpgraded?: true;
isRefunded?: true;
canUpgrade?: true;
+ isPrepaidUpgrade?: true;
gift: ApiStarGiftRegular;
message?: ApiFormattedText;
starsToConvert?: number;
upgradeMsgId?: number;
+ giftMsgId?: number;
alreadyPaidUpgradeStars?: number;
fromId?: string;
peerId?: string;
savedId?: string;
+ prepaidUpgradeHash?: string;
}
export interface ApiMessageActionStarGiftUnique extends ActionMediaType {
@@ -258,6 +261,7 @@ export interface ApiMessageActionStarGiftUnique extends ActionMediaType {
isTransferred?: true;
isSaved?: true;
isRefunded?: true;
+ isPrepaidUpgrade?: true;
gift: ApiStarGiftUnique;
canExportAt?: number;
transferStars?: number;
diff --git a/src/api/types/payments.ts b/src/api/types/payments.ts
index e76ff4456..7a909ef4c 100644
--- a/src/api/types/payments.ts
+++ b/src/api/types/payments.ts
@@ -399,10 +399,17 @@ export type ApiInputInvoiceStarGiftTransfer = {
recipientId: string;
};
+export type ApiInputInvoiceStarGiftPrepaidUpgrade = {
+ type: 'stargiftPrepaidUpgrade';
+ peerId: string;
+ hash: string;
+};
+
export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway
| ApiInputInvoiceGiftCode | ApiInputInvoicePremiumGiftStars | ApiInputInvoiceStars | ApiInputInvoiceStarsGift
| ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription
- | ApiInputInvoiceStarGiftUpgrade | ApiInputInvoiceStarGiftTransfer | ApiInputInvoiceStarGiftResale;
+ | ApiInputInvoiceStarGiftUpgrade | ApiInputInvoiceStarGiftTransfer | ApiInputInvoiceStarGiftResale
+ | ApiInputInvoiceStarGiftPrepaidUpgrade;
/* Used for Invoice request */
export type ApiRequestInputInvoiceMessage = {
@@ -472,11 +479,18 @@ export type ApiRequestInputInvoiceStarGiftTransfer = {
recipient: ApiPeer;
};
+export type ApiRequestInputInvoiceStarGiftPrepaidUpgrade = {
+ type: 'stargiftPrepaidUpgrade';
+ peer: ApiPeer;
+ hash: string;
+};
+
export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestInputInvoiceSlug
| ApiRequestInputInvoiceGiveaway | ApiRequestInputInvoiceStars | ApiRequestInputInvoiceStarsGiveaway
| ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift
| ApiRequestInputInvoiceStarGiftUpgrade | ApiRequestInputInvoiceStarGiftTransfer
- | ApiRequestInputInvoicePremiumGiftStars | ApiRequestInputInvoiceStarGiftResale;
+ | ApiRequestInputInvoicePremiumGiftStars | ApiRequestInputInvoiceStarGiftResale
+ | ApiRequestInputInvoiceStarGiftPrepaidUpgrade;
export interface ApiUniqueStarGiftValueInfo {
isLastSaleOnFragment?: true;
diff --git a/src/api/types/stars.ts b/src/api/types/stars.ts
index 7a7ee3271..7a9c4ab44 100644
--- a/src/api/types/stars.ts
+++ b/src/api/types/stars.ts
@@ -107,6 +107,7 @@ export interface ApiSavedStarGift {
canTransferAt?: number;
canResellAt?: number;
isPinned?: boolean;
+ prepaidUpgradeHash?: string;
isConverted?: boolean; // Local field, used for Action Message
upgradeMsgId?: number; // Local field, used for Action Message
localTag?: number; // Local field, used for key in list
@@ -244,6 +245,7 @@ export interface ApiStarsTransaction {
isGiftResale?: true;
paidMessages?: number;
isPostsSearch?: true;
+ isPrepaidUpgrade?: true;
}
export interface ApiStarsSubscription {
diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings
index 1aa5f2806..82dfc85de 100644
--- a/src/assets/localization/fallback.strings
+++ b/src/assets/localization/fallback.strings
@@ -1569,8 +1569,14 @@
"GiftUpgradeTextOwn" = "Turn your gift into a unique collectible that you can transfer or auction.";
"GiftUpgradeKeepDetails" = "Keep sender's name and comment";
"GiftUpgradeButton" = "Upgrade for {amount}";
+"GiftPayForUpgradeButton" = "Pay {amount} for Upgrade";
"GiftUpgradedTitle" = "This gift is now a collectible";
"GiftUpgradedDescription" = "You have received a unique number, model, backdrop and symbol for your gift.";
+"GiftPeerUpgradeUniqueDescription" = "{user} will get a unique number, model, backdrop, and symbol for the gift.";
+"GiftPeerUpgradeTransferableDescription" = "{user} will be able to send the gift to anyone on Telegram.";
+"GiftPeerUpgradeTradeableDescription" = "{user} will be able to auction the gift on third-party NFT marketplaces.";
+"GiftUpgradeSentTitle" = "Gift Upgrade sent!";
+"GiftUpgradeSentMessage" = "{user} can now upgrade this gift for free.";
"GiftMakeUnique" = "Make unique for {stars}";
"GiftMakeUniqueAcc" = "Make unique";
"GiftMakeUniqueDescription" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
@@ -1872,6 +1878,8 @@
"ActionStarGiftReceivedAnonymous" = "Someone sent you a gift for {cost}";
"ActionStarGiftSentChannel" = "{user} sent a gift to {channel} for {cost}";
"ActionStarGiftSentChannelYou" = "You sent a gift to {channel} for {cost}";
+"ActionStarGiftPrepaidUpgradeYou" = "You sent an upgrade worth {cost} for {peer}'s gift";
+"ActionStarGiftPrepaidUpgrade" = "{peer} sent an upgrade worth {cost} for your gift";
"ActionStarGiftSelfBought" = "You bought a gift for {cost}";
"ActionStarGiftTo" = "Gift to {peer}";
"ActionStarGiftFrom" = "Gift from {peer}";
@@ -2293,6 +2301,10 @@
"TitleGiftLocked" = "Gift Locked";
"GiftLockedMessage" = "This gift is currently only available to earlier Telegram users. It will unlock for your account in about **{relativeDate}**.";
"QuickPreview" = "Quick Preview";
+"GiftAnUpgradeButton" = "Gift an Upgrade";
+"GiftPrepaidUpgradeTransactionTitle" = "Gift Upgrade";
+"ActionStarGiftPrepaidUpgraded" = "{user} turned the gift into a unique collectible";
+"ActionStarGiftPrepaidUpgradedYou" = "You turned the gift into a unique collectible";
"UserNoteTitle" = "Notes";
"UserNoteHint" = "only visible to you";
"EditUserNoteHint" = "Notes are only visible to you.";
diff --git a/src/components/middle/message/ActionMessageText.tsx b/src/components/middle/message/ActionMessageText.tsx
index 395debe45..f8770963a 100644
--- a/src/components/middle/message/ActionMessageText.tsx
+++ b/src/components/middle/message/ActionMessageText.tsx
@@ -568,7 +568,7 @@ const ActionMessageText = ({
case 'starGift': {
const {
- gift, alreadyPaidUpgradeStars, peerId, savedId, fromId,
+ gift, alreadyPaidUpgradeStars, peerId, savedId, fromId, isPrepaidUpgrade,
} = action;
const isToChannel = Boolean(peerId && savedId);
@@ -576,9 +576,25 @@ const ActionMessageText = ({
const fromTitle = (fromPeer && getPeerTitle(lang, fromPeer)) || userFallbackText;
const fromLink = renderPeerLink(fromPeer?.id, fromTitle, asPreview);
+ const toPeer = peerId ? selectPeer(global, peerId) : undefined;
+ const toTitle = (toPeer && getPeerTitle(lang, toPeer))
+ || (isToChannel ? channelFallbackText : userFallbackText);
+ const toLink = renderPeerLink(toPeer?.id, toTitle, asPreview);
+
const starsAmount = gift.stars + (alreadyPaidUpgradeStars || 0);
const cost = renderStrong(formatStarsAsText(lang, starsAmount));
+ if (isPrepaidUpgrade && gift.upgradeStars) {
+ const upgradeCost = renderStrong(formatStarsAsText(lang, gift.upgradeStars));
+
+ return translateWithYou(
+ lang, 'ActionStarGiftPrepaidUpgrade', isOutgoing, {
+ peer: isOutgoing ? toLink : senderLink,
+ cost: upgradeCost,
+ },
+ );
+ }
+
if (isToChannel) {
const channelPeer = selectPeer(global, peerId!);
const isYou = fromPeer?.id === currentUserId;
@@ -607,7 +623,7 @@ const ActionMessageText = ({
case 'starGiftUnique': {
const {
- isTransferred, isUpgrade, savedId, peerId, fromId, resaleAmount, gift, transferStars,
+ isTransferred, isUpgrade, savedId, peerId, fromId, resaleAmount, gift, transferStars, isPrepaidUpgrade,
} = action;
const isToChannel = Boolean(peerId && savedId);
@@ -616,6 +632,18 @@ const ActionMessageText = ({
const fromTitle = (fromPeer && getPeerTitle(lang, fromPeer)) || userFallbackText;
const fromLink = renderPeerLink(fromPeer?.id, fromTitle, asPreview);
+ const toPeer = peerId ? selectPeer(global, peerId) : undefined;
+ const toTitle = (toPeer && getPeerTitle(lang, toPeer))
+ || (isToChannel ? channelFallbackText : userFallbackText);
+ const toLink = renderPeerLink(toPeer?.id, toTitle, asPreview);
+
+ if (isPrepaidUpgrade) {
+ if (isOutgoing) {
+ return lang('ActionStarGiftPrepaidUpgradedYou');
+ }
+ return lang('ActionStarGiftPrepaidUpgraded', { user: toLink }, { withNodes: true });
+ }
+
if (resaleAmount && !transferStars) {
const amountText = resaleAmount.currency === TON_CURRENCY_CODE
? formatTonAsText(lang, convertTonFromNanos(resaleAmount.amount))
diff --git a/src/components/modals/gift/UniqueGiftHeader.module.scss b/src/components/modals/gift/UniqueGiftHeader.module.scss
index 8c8d29a3f..4e6210eb1 100644
--- a/src/components/modals/gift/UniqueGiftHeader.module.scss
+++ b/src/components/modals/gift/UniqueGiftHeader.module.scss
@@ -93,7 +93,7 @@
}
.subtitle {
- max-width: 75%;
+ max-width: 85%;
font-size: 0.875rem;
text-align: center;
diff --git a/src/components/modals/gift/info/GiftInfoModal.tsx b/src/components/modals/gift/info/GiftInfoModal.tsx
index 076fce12e..b2b095d36 100644
--- a/src/components/modals/gift/info/GiftInfoModal.tsx
+++ b/src/components/modals/gift/info/GiftInfoModal.tsx
@@ -282,7 +282,8 @@ const GiftInfoModal = ({
const handleOpenUpgradeModal = useLastCallback(() => {
if (!savedGift) return;
- openGiftUpgradeModal({ giftId: savedGift.gift.id, gift: savedGift });
+ const giftOwnerId = renderingTargetPeer?.id;
+ openGiftUpgradeModal({ giftId: savedGift.gift.id, gift: savedGift, peerId: giftOwnerId });
});
const handleBuyGift = useLastCallback(() => {
@@ -376,6 +377,15 @@ const GiftInfoModal = ({
);
}
+ if (savedGift?.prepaidUpgradeHash) {
+ return (
+
+ );
+ }
+
return (