diff --git a/apps/desktop/package.json b/apps/desktop/package.json index d3366a6b8..c53e5a9ef 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@tonkeeper/desktop", "license": "Apache-2.0", - "version": "4.3.1", + "version": "4.3.3", "description": "Your desktop wallet on The Open Network", "main": ".webpack/main", "repository": { diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index 234629fc3..688be7552 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -85,6 +85,7 @@ import { CryptoStrategyInstaller } from '@tonkeeper/uikit/dist/components/pro/Cr import { localesList } from '@tonkeeper/locales/localesList'; import { useAppCountryInfo } from '@tonkeeper/uikit/dist/state/country'; import { SecureWalletNotification } from '@tonkeeper/uikit/dist/components/desktop/SecureWalletNotification'; +import { DesktopCancelLegacySubscriptionBanner } from '@tonkeeper/uikit/dist/components/legacy-plugins/DesktopCancelLegacySubscriptionBanner'; const queryClient = new QueryClient({ defaultOptions: { @@ -225,6 +226,7 @@ const WalletLayout = styled.div` `; const WalletLayoutBody = styled.div` + position: relative; flex: 1; display: flex; max-height: calc(100% - ${desktopHeaderContainerHeight}); @@ -426,6 +428,7 @@ const WalletContent = () => { + ); diff --git a/apps/extension/package.json b/apps/extension/package.json index b86190b8f..f8d1b90d8 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/extension", - "version": "4.3.1", + "version": "4.3.3", "author": "Ton APPS UK Limited ", "description": "Your extension wallet on The Open Network", "dependencies": { diff --git a/apps/mobile/package.json b/apps/mobile/package.json index cc9510eed..987f8994d 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/mobile", - "version": "4.3.1", + "version": "4.3.3", "license": "Apache-2.0", "description": "Your tablet wallet on The Open Network", "type": "module", diff --git a/apps/mobile/src/app/app-content/WideContent.tsx b/apps/mobile/src/app/app-content/WideContent.tsx index d4ec1927c..731075b6f 100644 --- a/apps/mobile/src/app/app-content/WideContent.tsx +++ b/apps/mobile/src/app/app-content/WideContent.tsx @@ -37,6 +37,7 @@ import { SplashScreen } from '@capacitor/splash-screen'; import { useRealtimeUpdatesInvalidation } from '@tonkeeper/uikit/dist/hooks/realtime'; import DashboardPage from '@tonkeeper/uikit/dist/desktop-pages/dashboard'; import { SecureWalletNotification } from '@tonkeeper/uikit/dist/components/desktop/SecureWalletNotification'; +import { DesktopCancelLegacySubscriptionBanner } from '@tonkeeper/uikit/dist/components/legacy-plugins/DesktopCancelLegacySubscriptionBanner'; const FullSizeWrapper = styled(Container)` max-width: 800px; @@ -191,6 +192,7 @@ const WalletContent = () => { + ); diff --git a/apps/web/package.json b/apps/web/package.json index d6213b2ab..86c745c63 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/web", - "version": "4.3.1", + "version": "4.3.3", "author": "Ton APPS UK Limited ", "description": "Your web wallet on The Open Network", "dependencies": { diff --git a/apps/web/src/AppDesktop.tsx b/apps/web/src/AppDesktop.tsx index c09122765..49005701d 100644 --- a/apps/web/src/AppDesktop.tsx +++ b/apps/web/src/AppDesktop.tsx @@ -39,6 +39,9 @@ import { import { DesktopMultisigOrdersPage } from "@tonkeeper/uikit/dist/desktop-pages/multisig-orders/DesktopMultisigOrders"; import { UrlTonConnectSubscription } from "./components/UrlTonConnectSubscription"; import { useRealtimeUpdatesInvalidation } from '@tonkeeper/uikit/dist/hooks/realtime'; +import { + DesktopCancelLegacySubscriptionBanner +} from "@tonkeeper/uikit/dist/components/legacy-plugins/DesktopCancelLegacySubscriptionBanner"; const DesktopAccountSettingsPage = React.lazy( () => import('@tonkeeper/uikit/dist/desktop-pages/settings/DesktopAccountSettingsPage') @@ -137,6 +140,7 @@ const WalletLayout = styled.div` `; const WalletLayoutBody = styled.div` + position: relative; flex: 1; display: flex; max-height: calc(100% - ${desktopHeaderContainerHeight}); @@ -280,6 +284,7 @@ const WalletContent = () => { + ); diff --git a/package.json b/package.json index 83e726036..dfb782f2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tonkeeper-web", - "version": "4.3.1", + "version": "4.3.2", "repository": { "type": "git", "url": "https://github.com/tonkeeper/tonkeeper-web.git" diff --git a/packages/core/src/entries/tonConnect.ts b/packages/core/src/entries/tonConnect.ts index f633d7041..e80e28153 100644 --- a/packages/core/src/entries/tonConnect.ts +++ b/packages/core/src/entries/tonConnect.ts @@ -199,10 +199,19 @@ const signDataFeatureSchema = z.object({ }); export type SignDataFeature = z.infer; +const subscriptionFeatureSchema = z.object({ + name: z.literal('Subscription'), + versions: z.object({ + v2: z.boolean() + }) +}); +export type SubscriptionFeature = z.infer; + export const featureSchema = z.union([ sendTransactionFeatureDeprecatedSchema, sendTransactionFeatureSchema, - signDataFeatureSchema + signDataFeatureSchema, + subscriptionFeatureSchema ]); export type Feature = z.infer; @@ -268,9 +277,78 @@ const keyPairSchema = z.object({ }); export type KeyPair = z.infer; -const rpcMethodSchema = z.enum(['disconnect', 'sendTransaction', 'signData']); +const rpcMethodSchema = z.enum([ + 'disconnect', + 'sendTransaction', + 'signData', + 'createSubscriptionV2', + 'cancelSubscriptionV2' +]); export type RpcMethod = z.infer; +const createSubscriptionV2RpcRequestSchema = z.object({ + id: z.string(), + method: z.literal('createSubscriptionV2'), + params: z.tuple([z.string()]) +}); +export type CreateSubscriptionV2RpcRequest = z.infer; + +const cancelSubscriptionV2RpcRequestSchema = z.object({ + id: z.string(), + method: z.literal('cancelSubscriptionV2'), + params: z.tuple([z.string()]) +}); +export type CancelSubscriptionV2RpcRequest = z.infer; + +export enum SUBSCRIPTION_V2_ERROR_CODES { + UNKNOWN_ERROR = 0, + BAD_REQUEST_ERROR = 1, + UNKNOWN_APP_ERROR = 100, + USER_REJECTS_ERROR = 300, + METHOD_NOT_SUPPORTED = 400, + EXTENSION_NOT_FOUND = 404 +} + +const createSubscriptionV2RpcResponseSuccessSchema = z.object({ + id: z.string(), + result: z.object({ + boc: z.string() + }) +}); +export type CreateSubscriptionV2RpcResponseSuccess = z.infer< + typeof createSubscriptionV2RpcResponseSuccessSchema +>; + +const createSubscriptionV2RpcResponseErrorSchema = z.object({ + id: z.string(), + error: z.object({ + code: z.nativeEnum(SUBSCRIPTION_V2_ERROR_CODES), + message: z.string() + }) +}); +export type CreateSubscriptionV2RpcResponseError = z.infer< + typeof createSubscriptionV2RpcResponseErrorSchema +>; + +const cancelSubscriptionV2RpcResponseSuccessSchema = z.object({ + id: z.string(), + result: z.object({}) +}); +export type CancelSubscriptionV2RpcResponseSuccess = z.infer< + typeof cancelSubscriptionV2RpcResponseSuccessSchema +>; + +const cancelSubscriptionV2RpcResponseErrorSchema = z.object({ + id: z.string(), + error: z.object({ + code: z.nativeEnum(SUBSCRIPTION_V2_ERROR_CODES), + message: z.string() + }) +}); +export type CancelSubscriptionV2RpcResponseError = z.infer< + typeof cancelSubscriptionV2RpcResponseErrorSchema +>; + export enum SEND_TRANSACTION_ERROR_CODES { UNKNOWN_ERROR = 0, BAD_REQUEST_ERROR = 1, @@ -365,7 +443,9 @@ export type SignDataRequestPayload = z.infer; @@ -411,6 +491,14 @@ const rpcResponsesSchema = z.object({ disconnect: z.object({ error: disconnectRpcResponseErrorSchema, success: disconnectRpcResponseSuccessSchema + }), + createSubscriptionV2: z.object({ + error: createSubscriptionV2RpcResponseErrorSchema, + success: createSubscriptionV2RpcResponseSuccessSchema + }), + cancelSubscriptionV2: z.object({ + error: cancelSubscriptionV2RpcResponseErrorSchema, + success: cancelSubscriptionV2RpcResponseSuccessSchema }) }); export type RpcResponses = z.infer; @@ -424,7 +512,11 @@ export const walletResponseSchema = z.union([ signDataRpcResponseSuccessSchema, signDataRpcResponseErrorSchema, disconnectRpcResponseSuccessSchema, - disconnectRpcResponseErrorSchema + disconnectRpcResponseErrorSchema, + createSubscriptionV2RpcResponseSuccessSchema, + createSubscriptionV2RpcResponseErrorSchema, + cancelSubscriptionV2RpcResponseSuccessSchema, + cancelSubscriptionV2RpcResponseErrorSchema ]); export type WalletResponse = WalletResponseSuccess | WalletResponseError; @@ -439,7 +531,9 @@ assertTypesEqual>(true); export const appRequestSchema = z.union([ sendTransactionRpcRequestSchema, signDataRpcRequestSchema, - disconnectRpcRequestSchema + disconnectRpcRequestSchema, + createSubscriptionV2RpcRequestSchema, + cancelSubscriptionV2RpcRequestSchema ]); export type AppRequest = RpcRequests[T]; assertTypesEqual, z.infer>(true); @@ -476,9 +570,39 @@ export interface SignDatAppRequest< payload: SignDataRequestPayload; } +export interface CreateSubscriptionV2AppRequest< + T extends AccountConnection['type'] = AccountConnection['type'] +> { + id: string; + connection: T extends 'http' + ? AccountConnectionHttp + : T extends 'injected' + ? AccountConnectionInjected + : AccountConnection; + kind: 'createSubscriptionV2'; + payload: CreateSubscriptionV2Payload; +} + +export interface CancelSubscriptionV2AppRequest< + T extends AccountConnection['type'] = AccountConnection['type'] +> { + id: string; + connection: T extends 'http' + ? AccountConnectionHttp + : T extends 'injected' + ? AccountConnectionInjected + : AccountConnection; + kind: 'cancelSubscriptionV2'; + payload: CancelSubscriptionV2Payload; +} + export type TonConnectAppRequestPayload< T extends AccountConnection['type'] = AccountConnection['type'] -> = SendTransactionAppRequest | SignDatAppRequest; +> = + | SendTransactionAppRequest + | SignDatAppRequest + | CreateSubscriptionV2AppRequest + | CancelSubscriptionV2AppRequest; export interface InjectedWalletInfo { name: string; @@ -497,3 +621,45 @@ export interface ITonConnectInjectedBridge { send(message: AppRequest): Promise>; listen(callback: (event: WalletEvent) => void): () => void; } + +export type SubscriptionMetadataSource = z.infer; + +export const subscriptionMetadataSchema = z.object({ + logo: z.string().url(), + name: z.string(), + description: z.string(), + link: z.string().url(), + tos: z.string().url(), + merchant: z.string(), + website: z.string().url() +}); + +export const subscriptionSchema = z.object({ + beneficiary: z.string(), + id: z.number(), + period: z.number().int().positive(), + amount: z.string(), + first_charge_date: z.number(), + withdraw_address: z.string(), + withdraw_msg_body: z.string(), + metadata: subscriptionMetadataSchema +}); + +export const createSubscriptionV2PayloadSchema = z.object({ + validUntil: z.number(), + subscription: subscriptionSchema, + from: rawAddressSchema.optional(), + network: tonConnectNetworkSchema, + valid_until: z.number() +}); + +export const cancelSubscriptionV2PayloadSchema = z.object({ + validUntil: z.number(), + extensionAddress: z.string(), + from: rawAddressSchema.optional(), + network: tonConnectNetworkSchema, + valid_until: z.number() +}); + +export type CreateSubscriptionV2Payload = z.infer; +export type CancelSubscriptionV2Payload = z.infer; diff --git a/packages/core/src/service/ton-blockchain/encoder/subscription-encoder/subscription-encoder.ts b/packages/core/src/service/ton-blockchain/encoder/subscription-encoder/subscription-encoder.ts index 1325adfed..c7ba7d296 100644 --- a/packages/core/src/service/ton-blockchain/encoder/subscription-encoder/subscription-encoder.ts +++ b/packages/core/src/service/ton-blockchain/encoder/subscription-encoder/subscription-encoder.ts @@ -158,8 +158,6 @@ export class SubscriptionEncoder { private encodeWithdrawMsgBody(message?: string): Cell { if (!message) return Cell.EMPTY; - const boc = Buffer.from(message, 'hex'); - - return Cell.fromBoc(boc)[0]; + return beginCell().storeBuffer(Buffer.from(message, 'utf8')).endCell(); } } diff --git a/packages/core/src/service/tonConnect/connectService.ts b/packages/core/src/service/tonConnect/connectService.ts index ff8b6b74b..8a5180285 100644 --- a/packages/core/src/service/tonConnect/connectService.ts +++ b/packages/core/src/service/tonConnect/connectService.ts @@ -16,6 +16,7 @@ import { SEND_TRANSACTION_ERROR_CODES, SendTransactionRpcResponseError, SendTransactionRpcResponseSuccess, + SUBSCRIPTION_V2_ERROR_CODES, TonAddressItemReply, TonConnectEventPayload, TonConnectNetwork, @@ -211,6 +212,10 @@ export const getDeviceInfo = ( { name: 'SignData', types: ['text', 'binary', 'cell'] + }, + { + name: 'Subscription', + versions: { v2: true } } ] }; @@ -608,3 +613,51 @@ export async function checkTonConnectFromAndNetwork( } } } + +export interface ICreateSubscriptionV2Response { + boc: string; + extensionAddress: string; +} + +export const createSubscriptionV2SuccessResponse = ( + id: string, + result: ICreateSubscriptionV2Response +) => { + return { + id, + result + }; +}; + +export const createSubscriptionV2ErrorResponse = ( + id: string, + message = 'User rejected subscription' +) => { + return { + id, + error: { + code: SUBSCRIPTION_V2_ERROR_CODES.USER_REJECTS_ERROR, + message + } + }; +}; + +export const cancelSubscriptionV2SuccessResponse = (id: string, boc: string) => { + return { + id, + result: { boc } + }; +}; + +export const cancelSubscriptionV2ErrorResponse = ( + id: string, + message = 'User rejected cancellation' +) => { + return { + id, + error: { + code: SUBSCRIPTION_V2_ERROR_CODES.USER_REJECTS_ERROR, + message + } + }; +}; diff --git a/packages/locales/src/tonkeeper-web/ar.json b/packages/locales/src/tonkeeper-web/ar.json index 969463692..d85dfe396 100644 --- a/packages/locales/src/tonkeeper-web/ar.json +++ b/packages/locales/src/tonkeeper-web/ar.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "بعد حذف جميع المحافظ، ستصبح ميزات PRO غير متاحة. سيتم استعادتها بعد تسجيل الدخول إلى محفظة تحتوي على اشتراك نشط.", "deleting_wallet_warning": "بعد حذف المحفظة، ستصبح ميزات PRO غير متاحة. سيتم استعادتها بعد تسجيل الدخول باستخدام محفظة تحتوي على اشتراك نشط.", "desktop_only": "سطح المكتب فقط", + "disable": "تعطيل", "disabled": "معطل", "disconnect": "الغاء ربط", "disconnect_all_apps": "الغاء ربط كل التطبيقات", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "رمز الاستجابة السريعة (QR) غير المتوقع", "unknown_operation": "عملية غير معروفة", "Unlock": "فتح القفل", + "unsubscribe_legacy_plugin_banner_subtitle": "قد يستمر في سحب الأموال من محفظتك. عَطِّلْهُ لتفادي دفعات إضافية.", + "unsubscribe_legacy_plugin_banner_title": "لديك {count} اشتراك منتهي من @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "قد يستمر بعضها في خصم الأموال من حسابك.", + "unsubscribe_legacy_plugin_success_toast": "سيتم تأكيد إلغاء الاشتراك في البلوكشين خلال بضع دقائق", "unsupported_two_fa": "لا يتم دعم المحافظ التي تستخدم التحقق بخطوتين مؤقتًا. استخدم محفظة بدون التحقق بخطوتين أو قم بتعطيلها.\n", "unused": { "Edit_jettons": "تعديل", diff --git a/packages/locales/src/tonkeeper-web/bg.json b/packages/locales/src/tonkeeper-web/bg.json index e9ce902b2..fbb0b5d4b 100644 --- a/packages/locales/src/tonkeeper-web/bg.json +++ b/packages/locales/src/tonkeeper-web/bg.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "След изтриване на всички портфейли, PRO функциите ще станат недостъпни. Те ще бъдат възстановени след влизане в портфейл с активен абонамент.", "deleting_wallet_warning": "След изтриване на портфейла PRO функциите ще станат недостъпни. Те ще бъдат възстановени след влизане с портфейл с активен абонамент.", "desktop_only": "Само за настолен компютър", + "disable": "Деактивиране", "disabled": "Деактивирано", "disconnect": "Прекъсване", "disconnect_all_apps": "Прекъснете всички приложения", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Неочакван QR код", "unknown_operation": "Неизвестна операция", "Unlock": "Отключи", + "unsubscribe_legacy_plugin_banner_subtitle": "Може да продължи да таксува портфейла ви. Деактивирайте го, за да избегнете допълнителни плащания.", + "unsubscribe_legacy_plugin_banner_title": "Имате {count} изтекъл абонамент от @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Някои от тях може да продължат да таксуват вашия портфейл.", + "unsubscribe_legacy_plugin_success_toast": "Отмяната на абонамента ще бъде потвърдена в блокчейна след няколко минути", "unsupported_two_fa": "Портфейли с 2FA временно не се поддържат. Използвайте портфейл без 2FA или го деактивирайте.\n", "unused": { "add_wallet_existing_multisig_description": "%{number} портфейла, управлявани от вашия списък с портфейли", diff --git a/packages/locales/src/tonkeeper-web/bn.json b/packages/locales/src/tonkeeper-web/bn.json index 9fc971535..ea8dbc55c 100644 --- a/packages/locales/src/tonkeeper-web/bn.json +++ b/packages/locales/src/tonkeeper-web/bn.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "সমস্ত ওয়ালেট মুছে ফেলার পর, PRO বৈশিষ্ট্যগুলি আর উপলব্ধ থাকবে না। সক্রিয় সাবস্ক্রিপশন থাকা কোনও ওয়ালেটে লগ ইন করলে এগুলি পুনরুদ্ধার হবে।", "deleting_wallet_warning": "ওয়ালেট মুছে ফেললে, PRO বৈশিষ্ট্যগুলি আর ব্যবহারযোগ্য থাকবে না। একটি সক্রিয় সাবস্ক্রিপশন থাকা ওয়ালেট দিয়ে লগ ইন করলে এগুলো পুনরুদ্ধার করা হবে।", "desktop_only": "শুধুমাত্র ডেস্কটপ", + "disable": "নিষ্ক্রিয়", "disabled": "নিষ্ক্রিয়", "disconnect": "সংযোগ বিচ্ছিন্ন করুন", "disconnect_all_apps": "সমস্ত অ্যাপস সংযোগ বিচ্ছিন্ন করুন", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "অপ্রত্যাশিত QR কোড", "unknown_operation": "অজানা অপারেশন", "Unlock": "আনলক করুন", + "unsubscribe_legacy_plugin_banner_subtitle": "এটি আপনার ওয়ালেট থেকে চার্জ করতে পারে। পুনরায় অর্থপ্রদান এড়াতে এটি নিষ্ক্রিয় করুন।", + "unsubscribe_legacy_plugin_banner_title": "আপনার @donate থেকে {count} মেয়াদোত্তীর্ণ সদস্যতা আছে", + "unsubscribe_legacy_plugin_many_banner_subtitle": "তাদের মধ্যে কিছু আপনার ওয়ালেট চার্জ করতে পারে।", + "unsubscribe_legacy_plugin_success_toast": "গ্রাহকতা বাতিল কয়েক মিনিটের মধ্যে ব্লকচেইনে নিশ্চিত হবে", "unsupported_two_fa": "২এফএ ওয়ালেটগুলো আপাতত সমর্থিত নয়। ২এফএ ছাড়া একটি ওয়ালেট ব্যবহার করুন বা এটিকে নিষ্ক্রিয় করুন।\n", "unused": { "Edit_jettons": "সম্পাদনা করুন", diff --git a/packages/locales/src/tonkeeper-web/de.json b/packages/locales/src/tonkeeper-web/de.json index 8db7e2c4c..5445c0b25 100644 --- a/packages/locales/src/tonkeeper-web/de.json +++ b/packages/locales/src/tonkeeper-web/de.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Nach dem Löschen aller Wallets sind die PRO-Funktionen nicht mehr verfügbar. Sie werden wiederhergestellt, nachdem Sie sich mit einer Wallet mit aktivem Abonnement angemeldet haben.", "deleting_wallet_warning": "Nach dem Löschen der Wallet sind die PRO-Funktionen nicht mehr verfügbar. Sie werden wiederhergestellt, sobald Sie sich mit einer Wallet mit aktivem Abonnement anmelden.", "desktop_only": "Nur für Desktop", + "disable": "Deaktivieren", "disabled": "Deaktiviert", "disconnect": "Trennen", "disconnect_all_apps": "Alle Apps trennen", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Unerwarteter QR-Code", "unknown_operation": "Unbekannte Operation", "Unlock": "Entsperren", + "unsubscribe_legacy_plugin_banner_subtitle": "Es könnte weiterhin Ihre Geldbörse belasten. Deaktivieren Sie es, um weitere Zahlungen zu vermeiden.", + "unsubscribe_legacy_plugin_banner_title": "Sie haben {count} abgelaufene Abonnement von @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Einige davon könnten weiterhin Ihr Wallet belasten.", + "unsubscribe_legacy_plugin_success_toast": "Das Abonnement wird in wenigen Minuten in der Blockchain bestätigt werden", "unsupported_two_fa": "2FA-Wallets werden vorübergehend nicht unterstützt. Verwenden Sie ein Wallet ohne 2FA oder deaktivieren Sie es.\n", "unused": { "add_wallet_existing_multisig_description": "%{number} Wallets werden von Ihrer Wallet-Liste verwaltet", diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index 90b0828ed..6132622b5 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "After deleting all wallets, PRO features will become unavailable. They will be restored after logging in with a wallet that has an active subscription.", "deleting_wallet_warning": "After deleting the wallet, PRO features will become unavailable. They will be restored after logging in with a wallet that has an active subscription.", "desktop_only": "DESKTOP ONLY", + "disable": "Disable", "disabled": "Disabled", "disconnect": "Disconnect", "disconnect_all_apps": "Disconnect All Apps", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Unexpected QR Code", "unknown_operation": "Unknown Operation", "Unlock": "Unlock", + "unsubscribe_legacy_plugin_banner_subtitle": "It may continue charging your wallet. Disable it to avoid further payments.", + "unsubscribe_legacy_plugin_banner_title": "You have {count} expired subscription from @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Some of them may continue charging your wallet.", + "unsubscribe_legacy_plugin_success_toast": "Subscription cancellation will be confirmed in blockchain in few minutes ", "unsupported_two_fa": "2FA wallets temporarily aren’t supported. Use a wallet without 2FA or disable it.\n", "unused": { "1_month": "1 month", diff --git a/packages/locales/src/tonkeeper-web/es.json b/packages/locales/src/tonkeeper-web/es.json index 8fcaa3a28..8c43ac9ac 100644 --- a/packages/locales/src/tonkeeper-web/es.json +++ b/packages/locales/src/tonkeeper-web/es.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Después de eliminar todas las billeteras, las funciones PRO dejarán de estar disponibles. Se restaurarán después de iniciar sesión con una billetera que tenga una suscripción activa.", "deleting_wallet_warning": "Después de eliminar la billetera, las funciones PRO dejarán de estar disponibles. Se restaurarán después de iniciar sesión con una billetera que tenga una suscripción activa.", "desktop_only": "Solo para escritorio", + "disable": "Desactivar", "disabled": "Desactivado", "disconnect": "Desconectar", "disconnect_all_apps": "Desconectar Todas las Aplicaciones", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Código QR inesperado", "unknown_operation": "Operación desconocida", "Unlock": "Desbloquear", + "unsubscribe_legacy_plugin_banner_subtitle": "Puede seguir cobrando a su cartera. Desactívelo para evitar más pagos.", + "unsubscribe_legacy_plugin_banner_title": "Tienes {count} suscripción expirada de @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Algunos de ellos pueden seguir cobrando de tu billetera.", + "unsubscribe_legacy_plugin_success_toast": "La cancelación de la suscripción será confirmada en blockchain en unos minutos", "unsupported_two_fa": "Las carteras con 2FA no son soportadas temporalmente. Utiliza una cartera sin 2FA o desactívala.\n", "unused": { "Edit_jettons": "Editar", diff --git a/packages/locales/src/tonkeeper-web/fa.json b/packages/locales/src/tonkeeper-web/fa.json index 64a94f72c..ad4858ad4 100644 --- a/packages/locales/src/tonkeeper-web/fa.json +++ b/packages/locales/src/tonkeeper-web/fa.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "پس از حذف همه کیف‌پول‌ها، قابلیت‌های PRO دیگر در دسترس نخواهند بود. این قابلیت‌ها پس از ورود به کیف‌پولی با اشتراک فعال بازیابی می‌شوند.", "deleting_wallet_warning": "پس از حذف کیف پول، امکانات PRO دیگر در دسترس نخواهند بود. این امکانات پس از ورود با کیف پولی که اشتراک فعال دارد، بازیابی خواهند شد.", "desktop_only": "فقط دسکتاپ", + "disable": "غیرفعال کردن", "disabled": "غیرفعال", "disconnect": "قطع کردن", "disconnect_all_apps": "قطع کردن تمام اپلیکیشن‌ها", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "کد QR نامعتبر", "unknown_operation": "عملیات ناشناخته", "Unlock": "باز کردن", + "unsubscribe_legacy_plugin_banner_subtitle": "این ممکن است همچنان کیف پول شما را شارژ کند. برای جلوگیری از پرداخت‌های بیشتر آن را غیرفعال کنید.", + "unsubscribe_legacy_plugin_banner_title": "شما {count} اشتراک منقضی شده از @donate دارید", + "unsubscribe_legacy_plugin_many_banner_subtitle": "بعضی از آنها ممکن است به کسر از کیف پول شما ادامه دهند.", + "unsubscribe_legacy_plugin_success_toast": "تأیید لغو اشتراک در بلاکچین تا چند دقیقه دیگر انجام خواهد شد", "unsupported_two_fa": "کیف پول‌های دارای 2FA به‌طور موقت پشتیبانی نمی‌شوند. از کیف پولی بدون 2FA استفاده کنید یا آن را غیرفعال نمایید.\n", "unused": { "Edit_jettons": "ویرایش", diff --git a/packages/locales/src/tonkeeper-web/fr.json b/packages/locales/src/tonkeeper-web/fr.json index 32454aa31..4e363bd77 100644 --- a/packages/locales/src/tonkeeper-web/fr.json +++ b/packages/locales/src/tonkeeper-web/fr.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Après avoir supprimé tous les portefeuilles, les fonctionnalités PRO deviendront indisponibles. Elles seront rétablies après connexion à un portefeuille avec un abonnement actif.", "deleting_wallet_warning": "Après avoir supprimé le portefeuille, les fonctionnalités PRO deviendront indisponibles. Elles seront restaurées après connexion avec un portefeuille ayant un abonnement actif.", "desktop_only": "Uniquement sur ordinateur", + "disable": "Désactiver", "disabled": "Désactivé", "disconnect": "Déconnecter", "disconnect_all_apps": "Déconnecter toutes les applications", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "QR code inattendu", "unknown_operation": "Opération inconnue", "Unlock": "Déverrouiller", + "unsubscribe_legacy_plugin_banner_subtitle": "Il peut continuer à facturer votre portefeuille. Désactivez-le pour éviter d'autres paiements.", + "unsubscribe_legacy_plugin_banner_title": "Vous avez {count} abonnement expiré de @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Certains d'entre eux pourraient continuer à débiter votre portefeuille.", + "unsubscribe_legacy_plugin_success_toast": "L'annulation de l'abonnement sera confirmée sur la blockchain dans quelques minutes", "unsupported_two_fa": "Les portefeuilles 2FA ne sont temporairement pas supportés. Utilisez un portefeuille sans 2FA ou désactivez-le.\n", "update": "Mettre à jour", "update_click_to_install": "Cliquez pour installer la dernière version", diff --git a/packages/locales/src/tonkeeper-web/hi.json b/packages/locales/src/tonkeeper-web/hi.json index 4a4500d13..0d6d2c3bf 100644 --- a/packages/locales/src/tonkeeper-web/hi.json +++ b/packages/locales/src/tonkeeper-web/hi.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "सभी वॉलेट हटाने के बाद, PRO फीचर्स उपलब्ध नहीं रहेंगे। वे एक सक्रिय सदस्यता वाले वॉलेट से लॉग इन करने पर पुनः उपलब्ध हो जाएंगे।", "deleting_wallet_warning": "वॉलेट हटाने के बाद PRO सुविधाएँ उपलब्ध नहीं रहेंगी। यह सुविधाएँ एक सक्रिय सदस्यता वाले वॉलेट के साथ लॉगिन करने पर पुनः सक्षम हो जाएंगी।", "desktop_only": "केवल डेस्कटॉप", + "disable": "अक्षम", "disabled": "अक्षम", "disconnect": "डिस्कनेक्ट करें", "disconnect_all_apps": "सभी ऐप्स डिस्कनेक्ट करें", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "अप्रत्याशित क्यूआर कोड", "unknown_operation": "अज्ञात ऑपरेशन", "Unlock": "अनलॉक करें", + "unsubscribe_legacy_plugin_banner_subtitle": "\"यह आपकी वॉलेट से चार्ज करना जारी रख सकता है। आगे भुगतान से बचने के लिए इसे निष्क्रिय करें।\"", + "unsubscribe_legacy_plugin_banner_title": "आपके पास @donate से {count} समाप्त सदस्यता है", + "unsubscribe_legacy_plugin_many_banner_subtitle": "उनमें से कुछ आपके वॉलेट से शुल्क वसूलते रह सकते हैं।", + "unsubscribe_legacy_plugin_success_toast": "सदस्यता रद्दीकरण की पुष्टि ब्लॉकचेन में कुछ ही मिनटों में होगी", "unsupported_two_fa": "2FA वॉलेट्स अस्थायी रूप से समर्थित नहीं हैं। बिना 2FA के वॉलेट का उपयोग करें या इसे अक्षम करें।\n", "unused": { "Edit_jettons": "संपादित करें", diff --git a/packages/locales/src/tonkeeper-web/id.json b/packages/locales/src/tonkeeper-web/id.json index dc7f5c69e..516a15a16 100644 --- a/packages/locales/src/tonkeeper-web/id.json +++ b/packages/locales/src/tonkeeper-web/id.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Setelah menghapus semua dompet, fitur PRO akan menjadi tidak tersedia. Fitur tersebut akan dipulihkan setelah login dengan dompet yang memiliki langganan aktif.", "deleting_wallet_warning": "Setelah dompet dihapus, fitur PRO akan menjadi tidak tersedia. Fitur tersebut akan dipulihkan setelah masuk dengan dompet yang memiliki langganan aktif.", "desktop_only": "Hanya untuk Desktop", + "disable": "Nonaktifkan", "disabled": "Dinonaktifkan", "disconnect": "Putuskan sambungan", "disconnect_all_apps": "Putuskan Sambungan Semua Aplikasi", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Kode QR tidak sesuai", "unknown_operation": "Operasi tidak dikenal", "Unlock": "Membuka Kunci", + "unsubscribe_legacy_plugin_banner_subtitle": "Ini dapat terus mengenakan biaya ke dompet Anda. Nonaktifkan untuk menghindari pembayaran lebih lanjut.", + "unsubscribe_legacy_plugin_banner_title": "Anda memiliki {count} langganan kedaluwarsa dari @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Beberapa di antaranya mungkin terus membebankan dompet Anda.", + "unsubscribe_legacy_plugin_success_toast": "Pembatalan langganan akan dikonfirmasi di blockchain dalam beberapa menit", "unsupported_two_fa": "Dompet 2FA sementara tidak didukung. Gunakan dompet tanpa 2FA atau nonaktifkan.\n", "unused": { "Edit_jettons": "Edit", diff --git a/packages/locales/src/tonkeeper-web/it.json b/packages/locales/src/tonkeeper-web/it.json index bbc90f824..cc7c091b1 100644 --- a/packages/locales/src/tonkeeper-web/it.json +++ b/packages/locales/src/tonkeeper-web/it.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Dopo aver eliminato tutti i wallet, le funzionalità PRO non saranno più disponibili. Verranno ripristinate dopo l’accesso con un wallet che dispone di un abbonamento attivo.", "deleting_wallet_warning": "Dopo aver eliminato il portafoglio, le funzionalità PRO non saranno più disponibili. Verranno ripristinate dopo l'accesso con un portafoglio che ha un abbonamento attivo.", "desktop_only": "Solo su desktop", + "disable": "Disabilitare", "disabled": "Disabilitato", "disconnect": "Disconnetti", "disconnect_all_apps": "Disconnetti tutte le app", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Codice QR inatteso", "unknown_operation": "Operazione sconosciuta", "Unlock": "Sblocca", + "unsubscribe_legacy_plugin_banner_subtitle": "Potrebbe continuare a prelevare denaro dal tuo portafoglio. Disattivalo per evitare ulteriori pagamenti.", + "unsubscribe_legacy_plugin_banner_title": "Hai {count} abbonamento scaduto da @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Alcuni di essi potrebbero continuare ad addebitare il tuo portafoglio.", + "unsubscribe_legacy_plugin_success_toast": "La cancellazione dell'abbonamento sarà confermata sulla blockchain entro pochi minuti", "unsupported_two_fa": "I portafogli 2FA temporaneamente non sono supportati. Usa un portafoglio senza 2FA o disabilitalo.\n", "update": "Aggiorna", "update_click_to_install": "Clicca per installare la versione più recente", diff --git a/packages/locales/src/tonkeeper-web/pa.json b/packages/locales/src/tonkeeper-web/pa.json index 957c811eb..ad4742471 100644 --- a/packages/locales/src/tonkeeper-web/pa.json +++ b/packages/locales/src/tonkeeper-web/pa.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "ਸਾਰੇ ਵਾਲਿਟ ਮਿਟਾਉਣ ਤੋਂ ਬਾਅਦ, PRO ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਉਪਲੱਬਧ ਨਹੀਂ ਰਹਿਣਗੀਆਂ। ਇਹ ਉਹ ਦੁਬਾਰਾ ਤਦ ਉਪਲੱਬਧ ਹੋਣਗੀਆਂ ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਸਰਗਰਮ ਸਬਸਕ੍ਰਿਪਸ਼ਨ ਵਾਲੇ ਵਾਲਿਟ ਨਾਲ ਲੌਗ ਇਨ ਕਰਦੇ ਹੋ।", "deleting_wallet_warning": "ਵਾਲਟ ਨੂੰ ਹਟਾਉਣ ਤੋਂ ਬਾਅਦ, PRO ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਉਪਲਬਧ ਨਹੀਂ ਰਹਿਣਗੀਆਂ। ਇਹਾਂ ਨੂੰ ਇੱਕ ਸਰਗਰਮ ਸਭਸਕ੍ਰਿਪਸ਼ਨ ਵਾਲੇ ਵਾਲਟ ਨਾਲ ਲਾਗਇਨ ਕਰਨ ਉਪਰੰਤ ਮੁੜ ਬਹਾਲ ਕਰ ਦਿੱਤਾ ਜਾਵੇਗਾ।", "desktop_only": "ਕੇਵਲ ਡੈਸਕਟਾਪ", + "disable": "ਅਣਚਾਲੂ", "disabled": "ਅਸਮਰਥ", "disconnect": "ਡਿਸਕਨੈਕਟ ਕਰੋ", "disconnect_all_apps": "ਸਭ ਐਪਸ ਦਾ ਕੁਨੈਕਸ਼ਨ ਤੋੜੋ", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "ਅਣਪੇक्षित QR ਕੋਡ", "unknown_operation": "ਅਣਜਾਣ ਓਪਰੇਸ਼ਨ", "Unlock": "ਲਾਕ ਖੋਲ੍ਹੋ", + "unsubscribe_legacy_plugin_banner_subtitle": "ਇਹ ਤੁਹਾਡੇ ਵੌਲੇਟ ਨੂੰ ਚਾਰਜ ਕਰਦਾ ਰਹਿ ਸਕਦਾ ਹੈ। ਹੋਰ ਭੁਗਤਾਨਾਂ ਤੋਂ ਬਚਣ ਲਈ ਇਸਨੂੰ ਬੰਦ ਕਰੋ।", + "unsubscribe_legacy_plugin_banner_title": "ਤੁਹਾਡੇ ਕੋਲ {count} ਸਬਸਕ੍ਰਿਪਸ਼ਨ ਮਿਆਦ ਪੁੱਗ ਚੁੱਕਾ ਹੈ @donate ਤੋਂ", + "unsubscribe_legacy_plugin_many_banner_subtitle": "ਇਨ੍ਹਾਂ ਵਿੱਚੋਂ ਕੁਝ ਤੁਹਾਡੇ ਵੌਲਟ ਨੂੰ ਚਾਰਜ ਕਰਦੇ ਰਹਿ ਸਕਦੇ ਹਨ।", + "unsubscribe_legacy_plugin_success_toast": "ਸਬਸਕ੍ਰਿਪਸ਼ਨ ਰੱਦ ਕੀਤੀ ਜਾਣ ਦੀ ਪੁਸ਼ਟੀ ਬਲੌਕਚੇਨ ਵਿੱਚ ਕੁਝ ਮਿੰਟਾਂ ਵਿੱਚ ਕੀਤੀ ਜਾਵੇਗੀ", "unsupported_two_fa": "2FA ਬਟੂਆਂ ਨੂੰ ਅਸਥਾਈ ਤੌਰ ਤੇ ਸਮਰਥਨ ਨਹੀਂ ਕੀਤਾ ਗਿਆ।\n2FA ਤੋਂ ਬਿਨਾ ਬਟੂਆ ਵਰਤੋਂ ਵਿੱਚ ਲਿਆਵੋ ਜਾਂ ਇਸਨੂੰ ਬੰਦ ਕਰੋ।\n", "update": "ਅੱਪਡੇਟ ਕਰੋ", "update_click_to_install": "ਆਖਰੀ ਸੰਸਕਰਣ ਨੂੰ ਇੰਸਟਾਲ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ", diff --git a/packages/locales/src/tonkeeper-web/pt.json b/packages/locales/src/tonkeeper-web/pt.json index e6bfec081..a89906c35 100644 --- a/packages/locales/src/tonkeeper-web/pt.json +++ b/packages/locales/src/tonkeeper-web/pt.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Após excluir todas as carteiras, os recursos PRO ficarão indisponíveis. Eles serão restaurados após o login com uma carteira que possua uma assinatura ativa.", "deleting_wallet_warning": "Após excluir a carteira, os recursos PRO ficarão indisponíveis. Eles serão restaurados após o login com uma carteira que tenha uma assinatura ativa.", "desktop_only": "Somente para desktop", + "disable": "Desativar", "disabled": "Desativado", "disconnect": "Desconectar", "disconnect_all_apps": "Desconectar todos os aplicativos", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Código QR inesperado", "unknown_operation": "Operação desconhecida", "Unlock": "Desbloquear", + "unsubscribe_legacy_plugin_banner_subtitle": "Ele pode continuar debitando sua carteira. Desative-o para evitar cobranças futuras.", + "unsubscribe_legacy_plugin_banner_title": "Você tem {count} assinatura expirada de @doar", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Alguns deles podem continuar a cobrar da sua carteira.", + "unsubscribe_legacy_plugin_success_toast": "O cancelamento da assinatura será confirmado no blockchain em poucos minutos", "unsupported_two_fa": "Carteiras 2FA temporariamente não são suportadas. Utilize uma carteira sem 2FA ou desative-o.\n", "update": "Atualizar", "update_click_to_install": "Clique para instalar a versão mais recente", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index 2ea61b132..df0cc9618 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "После удаления всех кошельков PRO-функции станут недоступны. Они вернутся после входа в кошелёк с активной подпиской.", "deleting_wallet_warning": "После удаления кошелька PRO-функции станут недоступны. Они вернутся после входа в кошелёк с активной подпиской.", "desktop_only": "ТОЛЬКО ПК", + "disable": "Отключить", "disabled": "Отключено", "disconnect": "Отключить", "disconnect_all_apps": "Отключите все приложения", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Неожиданный QR-код", "unknown_operation": "Неизвестная операция", "Unlock": "Разблокировать", + "unsubscribe_legacy_plugin_banner_subtitle": "Он может продолжать списывать средства. Отключите его, чтобы избежать дальнейших платежей.", + "unsubscribe_legacy_plugin_banner_title": "У вас {count} истекшая подписка на @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Некоторые из них могут продолжать списывать средства с вашего кошелька.", + "unsubscribe_legacy_plugin_success_toast": "Отмена подписки будет подтверждена в блокчейне через несколько минут", "unsupported_two_fa": "Кошельки с двухфакторной аутентификацией временно не поддерживаются. Используйте кошелек без 2FA или отключите ее.\n", "unused": { "1_month": "1 месяц", diff --git a/packages/locales/src/tonkeeper-web/tr-TR.json b/packages/locales/src/tonkeeper-web/tr-TR.json index 9eb7c8fba..640e1e854 100644 --- a/packages/locales/src/tonkeeper-web/tr-TR.json +++ b/packages/locales/src/tonkeeper-web/tr-TR.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Tüm cüzdanlar silindikten sonra, PRO özellikler kullanılamaz hale gelir. Aktif aboneliği olan bir cüzdanla giriş yaptıktan sonra tekrar kullanılabilir olacaklardır.", "deleting_wallet_warning": "Cüzdan silindikten sonra PRO özellikler kullanılamaz hale gelecektir. Aktif aboneliği olan bir cüzdanla giriş yaptıktan sonra bu özellikler geri yüklenecektir.", "desktop_only": "Yalnızca masaüstü", + "disable": "Devre Dışı Bırak", "disabled": "Devre Dışı", "disconnect": "Bağlantıyı kesin", "disconnect_all_apps": "Tüm uygulamaların bağlantısını kesin", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Beklenmeyen QR Kodu", "unknown_operation": "Bilinmeyen işlem", "Unlock": "Kilidi Aç", + "unsubscribe_legacy_plugin_banner_subtitle": "Cüzdanınızdan ücret almaya devam edebilir. Daha fazla ödeme yapılmasını önlemek için devre dışı bırakın.", + "unsubscribe_legacy_plugin_banner_title": "{count} adet süresi dolmuş aboneliğiniz var @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Bazıları cüzdanınızı ücretlendirmeye devam edebilir.", + "unsubscribe_legacy_plugin_success_toast": "Aboneliğin iptali birkaç dakika içerisinde blok zincirde onaylanacaktır", "unsupported_two_fa": "2FA cüzdanlar geçici olarak desteklenmiyor. 2FA olmayan bir cüzdan kullanın veya devre dışı bırakın.", "unused": { "Edit_jettons": "Düzenle", diff --git a/packages/locales/src/tonkeeper-web/uk.json b/packages/locales/src/tonkeeper-web/uk.json index 8afe877a4..0ca76b3f8 100644 --- a/packages/locales/src/tonkeeper-web/uk.json +++ b/packages/locales/src/tonkeeper-web/uk.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Після видалення всіх гаманців функції PRO стануть недоступними. Вони будуть відновлені після входу в гаманець з активною підпискою.", "deleting_wallet_warning": "Після видалення гаманця функції PRO стануть недоступними. Вони будуть відновлені після входу в гаманець з активною підпискою.", "desktop_only": "Тільки для ПК", + "disable": "Вимкнути", "disabled": "Вимкнено", "disconnect": "Відключити", "disconnect_all_apps": "Відключіть усі додатки", @@ -693,8 +694,15 @@ "Unexpected_QR_Code": "Неочікуваний QR-код", "unknown_operation": "Невідома операція", "Unlock": "Розблокувати", + "unsubscribe_legacy_plugin_banner_subtitle": "Він може продовжити стягувати кошти з вашого рахунку. Вимкніть, щоб уникнути додаткових платежів.", + "unsubscribe_legacy_plugin_banner_title": "У вас {count} прострочених підписок від @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Деякі з них можуть продовжувати стягувати кошти з вашого гаманця.", + "unsubscribe_legacy_plugin_success_toast": "Скасування підписки буде підтверджено в блокчейні через кілька хвилин", "unsupported_two_fa": "Гаманці з двофакторною автентифікацією тимчасово не підтримуються. Використовуйте гаманець без 2FA або вимкніть його.\n", "unused": { + "1_month": "1 місяць", + "1_year": "1 рік", + "add_wallet_existing_multisig_title": "Існуючий гаманець Multisig", "Edit_jettons": "Редагувати", "Enable_storing_config": "Збереження конфігурації", "ge_header_history": "Історія", diff --git a/packages/locales/src/tonkeeper-web/uz.json b/packages/locales/src/tonkeeper-web/uz.json index 257f049fa..92bb8b87f 100644 --- a/packages/locales/src/tonkeeper-web/uz.json +++ b/packages/locales/src/tonkeeper-web/uz.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Barcha hamyonlarni o‘chirib tashlaganingizdan so‘ng PRO funksiyalari ishlamaydi. Faol obunasi bor hamyon bilan tizimga kirganingizdan so‘ng ular tiklanadi.", "deleting_wallet_warning": "Hamyon o‘chirib tashlangandan so‘ng PRO funksiyalari mavjud bo‘lmaydi. Ular faollashtirilgan obunaga ega hamyon bilan tizimga kirilgandan so‘ng qayta tiklanadi.", "desktop_only": "Faqat kompyuterda", + "disable": "O‘chirish", "disabled": "O‘chirilgan", "disconnect": "Ochirish", "disconnect_all_apps": "Barcha ilovalarni o'chirish", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Kutilmagan QR kodi", "unknown_operation": "Nomaʼlum operatsiya", "Unlock": "Qulfni ochish", + "unsubscribe_legacy_plugin_banner_subtitle": "U bu sizning hamyoningizdan to'lovni davom ettirish mumkin. Kelgusida to'lovlardan qochish uchun uni o'chiring.", + "unsubscribe_legacy_plugin_banner_title": "Sizda @donate'dan {count} eskirgan obuna mavjud", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Ba'zilari hamyoningizdan to'lov olishni davom ettirishi mumkin.", + "unsubscribe_legacy_plugin_success_toast": "Obunani bekor qilish blokcheynda bir necha daqiqada tasdiqlanadi", "unsupported_two_fa": "Ikki faktorli autentifikatsiya hamyonlari vaqtincha qo'llab-quvvatlanmaydi. Ikkita autentifikatsiyasiz hamyon ishlating yoki uni o'chiring.\n", "unused": { "add_wallet_existing_multisig_description": "%{number} hamyonlar sizning hamyon ro'yxatingiz tomonidan boshqariladi", diff --git a/packages/locales/src/tonkeeper-web/vi.json b/packages/locales/src/tonkeeper-web/vi.json index d251688a0..483e46446 100644 --- a/packages/locales/src/tonkeeper-web/vi.json +++ b/packages/locales/src/tonkeeper-web/vi.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "Sau khi xóa tất cả ví, các tính năng PRO sẽ không khả dụng. Chúng sẽ được khôi phục sau khi đ��ng nhập bằng ví có đăng ký còn hiệu lực.", "deleting_wallet_warning": "Sau khi xóa ví, các tính năng PRO sẽ không còn khả dụng. Chúng sẽ được khôi phục sau khi đăng nhập bằng ví có đăng ký đang hoạt động.", "desktop_only": "Chỉ dành cho máy tính để bàn", + "disable": "Tắt", "disabled": "Đã tắt", "disconnect": "Ngắt kết nối", "disconnect_all_apps": "Ngắt kết nối tất cả ứng dụng", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "Mã QR không mong đợi", "unknown_operation": "Hoạt động không xác định", "Unlock": "Mở khóa", + "unsubscribe_legacy_plugin_banner_subtitle": "Nó có thể tiếp tục tính phí ví của bạn. Vô hiệu hóa nó để tránh các khoản thanh toán thêm.", + "unsubscribe_legacy_plugin_banner_title": "Bạn có {count} đăng ký hết hạn từ @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "Một số trong số họ có thể tiếp tục thu phí ví của bạn.", + "unsubscribe_legacy_plugin_success_toast": "Việc hủy đăng ký sẽ được xác nhận trên blockchain trong vài phút", "unsupported_two_fa": "Ví 2FA tạm thời không được hỗ trợ. Sử dụng ví không có 2FA hoặc vô hiệu hóa nó.\n", "update": "Cập nhật", "update_click_to_install": "Nhấp để cài đặt phiên bản mới nhất", diff --git a/packages/locales/src/tonkeeper-web/zh-Hans-CN.json b/packages/locales/src/tonkeeper-web/zh-Hans-CN.json index 86a6c7235..a2d5a6aae 100644 --- a/packages/locales/src/tonkeeper-web/zh-Hans-CN.json +++ b/packages/locales/src/tonkeeper-web/zh-Hans-CN.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "删除所有钱包后,PRO功能将不可用。使用具有有效订阅的钱包登录后,这些功能将恢复。", "deleting_wallet_warning": "删除钱包后,PRO 功能将不可用。使用具有有效订阅的钱包登录后,这些功能将被恢复。", "desktop_only": "仅限桌面", + "disable": "禁用", "disabled": "已禁用", "disconnect": "断开连接", "disconnect_all_apps": "断开所有应用程序", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "非預期的二维码", "unknown_operation": "未知操作", "Unlock": "解锁", + "unsubscribe_legacy_plugin_banner_subtitle": "它 可能会继续 收费。禁用 它 以 避免 进一步 付款。", + "unsubscribe_legacy_plugin_banner_title": "您有 {count} 个已过期的订阅 从 @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "他们中的一些可能会继续向您的 wallet 收费。", + "unsubscribe_legacy_plugin_success_toast": "订阅取消将在区块链中确认几分钟后", "unsupported_two_fa": "暂不支持 2FA 钱包。请使用不带 2FA 的钱包或禁用 2FA。\n", "unused": { "add_wallet_existing_multisig_description": "%{number} 个钱包由您的钱包列表管理", diff --git a/packages/locales/src/tonkeeper-web/zh-Hant.json b/packages/locales/src/tonkeeper-web/zh-Hant.json index 63e11b700..a0317b4dc 100644 --- a/packages/locales/src/tonkeeper-web/zh-Hant.json +++ b/packages/locales/src/tonkeeper-web/zh-Hant.json @@ -147,6 +147,7 @@ "deleting_wallets_warning": "刪除所有錢包後,PRO 功能將不可用。登入具有有效訂閱的錢包後,這些功能將恢復。", "deleting_wallet_warning": "刪除錢包後,PRO 功能將無法使用。使用具有有效訂閱的錢包登入後,這些功能將恢復。", "desktop_only": "僅限桌面", + "disable": "停用", "disabled": "已停用", "disconnect": "斷開連接", "disconnect_all_apps": "斷開連接所有應用程式", @@ -693,6 +694,10 @@ "Unexpected_QR_Code": "出現未預期的二維碼", "unknown_operation": "未知操作", "Unlock": "解鎖", + "unsubscribe_legacy_plugin_banner_subtitle": "它 可能 會 繼續 向 你 的 賬戶 收費。 禁用 它 以 避免 更多 付款。", + "unsubscribe_legacy_plugin_banner_title": "你有 {count} 個過期的訂閱來自 @donate", + "unsubscribe_legacy_plugin_many_banner_subtitle": "有些可能會持續從你的錢包扣款。", + "unsubscribe_legacy_plugin_success_toast": "訂閱取消將在幾分鐘內於 blockchain 中確認", "unsupported_two_fa": "2FA 錢包暫時不支援。請使用沒有 2FA 的錢包或停用 2FA。\n", "unused": { "add_wallet_existing_multisig_description": "%{number} 個錢包由您的錢包列表管理", diff --git a/packages/locales/src/tonkeeper/uk.json b/packages/locales/src/tonkeeper/uk.json index 290a9730e..4df9d87d3 100644 --- a/packages/locales/src/tonkeeper/uk.json +++ b/packages/locales/src/tonkeeper/uk.json @@ -141,7 +141,7 @@ "exchange_title": "Купити TON", "import_add_wallet": "Додати гаманець", "import_add_wallet_description": "Створіть новий гаманець або додайте існуючий.", - "import_existing_wallet": "Існуючий Гаманець", + "import_existing_wallet": "Імпортувати існуючий гаманець", "import_existing_wallet_description": "Імпорт гаманця за допомогою 24 секретних фраз відновлення", "import_new_wallet": "Новий Гаманець", "import_new_wallet_description": "Створіть новий гаманець", @@ -555,12 +555,11 @@ "description": { "empty": "Обмінюйте через Tonkeeper, надсилайте токени та NFT.", "less_10": "У вас достатньо заряду батареї для менше ніж 10 транзакцій.", - "other": { - "few": "{count} заряди", - "many": "{count} зарядів", - "one": "{count} заряд", - "other": "{count} зарядів" - } + "other": "У вас достатньо заряду акумулятора для виконання більше ніж % транзакцій.", + "other.few": "{count} заряди", + "other.many": "{count} зарядів", + "other.one": "{count} заряд", + "other.other": "{count} зарядів" }, "max_input_amount": "Максимальна сума {amount}", "ok": "OK", @@ -870,6 +869,7 @@ "info_about_inactive_title": "Неактивний контракт", "intro_item3_caption": "-", "intro_item3_title": "-", + "it_expires_on": "Термін дії закінчується", "jetton_buy_tether": "Купити USD₮", "jetton_id_copied": "ID токена скопійовано", "jetton_locked_till": "Заблоковано до {date}", @@ -1096,6 +1096,8 @@ "title": "Ви впевнені, що хочете відкрити зовнішнє посилання?" } }, + "pro_subscription_is_active": "Підписка Pro активна.", + "pro_trial_is_active": "Пробна версія активна.", "receive_address_title": "Або використовуйте адресу гаманця", "receive_copy": "Копіювати", "receiveModal": { @@ -1212,6 +1214,9 @@ "send_title": "Відправити {currency}", "settings_appearance": "Зовнішній вигляд", "settings_bank_card": "Банківська картка", + "settings_delete_alert_button": "Видалити обліковий запис та дані", + "settings_delete_alert_caption": "Ця дія призведе до видалення вашого акаунту та всіх даних з цього додатку.", + "settings_delete_alert_title": "Ви впевнені, що хочете видалити свій акаунт?", "settings_delete_signer_account": "Видалити акаунт?", "settings_delete_watch_account": "Видалити обліковий запис для перегляду?", "settings_delete_watch_account_button": "Видалити", diff --git a/packages/uikit/src/components/connect/InstallSubscriptionV2Notification.tsx b/packages/uikit/src/components/connect/InstallSubscriptionV2Notification.tsx new file mode 100644 index 000000000..4e8be35d1 --- /dev/null +++ b/packages/uikit/src/components/connect/InstallSubscriptionV2Notification.tsx @@ -0,0 +1,329 @@ +import { + CreateSubscriptionV2Payload, + SubscriptionMetadataSource +} from '@tonkeeper/core/dist/entries/tonConnect'; +import React, { FC, useEffect, useMemo } from 'react'; +import styled from 'styled-components'; +import { useTranslation } from '../../hooks/translation'; +import { SpinnerIcon } from '../Icon'; +import { Notification, NotificationFooter, NotificationFooterPortal } from '../Notification'; +import { Body2, Body3, Label2 } from '../Text'; +import { Button } from '../fields/Button'; +import { ConfirmMainButtonProps } from '../transfer/common'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; +import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { ListBlock, ListItem, ListItemPayload } from '../List'; +import { secondsToUnitCount } from '@tonkeeper/core/dist/utils/pro'; +import { useFormatFiat, useRate } from '../../state/rates'; +import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; +import { ErrorBoundary } from '../shared/ErrorBoundary'; +import { fallbackRenderOver } from '../Error'; +import { + useCreateSubscription, + useEstimateDeploySubscription +} from '../../hooks/blockchain/subscription'; +import { + ConfirmView, + ConfirmViewAdditionalBottomSlot, + ConfirmViewButtons, + ConfirmViewButtonsSlot, + ConfirmViewDetailsSlot, + ConfirmViewHeadingSlot +} from '../transfer/ConfirmView'; +import { ProActiveWallet } from '../pro/ProActiveWallet'; +import { ProSubscriptionHeader } from '../pro/ProSubscriptionHeader'; +import { + CryptoCurrency, + SubscriptionExtension, + SubscriptionExtensionMetadata, + SubscriptionExtensionStatus, + SubscriptionExtensionVersion +} from '@tonkeeper/core/dist/pro'; +import { useProCompatibleAccountsWallets } from '../../state/wallet'; +import { backwardCompatibilityFilter } from '@tonkeeper/core/dist/service/proService'; +import { toNano } from '@ton/core'; +import { ICreateSubscriptionV2Response } from '@tonkeeper/core/dist/service/tonConnect/connectService'; + +interface IProInstallExtensionProps { + isOpen: boolean; + onClose: (result?: ICreateSubscriptionV2Response) => void; + extensionData?: SubscriptionExtension; +} + +function toSubscriptionMetadata(src: SubscriptionMetadataSource): SubscriptionExtensionMetadata { + return { + l: src.logo, + n: src.name, + u: src.link, + m: src.merchant, + w: src.website, + ...(src.description ? { d: src.description } : {}), + ...(src.tos ? { t: src.tos } : {}) + }; +} + +export const InstallSubscriptionV2Notification: FC<{ + params: CreateSubscriptionV2Payload | null; + handleClose: (result?: ICreateSubscriptionV2Response) => void; +}> = ({ params, handleClose }) => { + const subscription = params?.subscription; + + if (!subscription || !params?.from) return null; + + const extensionData: SubscriptionExtension = { + version: SubscriptionExtensionVersion.V2, + status: SubscriptionExtensionStatus.NOT_INITIALIZED, + admin: subscription.beneficiary, + recipient: subscription.withdraw_address, + subscription_id: subscription.id, + first_charging_date: 0, + last_charging_date: 0, + grace_period: 0, + payment_per_period: subscription.amount, + currency: CryptoCurrency.TON, + created_at: Date.now(), + deploy_value: toNano('0.11').toString(), + destroy_value: toNano('0.08').toString(), + caller_fee: '10000000', + payer: params.from, + contract: '', + period: subscription.period, + withdraw_msg_body: subscription.withdraw_msg_body, + metadata: toSubscriptionMetadata(subscription.metadata) + }; + + return ( + <> + handleClose()} + hideButton + backShadow + > + {() => ( + + {extensionData && ( + + )} + + )} + + + ); +}; + +const ProInstallExtensionNotificationContent: FC< + Required> +> = ({ onClose, extensionData }) => { + const { t } = useTranslation(); + const deployMutation = useCreateSubscription(); + const estimateFeeMutation = useEstimateDeploySubscription(); + const { + data: estimation, + error: estimationError, + isLoading: isEstimating + } = estimateFeeMutation; + + const accountsWallets = useProCompatibleAccountsWallets(backwardCompatibilityFilter); + + const accountWallet = accountsWallets.find( + accWallet => accWallet.wallet.id === extensionData.payer + ); + + const selectedWallet = accountWallet?.wallet; + + const { data: rate } = useRate(CryptoCurrency.TON); + + const { fiatAmount: fiatEquivalent } = useFormatFiat( + rate, + formatDecimals(extensionData.payment_per_period) + ); + const { fiatAmount: feeEquivalent } = useFormatFiat( + rate, + formatDecimals(estimation?.fee?.extra?.stringWeiAmount ?? 0) + ); + + useEffect(() => { + if (!selectedWallet) return; + + estimateFeeMutation.mutate({ + selectedWallet, + ...extensionData + }); + }, [selectedWallet]); + + const price = useMemo( + () => + new AssetAmount({ + asset: TON_ASSET, + weiAmount: extensionData.payment_per_period + }), + [extensionData?.payment_per_period] + ); + + const deployMutate = async () => { + if (!selectedWallet) { + throw new Error('Selected wallet is required!'); + } + + const result = await deployMutation.mutateAsync({ + subscriptionParams: { + selectedWallet, + ...extensionData + }, + options: { + disableAddressCheck: true + } + }); + + setTimeout(() => { + onClose(result); + }, 1500); + + return !!result; + }; + + const { + unit: periodUnit, + count: periodCount, + form: periodForm + } = secondsToUnitCount(extensionData.period); + + return ( + onClose()} + estimation={{ ...estimateFeeMutation }} + {...deployMutation} + mutateAsync={deployMutate} + > + + + + + + + + + + + + {t('price')} + + {price.toStringAssetRelativeAmount()} + {`≈ ${fiatEquivalent}`} + + + + + + + {t('interval')} + + {t(`every_${periodUnit}_${periodForm}`, { + count: periodCount + })} + + + + + + + {t('swap_blockchain_fee')} + + + {isEstimating && } + {!!estimationError && <>—} + {estimation?.fee?.extra && + estimateFeeMutation.data.fee.extra.toStringAssetRelativeAmount()} + + {!estimationError && !isEstimating && ( + {`≈ ${feeEquivalent}`} + )} + + + + + + + + + + + + + + + ); +}; + +export const ConfirmMainButton: ConfirmMainButtonProps = props => { + const { isLoading, isDisabled, onClick } = props; + + const { t } = useTranslation(); + + return ( + + ); +}; + +const Body2Styled = styled(Body2)` + color: ${props => props.theme.textSecondary}; +`; + +const Body3Styled = styled(Body3)` + color: ${props => props.theme.textSecondary}; +`; + +const ProSubscriptionHeaderStyled = styled(ProSubscriptionHeader)` + margin-bottom: 0; +`; + +const NotificationStyled = styled(Notification)` + max-width: 650px; + + @media (pointer: fine) { + &:hover { + [data-swipe-button] { + color: ${p => p.theme.textSecondary}; + } + } + } +`; + +const ListItemStyled = styled(ListItem)` + &:not(:first-child) > div { + padding-top: 10px; + } +`; + +const ListItemPayloadStyled = styled(ListItemPayload)<{ alignItems?: string }>` + padding-top: 10px; + padding-bottom: 10px; + + align-items: ${({ alignItems }) => alignItems ?? 'center'}; +`; + +const FiatEquivalentWrapper = styled.div` + display: grid; + justify-items: end; +`; diff --git a/packages/uikit/src/components/connect/RemoveSubscriptionV2Notification.tsx b/packages/uikit/src/components/connect/RemoveSubscriptionV2Notification.tsx new file mode 100644 index 000000000..b930e2df4 --- /dev/null +++ b/packages/uikit/src/components/connect/RemoveSubscriptionV2Notification.tsx @@ -0,0 +1,276 @@ +import React, { FC, useEffect, useMemo } from 'react'; +import styled from 'styled-components'; +import { useTranslation } from '../../hooks/translation'; +import { SpinnerIcon } from '../Icon'; +import { Notification, NotificationFooter, NotificationFooterPortal } from '../Notification'; +import { Body2, Body3, Label2 } from '../Text'; +import { Button } from '../fields/Button'; +import { ConfirmMainButtonProps } from '../transfer/common'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; +import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { ListItem, ListItemPayload } from '../List'; +import { useFormatFiat, useRate } from '../../state/rates'; +import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; +import { ErrorBoundary } from '../shared/ErrorBoundary'; +import { fallbackRenderOver } from '../Error'; +import { + useCancelSubscription, + useEstimateRemoveExtension +} from '../../hooks/blockchain/subscription'; +import { + ConfirmView, + ConfirmViewButtons, + ConfirmViewButtonsSlot, + ConfirmViewDetailsSlot, + ConfirmViewHeadingSlot +} from '../transfer/ConfirmView'; +import { ProSubscriptionHeader } from '../pro/ProSubscriptionHeader'; +import { CryptoCurrency } from '@tonkeeper/core/dist/pro'; +import { useProCompatibleAccountsWallets } from '../../state/wallet'; +import { backwardCompatibilityFilter } from '@tonkeeper/core/dist/service/proService'; +import { useToast } from '../../hooks/useNotification'; +import { useDateTimeFormat } from '../../hooks/useDateTimeFormat'; +import { hexToRGBA } from '../../libs/css'; +import { toNano } from '@ton/core'; +import { CancelSubscriptionV2Payload } from '@tonkeeper/core/dist/entries/tonConnect'; + +export const RemoveSubscriptionV2Notification: FC<{ + params: CancelSubscriptionV2Payload | null; + handleClose: (boc?: string) => void; +}> = ({ params, handleClose }) => { + return ( + <> + handleClose()} + hideButton + backShadow + > + {() => ( + + {!!params?.extensionAddress && ( + + )} + + )} + + + ); +}; + +const ProRemoveSubscriptionV2NotificationContent: FC<{ + params: any; + onClose: (boc?: string) => void; +}> = ({ onClose, params }) => { + const { extensionAddress, from } = params; + const destroyValue = toNano('0.05').toString(); + + const accountsWallets = useProCompatibleAccountsWallets(backwardCompatibilityFilter); + + const accountWallet = accountsWallets.find(accWallet => accWallet.wallet.id === from); + + const selectedWallet = accountWallet?.wallet; + const finalExpiresDate = new Date(); + + const toast = useToast(); + const { t } = useTranslation(); + const formatDate = useDateTimeFormat(); + const { data: rate } = useRate(CryptoCurrency.TON); + + const removeMutation = useCancelSubscription(); + const estimateFeeMutation = useEstimateRemoveExtension(); + const { + data: estimation, + error: estimationError, + isLoading: isEstimating + } = estimateFeeMutation; + + const { fiatAmount: feeEquivalent } = useFormatFiat( + rate, + formatDecimals(estimation?.fee?.extra?.stringWeiAmount ?? 0) + ); + + useEffect(() => { + if (!removeMutation.isSuccess || !finalExpiresDate) return; + + toast( + `${t('extension_cancellation_success')} ${formatDate(finalExpiresDate, { + day: 'numeric', + month: 'short', + year: 'numeric', + inputUnit: 'seconds' + })}` + ); + }, [removeMutation.isSuccess]); + + useEffect(() => { + if (!selectedWallet) return; + + estimateFeeMutation.mutate({ + destroyValue, + selectedWallet, + extensionContract: extensionAddress + }); + }, [selectedWallet]); + + const removeMutate = async () => { + if (!selectedWallet) { + throw new Error('Selected wallet not found!'); + } + + const boc = await removeMutation.mutateAsync({ + destroyValue, + selectedWallet, + extensionContract: extensionAddress + }); + + setTimeout(() => { + onClose(boc); + }, 1500); + + return !!boc; + }; + + const deployReserve = useMemo( + () => + new AssetAmount({ + asset: TON_ASSET, + weiAmount: destroyValue + }), + [destroyValue] + ); + + return ( + onClose()} + estimation={{ ...estimateFeeMutation }} + {...removeMutation} + mutateAsync={removeMutate} + > + + + + + + {finalExpiresDate && ( + + + {t('will_be_active_until')} + + {formatDate(finalExpiresDate, { + day: 'numeric', + month: 'short', + year: 'numeric', + inputUnit: 'seconds' + })} + + + + )} + + + + {t('swap_blockchain_fee')} + + + {isEstimating && } + {!!estimationError && <>—} + {estimation?.fee?.extra && + estimation.fee.extra.toStringAssetRelativeAmount()} + + {!estimationError && !isEstimating && ( + {`≈ ${feeEquivalent}`} + )} + + + + + + + + + + + + + + ); +}; + +export const ConfirmMainButton: ConfirmMainButtonProps = props => { + const { isLoading, isDisabled, onClick } = props; + + const { t } = useTranslation(); + + return ( + + {t('cancel_subscription')} + + ); +}; + +const Body2Styled = styled(Body2)` + color: ${props => props.theme.textSecondary}; +`; + +const Body3Styled = styled(Body3)` + color: ${props => props.theme.textSecondary}; +`; + +const ProSubscriptionHeaderStyled = styled(ProSubscriptionHeader)` + margin-bottom: 0; +`; + +const NotificationStyled = styled(Notification)` + max-width: 650px; + + @media (pointer: fine) { + &:hover { + [data-swipe-button] { + color: ${p => p.theme.textSecondary}; + } + } + } +`; + +const ListItemStyled = styled(ListItem)` + &:not(:first-child) > div { + padding-top: 10px; + } +`; + +const ListItemPayloadStyled = styled(ListItemPayload)` + padding-top: 10px; + padding-bottom: 10px; +`; + +const FiatEquivalentWrapper = styled.div` + display: grid; + justify-items: end; +`; + +const CancelButtonStyled = styled(Button)` + color: ${p => p.theme.accentRed}; + background-color: ${({ theme }) => hexToRGBA(theme.accentRed, 0.16)}; + transition: background-color 0.1s ease-in; + + &:enabled:hover { + background-color: ${({ theme }) => hexToRGBA(theme.accentRed, 0.12)}; + } +`; diff --git a/packages/uikit/src/components/connect/TonConnectRequestNotification.tsx b/packages/uikit/src/components/connect/TonConnectRequestNotification.tsx index 6ee32e6e1..7f5f89c2a 100644 --- a/packages/uikit/src/components/connect/TonConnectRequestNotification.tsx +++ b/packages/uikit/src/components/connect/TonConnectRequestNotification.tsx @@ -9,6 +9,10 @@ import { FC } from 'react'; import { TonTransactionNotification } from './TonTransactionNotification'; import { SignDataNotification } from './SignDataNotification'; import { + cancelSubscriptionV2ErrorResponse, + cancelSubscriptionV2SuccessResponse, + createSubscriptionV2ErrorResponse, + createSubscriptionV2SuccessResponse, sendTransactionErrorResponse, sendTransactionSuccessResponse } from '@tonkeeper/core/dist/service/tonConnect/connectService'; @@ -16,6 +20,8 @@ import { useTrackerTonConnectSendSuccess, useTrackTonConnectActionRequest } from '../../hooks/analytics/events-hooks'; +import { InstallSubscriptionV2Notification } from './InstallSubscriptionV2Notification'; +import { RemoveSubscriptionV2Notification } from './RemoveSubscriptionV2Notification'; export const TonConnectRequestNotification: FC<{ request: TonConnectAppRequestPayload | undefined; @@ -72,6 +78,30 @@ export const TonConnectRequestNotification: FC<{ } }} /> + { + if (request) { + handleClose( + result + ? createSubscriptionV2SuccessResponse(request.id, result) + : createSubscriptionV2ErrorResponse(request.id) + ); + } + }} + /> + { + if (request) { + handleClose( + boc + ? cancelSubscriptionV2SuccessResponse(request.id, boc) + : cancelSubscriptionV2ErrorResponse(request.id) + ); + } + }} + /> ); }; diff --git a/packages/uikit/src/components/connect/WebTonConnectSubscription.tsx b/packages/uikit/src/components/connect/WebTonConnectSubscription.tsx index 1e0ecd596..8224470be 100644 --- a/packages/uikit/src/components/connect/WebTonConnectSubscription.tsx +++ b/packages/uikit/src/components/connect/WebTonConnectSubscription.tsx @@ -96,6 +96,26 @@ const WebTonConnectSubscription = () => { }; return openNotification(params.connection.clientSessionId, value); } + case 'createSubscriptionV2': { + setRequest(undefined); + const value: TonConnectAppRequestPayload = { + connection: params.connection, + id: params.request.id, + kind: 'createSubscriptionV2', + payload: JSON.parse(params.request.params[0]) + }; + return openNotification(params.connection.clientSessionId, value); + } + case 'cancelSubscriptionV2': { + setRequest(undefined); + const value: TonConnectAppRequestPayload = { + connection: params.connection, + id: params.request.id, + kind: 'cancelSubscriptionV2', + payload: JSON.parse(params.request.params[0]) + }; + return openNotification(params.connection.clientSessionId, value); + } default: { return badRequestResponse({ ...params, diff --git a/packages/uikit/src/components/desktop/pro/ProInstallExtensionNotification.tsx b/packages/uikit/src/components/desktop/pro/ProInstallExtensionNotification.tsx index 1ab2efaf9..7bb54889d 100644 --- a/packages/uikit/src/components/desktop/pro/ProInstallExtensionNotification.tsx +++ b/packages/uikit/src/components/desktop/pro/ProInstallExtensionNotification.tsx @@ -114,10 +114,14 @@ const ProInstallExtensionNotificationContent: FC< throw new Error('Selected wallet is required!'); } - return deployMutation.mutateAsync({ - selectedWallet: targetAuth.wallet, - ...extensionData + const result = deployMutation.mutateAsync({ + subscriptionParams: { + selectedWallet: targetAuth.wallet, + ...extensionData + } }); + + return !!result; }; const { diff --git a/packages/uikit/src/components/desktop/pro/ProRemoveExtensionNotification.tsx b/packages/uikit/src/components/desktop/pro/ProRemoveExtensionNotification.tsx index 9bbb2121a..15777763e 100644 --- a/packages/uikit/src/components/desktop/pro/ProRemoveExtensionNotification.tsx +++ b/packages/uikit/src/components/desktop/pro/ProRemoveExtensionNotification.tsx @@ -118,13 +118,16 @@ const ProRemoveExtensionNotificationContent: FC< }); }, [selectedWallet]); - const removeMutate = async () => - removeMutation.mutateAsync({ + const removeMutate = async () => { + const boc = await removeMutation.mutateAsync({ selectedWallet, extensionContract, destroyValue }); + return !!boc; + }; + const deployReserve = useMemo( () => new AssetAmount({ diff --git a/packages/uikit/src/components/legacy-plugins/CancelLegacySubscriptionNotification.tsx b/packages/uikit/src/components/legacy-plugins/CancelLegacySubscriptionNotification.tsx new file mode 100644 index 000000000..62828ba79 --- /dev/null +++ b/packages/uikit/src/components/legacy-plugins/CancelLegacySubscriptionNotification.tsx @@ -0,0 +1,91 @@ +import { FC, useCallback, useEffect } from 'react'; +import { Notification } from '../Notification'; +import { ConfirmView, ConfirmViewHeading, ConfirmViewHeadingSlot } from '../transfer/ConfirmView'; +import { + useCancelSubscription, + useEstimateRemoveExtension +} from '../../hooks/blockchain/subscription'; +import { useActiveWallet } from '../../state/wallet'; +import { isStandardTonWallet, WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; +import { toNano } from '@ton/core'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; +import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { useAppSdk } from '../../hooks/appSdk'; +import { useTranslation } from '../../hooks/translation'; + +export const CancelLegacySubscriptionNotification: FC<{ + onClose: () => void; + pluginAddress: string | undefined; +}> = ({ onClose, pluginAddress }) => { + return ( + + {() => + !!pluginAddress && ( + + ) + } + + ); +}; + +const destroyValue = toNano(0.05).toString(); +const assetAmount = new AssetAmount({ weiAmount: destroyValue, asset: TON_ASSET }); + +const CancelLegacySubscription: FC<{ pluginAddress: string; onClose: () => void }> = ({ + pluginAddress, + onClose +}) => { + const wallet = useActiveWallet(); + const estimation = useEstimateRemoveExtension(); + const unsubscribeMutation = useCancelSubscription(); + const sdk = useAppSdk(); + const { t } = useTranslation(); + + const unsubscribeCallback = useCallback(() => { + if (!isStandardTonWallet(wallet) || wallet.version !== WalletVersion.V4R2) { + throw new Error('Unexpected wallet is used to unsubscribe from legacy subscription'); + } + + return unsubscribeMutation + .mutateAsync({ + selectedWallet: wallet, + extensionContract: pluginAddress, + destroyValue + }) + .then(succeed => { + if (succeed) { + sdk.topMessage(t('unsubscribe_legacy_plugin_success_toast')); + } + return !!succeed; + }); + }, [wallet, pluginAddress, t]); + + useEffect(() => { + if (!isStandardTonWallet(wallet) || wallet.version !== WalletVersion.V4R2) { + console.error('Unexpected wallet is used to unsubscribe from legacy subscription'); + return onClose(); + } + estimation.mutate({ + selectedWallet: wallet, + extensionContract: pluginAddress, + destroyValue + }); + }, []); + + return ( + + + + + + ); +}; diff --git a/packages/uikit/src/components/legacy-plugins/DesktopCancelLegacySubscriptionBanner.tsx b/packages/uikit/src/components/legacy-plugins/DesktopCancelLegacySubscriptionBanner.tsx new file mode 100644 index 000000000..c8f561575 --- /dev/null +++ b/packages/uikit/src/components/legacy-plugins/DesktopCancelLegacySubscriptionBanner.tsx @@ -0,0 +1,143 @@ +import { useWalletLegacyPlugins } from '../../state/plugins'; +import styled from 'styled-components'; +import { Body2, Label2 } from '../Text'; +import { useTranslation } from '../../hooks/translation'; +import { Button } from '../fields/Button'; +import React, { FC, Suspense, useState } from 'react'; +import { CancelLegacySubscriptionNotification } from './CancelLegacySubscriptionNotification'; +import { useOpenSupport } from '../../state/pro'; + +const Banner = styled.div` + position: absolute; + bottom: 16px; + left: 16px; + right: 16px; + + background: ${p => p.theme.backgroundContent}; + border-radius: ${p => p.theme.cornerSmall}; + padding: 0 16px; + display: flex; + align-items: center; + + > svg { + flex-shrink: 0; + } +`; + +const ColumnText = styled.div` + padding: 14px 16px; + display: flex; + flex-direction: column; + + ${Body2} { + color: ${p => p.theme.textSecondary}; + } +`; + +const WarnIcon = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const ButtonsBlock = styled.div` + display: flex; + gap: 8px; + margin-left: auto; +`; + +export const DesktopCancelLegacySubscriptionBanner: FC<{ className?: string }> = ({ + className +}) => { + const { data: legacyPlugins } = useWalletLegacyPlugins(); + const openSupport = useOpenSupport(); + const { t } = useTranslation(); + const [pluginToUnsubscribe, setPluginToUnsubscribe] = useState(); + + if (!legacyPlugins?.length) { + return null; + } + + return ( + <> + + + + + {t('unsubscribe_legacy_plugin_banner_title', { + count: legacyPlugins.length + })} + + + {' '} + {t( + legacyPlugins.length === 1 + ? 'unsubscribe_legacy_plugin_banner_subtitle' + : 'unsubscribe_legacy_plugin_many_banner_subtitle' + )} + + + + + + + + + setPluginToUnsubscribe(undefined)} + /> + + + ); +}; diff --git a/packages/uikit/src/components/legacy-plugins/MobileCancelLegacySubscriptionBanner.tsx b/packages/uikit/src/components/legacy-plugins/MobileCancelLegacySubscriptionBanner.tsx new file mode 100644 index 000000000..74336fd96 --- /dev/null +++ b/packages/uikit/src/components/legacy-plugins/MobileCancelLegacySubscriptionBanner.tsx @@ -0,0 +1,75 @@ +import { useWalletLegacyPlugins } from '../../state/plugins'; +import styled from 'styled-components'; +import { Body2, Label1 } from '../Text'; +import { useTranslation } from '../../hooks/translation'; +import { ButtonFlat } from '../fields/Button'; +import React, { FC, PropsWithChildren, Suspense, useState } from 'react'; +import { CancelLegacySubscriptionNotification } from './CancelLegacySubscriptionNotification'; + +const Banner = styled.div` + width: 100%; + box-sizing: border-box; + margin: 8px 0; + + background: ${p => p.theme.accentOrange}; + border-radius: ${p => p.theme.cornerSmall}; + padding: 12px 16px; + display: flex; + flex-direction: column; + align-items: flex-start; + + color: ${p => p.theme.constantBlack}; +`; + +const ColumnText = styled.div` + display: flex; + flex-direction: column; + margin-bottom: 4px; +`; + +const ButtonFlatStyled = styled(ButtonFlat)` + color: ${p => p.theme.constantBlack}; +`; + +export const MobileCancelLegacySubscriptionBanner: FC< + PropsWithChildren<{ className?: string }> +> = ({ className, children }) => { + const { data: legacyPlugins } = useWalletLegacyPlugins(); + const { t } = useTranslation(); + const [pluginToUnsubscribe, setPluginToUnsubscribe] = useState(); + + if (!legacyPlugins?.length) { + return null; + } + + return ( + <> + {children} + + + + {t('unsubscribe_legacy_plugin_banner_title', { + count: legacyPlugins.length + })} + + + {t( + legacyPlugins.length === 1 + ? 'unsubscribe_legacy_plugin_banner_subtitle' + : 'unsubscribe_legacy_plugin_many_banner_subtitle' + )} + + + setPluginToUnsubscribe(legacyPlugins![0].address)}> + {t('disable')} + + + + setPluginToUnsubscribe(undefined)} + /> + + + ); +}; diff --git a/packages/uikit/src/components/pro/ProActiveWallet.tsx b/packages/uikit/src/components/pro/ProActiveWallet.tsx index 497bb7871..573ad5a5c 100644 --- a/packages/uikit/src/components/pro/ProActiveWallet.tsx +++ b/packages/uikit/src/components/pro/ProActiveWallet.tsx @@ -14,6 +14,7 @@ import { useAtomValue } from '../../libs/useAtom'; interface IProps { title?: ReactNode; belowCaption?: ReactNode; + rawAddress?: string; isCurrentSubscription?: ReactNode; onDisconnect?: () => Promise; isLoading: boolean; @@ -24,6 +25,7 @@ export const ProActiveWallet: FC = props => { const { onDisconnect, isLoading, + rawAddress, title, belowCaption, isCurrentSubscription, @@ -34,6 +36,10 @@ export const ProActiveWallet: FC = props => { const targetAuth = useAtomValue(subscriptionFormTempAuth$); const { account, wallet } = useControllableAccountAndWalletByWalletId( (() => { + if (rawAddress) { + return rawAddress; + } + const currentAuth = subscription?.auth; if (targetAuth && !isCurrentSubscription) { diff --git a/packages/uikit/src/components/transfer/ConfirmView.tsx b/packages/uikit/src/components/transfer/ConfirmView.tsx index b449ff3ed..9cb8a5c3f 100644 --- a/packages/uikit/src/components/transfer/ConfirmView.tsx +++ b/packages/uikit/src/components/transfer/ConfirmView.tsx @@ -267,10 +267,9 @@ export const ConfirmViewHeadingSlot: FC {children}; -export const ConfirmViewHeading: FC> = ({ - className, - title -}) => { +export const ConfirmViewHeading: FC< + PropsWithChildren<{ className?: string; title?: string; caption?: string }> +> = ({ className, title, caption }) => { const { t } = useTranslation(); const { recipient, assetAmount } = useConfirmViewContext(); const image = useAssetImage(assetAmount.asset); @@ -302,7 +301,7 @@ export const ConfirmViewHeading: FC{t('approval_unverified_token')} )} - {t('confirm_sending_title')} + {caption ?? t('confirm_sending_title')} {title} ); diff --git a/packages/uikit/src/hooks/blockchain/subscription/useCancelSubscription.ts b/packages/uikit/src/hooks/blockchain/subscription/useCancelSubscription.ts index 23a24938b..614c302f2 100644 --- a/packages/uikit/src/hooks/blockchain/subscription/useCancelSubscription.ts +++ b/packages/uikit/src/hooks/blockchain/subscription/useCancelSubscription.ts @@ -15,7 +15,7 @@ export const useCancelSubscription = () => { const client = useQueryClient(); const accountsWallets = useProCompatibleAccountsWallets(backwardCompatibilityFilter); - return useMutation(async subscriptionParams => { + return useMutation(async subscriptionParams => { if (!subscriptionParams) throw new Error('No params'); const { selectedWallet, extensionContract, destroyValue } = subscriptionParams; @@ -40,10 +40,10 @@ export const useCancelSubscription = () => { const outgoingMsg = encoder.encodeDestructAction(extensionAddress, BigInt(destroyValue)); - await sender.send(outgoingMsg); + const boc = await sender.send(outgoingMsg); await client.invalidateQueries([QueryKey.pro]); - return true; + return boc.toBoc().toString('base64'); }); }; diff --git a/packages/uikit/src/hooks/blockchain/subscription/useCreateSubscription.ts b/packages/uikit/src/hooks/blockchain/subscription/useCreateSubscription.ts index b590e1587..8f755b791 100644 --- a/packages/uikit/src/hooks/blockchain/subscription/useCreateSubscription.ts +++ b/packages/uikit/src/hooks/blockchain/subscription/useCreateSubscription.ts @@ -10,74 +10,87 @@ import { } from '@tonkeeper/core/dist/service/ton-blockchain/encoder/subscription-encoder'; import { backwardCompatibilityFilter } from '@tonkeeper/core/dist/service/proService'; import { WalletMessageSender } from '@tonkeeper/core/dist/service/ton-blockchain/sender'; -import { useTranslation } from '../../translation'; import { QueryKey } from '../../../libs/queryKey'; import { MetaEncryptionSerializedMap } from '@tonkeeper/core/dist/entries/wallet'; import { AppKey } from '@tonkeeper/core/dist/Keys'; import { metaEncryptionMapSerializer } from '@tonkeeper/core/dist/utils/metadata'; +import { ICreateSubscriptionV2Response } from '@tonkeeper/core/dist/service/tonConnect/connectService'; + +interface ICreateSubscriptionProps { + subscriptionParams: SubscriptionEncodingParams; + options?: { + disableAddressCheck?: boolean; + }; +} export const useCreateSubscription = () => { const sdk = useAppSdk(); const api = useActiveApi(); - const { t } = useTranslation(); const client = useQueryClient(); const accountsWallets = useProCompatibleAccountsWallets(backwardCompatibilityFilter); - return useMutation(async subscriptionParams => { - if (!subscriptionParams) throw new Error('No params'); + return useMutation( + async ({ subscriptionParams, options }) => { + if (!subscriptionParams) throw new Error('No params'); - const { admin, subscription_id, contract, selectedWallet } = subscriptionParams; + const { disableAddressCheck } = options ?? {}; - const accountWallet = accountsWallets.find( - accWallet => accWallet.wallet.id === selectedWallet.id - ); - const account = accountWallet?.account; - const accountId = account?.id; + const { admin, selectedWallet, contract, subscription_id } = subscriptionParams; - if (!accountId) throw new Error('Account id is required!'); + const accountWallet = accountsWallets.find( + accWallet => accWallet.wallet.id === selectedWallet.id + ); + const account = accountWallet?.account; + const accountId = account?.id; - let metaEncryptionMap = await sdk.storage - .get(AppKey.META_ENCRYPTION_MAP) - .then(metaEncryptionMapSerializer); + if (!accountId) throw new Error('Account id is required!'); - const shouldCreateMetaKeys = !metaEncryptionMap?.[selectedWallet.rawAddress]; + let metaEncryptionMap = await sdk.storage + .get(AppKey.META_ENCRYPTION_MAP) + .then(metaEncryptionMapSerializer); - const signer = await getSigner(sdk, accountId, { - walletId: selectedWallet.id, - shouldCreateMetaKeys - }).catch(() => null); + const shouldCreateMetaKeys = !metaEncryptionMap?.[selectedWallet.rawAddress]; - if (!signer || signer.type !== 'cell') throw new Error('Signer is incorrect!'); + const signer = await getSigner(sdk, accountId, { + walletId: selectedWallet.id, + shouldCreateMetaKeys + }).catch(() => null); - if (shouldCreateMetaKeys) { - metaEncryptionMap = await sdk.storage - .get(AppKey.META_ENCRYPTION_MAP) - .then(metaEncryptionMapSerializer); - } + if (!signer || signer.type !== 'cell') throw new Error('Signer is incorrect!'); - const sender = new WalletMessageSender(api, selectedWallet, signer); - const encoder = new SubscriptionEncoder(selectedWallet); + if (shouldCreateMetaKeys) { + metaEncryptionMap = await sdk.storage + .get(AppKey.META_ENCRYPTION_MAP) + .then(metaEncryptionMapSerializer); + } - const beneficiary = Address.parse(admin); - const subscriptionId = subscription_id; + const sender = new WalletMessageSender(api, selectedWallet, signer); + const encoder = new SubscriptionEncoder(selectedWallet); - const extensionAddress = encoder.getExtensionAddress({ - beneficiary, - subscriptionId - }); + const beneficiary = Address.parse(admin); + const subscriptionId = subscription_id; - const outgoingMsg = await encoder.encodeCreateSubscriptionV2( - prepareSubscriptionParamsForEncoder(subscriptionParams, metaEncryptionMap) - ); + const extensionAddress = encoder.getExtensionAddress({ + beneficiary, + subscriptionId + }); - if (!extensionAddress.equals(Address.parse(contract))) { - throw new Error('Contract extension addresses do not match!'); - } + const outgoingMsg = await encoder.encodeCreateSubscriptionV2( + prepareSubscriptionParamsForEncoder(subscriptionParams, metaEncryptionMap) + ); - await sender.send(outgoingMsg); + if (!disableAddressCheck && !extensionAddress.equals(Address.parse(contract))) { + throw new Error('Contract extension addresses do not match!'); + } - await client.invalidateQueries([QueryKey.metaEncryptionData]); + const boc = await sender.send(outgoingMsg); - return true; - }); + await client.invalidateQueries([QueryKey.metaEncryptionData]); + + return { + boc: boc.toBoc().toString('base64'), + extensionAddress: extensionAddress.toString() + }; + } + ); }; diff --git a/packages/uikit/src/libs/queryKey.ts b/packages/uikit/src/libs/queryKey.ts index 9a9ea61ff..d5b273d1c 100644 --- a/packages/uikit/src/libs/queryKey.ts +++ b/packages/uikit/src/libs/queryKey.ts @@ -89,7 +89,9 @@ export enum QueryKey { appCountryInfo = 'appCountryInfo', trc20TrxDefaultFee = 'trc20TrxDefaultFee', - trc20FreeTransfersConfig = 'trc20FreeTransfersConfig' + trc20FreeTransfersConfig = 'trc20FreeTransfersConfig', + + legacyPlugins = 'legacyPlugins' } export enum JettonKey { diff --git a/packages/uikit/src/mobile-pro-pages/MobileProHomePage.tsx b/packages/uikit/src/mobile-pro-pages/MobileProHomePage.tsx index 8b75721a6..d46961a66 100644 --- a/packages/uikit/src/mobile-pro-pages/MobileProHomePage.tsx +++ b/packages/uikit/src/mobile-pro-pages/MobileProHomePage.tsx @@ -30,6 +30,7 @@ import { DesktopViewHeader, DesktopViewPageLayout } from '../components/desktop/ import { TwoFARecoveryStartedBanner } from '../components/settings/two-fa/TwoFARecoveryStartedBanner'; import { IfFeatureEnabled } from '../components/shared/IfFeatureEnabled'; import { FLAGGED_FEATURE } from '../state/tonendpoint'; +import { MobileCancelLegacySubscriptionBanner } from '../components/legacy-plugins/MobileCancelLegacySubscriptionBanner'; const MobileProHomeActionsStyled = styled(MobileProHomeActions)` margin: 0 8px 16px; @@ -83,6 +84,11 @@ const TwoFARecoveryStartedBannerStyled = styled(TwoFARecoveryStartedBanner)` margin: 0 8px -8px; `; +const MobileCancelLegacySubscriptionBannerStyled = styled(MobileCancelLegacySubscriptionBanner)` + margin: 0 8px; + width: unset; +`; + const BannerPadding = styled.div` height: 16px; `; @@ -104,6 +110,9 @@ export const MobileProHomePage = () => { + + + diff --git a/packages/uikit/src/pages/home/Home.tsx b/packages/uikit/src/pages/home/Home.tsx index 5efd32775..d7d75828d 100644 --- a/packages/uikit/src/pages/home/Home.tsx +++ b/packages/uikit/src/pages/home/Home.tsx @@ -12,6 +12,7 @@ import { usePreFetchRates } from '../../state/rates'; import { useWalletFilteredNftList } from '../../state/nft'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { FLAGGED_FEATURE, useIsFeatureEnabled } from '../../state/tonendpoint'; +import { MobileCancelLegacySubscriptionBanner } from '../../components/legacy-plugins/MobileCancelLegacySubscriptionBanner'; const HomeAssets: FC<{ assets: AssetAmount[]; @@ -41,6 +42,7 @@ const Home = () => { return ( <> + diff --git a/packages/uikit/src/state/plugins.ts b/packages/uikit/src/state/plugins.ts new file mode 100644 index 000000000..6d450b7e5 --- /dev/null +++ b/packages/uikit/src/state/plugins.ts @@ -0,0 +1,28 @@ +import { useQuery } from '@tanstack/react-query'; +import { QueryKey } from '../libs/queryKey'; +import { useActiveAccount, useActiveApi } from './wallet'; +import { WalletApi } from '@tonkeeper/core/dist/tonApiV2'; + +export const useWalletLegacyPlugins = () => { + const api = useActiveApi(); + const activeAccount = useActiveAccount(); + const wallet = activeAccount.activeTonWallet; + const isSuitableAccount = activeAccount.type === 'mnemonic' || activeAccount.type === 'mam'; + + return useQuery( + [QueryKey.legacyPlugins, isSuitableAccount, wallet.rawAddress, api], + async () => { + if (!isSuitableAccount) { + return []; + } + + const data = await new WalletApi(api.tonApiV2).getWalletInfo({ + accountId: wallet.rawAddress + }); + return data.plugins.filter(plugin => plugin.type === 'subscription_v1'); + }, + { + refetchInterval: data => (!!data?.length ? 30_000 : 0) + } + ); +}; diff --git a/packages/uikit/src/state/pro.ts b/packages/uikit/src/state/pro.ts index a76fbc83e..8d4bebd1d 100644 --- a/packages/uikit/src/state/pro.ts +++ b/packages/uikit/src/state/pro.ts @@ -49,6 +49,7 @@ import { TwoFAEncoder } from '@tonkeeper/core/dist/service/ton-blockchain/encode import { useProAuthNotification } from '../components/modals/ProAuthNotificationControlled'; import { useProPurchaseNotification } from '../components/modals/ProPurchaseNotificationControlled'; import { getWalletBalance } from '@tonkeeper/core/dist/service/ton-blockchain/utils'; +import { useCallback } from 'react'; export const useTrialAvailability = () => { const sdk = useAppSdk(); @@ -99,6 +100,15 @@ export const useSupport = () => { ); }; +export const useOpenSupport = () => { + const sdk = useAppSdk(); + const { data: support } = useSupport(); + return useCallback( + () => sdk.openPage(support.url, { forceExternalBrowser: true }), + [support.url] + ); +}; + export const useProBackupState = () => { const sdk = useAppSdk(); return useQuery(