diff --git a/@shared/api/internal.ts b/@shared/api/internal.ts index 17ebd8546b..e67811cadc 100644 --- a/@shared/api/internal.ts +++ b/@shared/api/internal.ts @@ -2081,6 +2081,34 @@ export const getTokenIds = async ({ return tokenIdList; }; +export const getUsdt0LaunchBannerDismissed = async (): Promise => { + const { isDismissed, error } = await sendMessageToBackground({ + activePublicKey: null, + type: SERVICE_TYPES.GET_USDT0_LAUNCH_BANNER_DISMISSED, + }); + + if (error) { + throw new Error(error); + } + + return !!isDismissed; +}; + +export const dismissUsdt0LaunchBanner = async (): Promise<{ + isDismissed: boolean; +}> => { + const { isDismissed, error } = await sendMessageToBackground({ + activePublicKey: null, + type: SERVICE_TYPES.DISMISS_USDT0_LAUNCH_BANNER, + }); + + if (error) { + throw new Error(error); + } + + return { isDismissed: !!isDismissed }; +}; + export const removeTokenId = async ({ activePublicKey, contractId, diff --git a/@shared/api/types/message-request.ts b/@shared/api/types/message-request.ts index 5dc83dd06a..53db55f049 100644 --- a/@shared/api/types/message-request.ts +++ b/@shared/api/types/message-request.ts @@ -428,6 +428,14 @@ export interface GetHiddenAssetsMessage extends BaseMessage { type: SERVICE_TYPES.GET_HIDDEN_ASSETS; } +export interface GetUsdt0LaunchBannerDismissedMessage extends BaseMessage { + type: SERVICE_TYPES.GET_USDT0_LAUNCH_BANNER_DISMISSED; +} + +export interface DismissUsdt0LaunchBannerMessage extends BaseMessage { + type: SERVICE_TYPES.DISMISS_USDT0_LAUNCH_BANNER; +} + export interface GetRecentProtocolsMessage extends BaseMessage { type: SERVICE_TYPES.GET_RECENT_PROTOCOLS; } @@ -565,6 +573,8 @@ export type ServiceMessageRequest = | GetIsAccountMismatchMessage | ChangeAssetVisibilityMessage | GetHiddenAssetsMessage + | GetUsdt0LaunchBannerDismissedMessage + | DismissUsdt0LaunchBannerMessage | GetRecentProtocolsMessage | AddRecentProtocolMessage | ClearRecentProtocolsMessage diff --git a/@shared/constants/services.ts b/@shared/constants/services.ts index d788684012..9c967543a3 100644 --- a/@shared/constants/services.ts +++ b/@shared/constants/services.ts @@ -54,6 +54,8 @@ export enum SERVICE_TYPES { CHANGE_ASSET_VISIBILITY = "CHANGE_ASSET_VISIBILITY", GET_HIDDEN_ASSETS = "GET_HIDDEN_ASSETS", GET_IS_ACCOUNT_MISMATCH = "GET_IS_ACCOUNT_MISMATCH", + GET_USDT0_LAUNCH_BANNER_DISMISSED = "GET_USDT0_LAUNCH_BANNER_DISMISSED", + DISMISS_USDT0_LAUNCH_BANNER = "DISMISS_USDT0_LAUNCH_BANNER", GET_BLOCKAID_DEBUG_OVERRIDE = "GET_BLOCKAID_DEBUG_OVERRIDE", SAVE_BLOCKAID_DEBUG_OVERRIDE = "SAVE_BLOCKAID_DEBUG_OVERRIDE", ADD_COLLECTIBLE = "ADD_COLLECTIBLE", diff --git a/extension/src/background/messageListener/handlers/dismissUsdt0LaunchBanner.ts b/extension/src/background/messageListener/handlers/dismissUsdt0LaunchBanner.ts new file mode 100644 index 0000000000..fd163574fa --- /dev/null +++ b/extension/src/background/messageListener/handlers/dismissUsdt0LaunchBanner.ts @@ -0,0 +1,11 @@ +import { DataStorageAccess } from "background/helpers/dataStorageAccess"; +import { USDT0_LAUNCH_BANNER_DISMISSED } from "constants/localStorageTypes"; + +export const dismissUsdt0LaunchBanner = async ({ + localStore, +}: { + localStore: DataStorageAccess; +}): Promise<{ isDismissed: boolean }> => { + await localStore.setItem(USDT0_LAUNCH_BANNER_DISMISSED, true); + return { isDismissed: true }; +}; diff --git a/extension/src/background/messageListener/handlers/getUsdt0LaunchBannerDismissed.ts b/extension/src/background/messageListener/handlers/getUsdt0LaunchBannerDismissed.ts new file mode 100644 index 0000000000..d0e9d6f350 --- /dev/null +++ b/extension/src/background/messageListener/handlers/getUsdt0LaunchBannerDismissed.ts @@ -0,0 +1,11 @@ +import { DataStorageAccess } from "background/helpers/dataStorageAccess"; +import { USDT0_LAUNCH_BANNER_DISMISSED } from "constants/localStorageTypes"; + +export const getUsdt0LaunchBannerDismissed = async ({ + localStore, +}: { + localStore: DataStorageAccess; +}): Promise<{ isDismissed: boolean }> => { + const dismissed = await localStore.getItem(USDT0_LAUNCH_BANNER_DISMISSED); + return { isDismissed: !!dismissed }; +}; diff --git a/extension/src/background/messageListener/popupMessageListener.ts b/extension/src/background/messageListener/popupMessageListener.ts index a3b42240e9..68a66236a0 100644 --- a/extension/src/background/messageListener/popupMessageListener.ts +++ b/extension/src/background/messageListener/popupMessageListener.ts @@ -90,6 +90,8 @@ import { modifyAssetsList } from "./handlers/modifyAssetsList"; import { getIsAccountMismatch } from "./handlers/getIsAccountMismatch"; import { changeAssetVisibility } from "./handlers/changeAssetVisibility"; import { getHiddenAssets } from "./handlers/getHiddenAssets"; +import { getUsdt0LaunchBannerDismissed } from "./handlers/getUsdt0LaunchBannerDismissed"; +import { dismissUsdt0LaunchBanner } from "./handlers/dismissUsdt0LaunchBanner"; import { loadBackendSettings } from "./handlers/loadBackendSettings"; import { saveBlockaidOverrideState } from "./handlers/saveDebugOverride"; import { getBlockaidOverrideState } from "./handlers/getDebugOverride"; @@ -579,6 +581,16 @@ export const popupMessageListener = ( localStore, }); } + case SERVICE_TYPES.GET_USDT0_LAUNCH_BANNER_DISMISSED: { + return getUsdt0LaunchBannerDismissed({ + localStore, + }); + } + case SERVICE_TYPES.DISMISS_USDT0_LAUNCH_BANNER: { + return dismissUsdt0LaunchBanner({ + localStore, + }); + } case SERVICE_TYPES.GET_BLOCKAID_DEBUG_OVERRIDE: { return getBlockaidOverrideState({ localStore, diff --git a/extension/src/constants/localStorageTypes.ts b/extension/src/constants/localStorageTypes.ts index d69f3a7627..3bca199400 100644 --- a/extension/src/constants/localStorageTypes.ts +++ b/extension/src/constants/localStorageTypes.ts @@ -28,6 +28,7 @@ export const HIDDEN_ASSETS = "hiddenAssets"; export const HIDDEN_COLLECTIBLES = "hiddenCollectibles"; export const TEMPORARY_STORE_ID = "temporaryStore"; export const TEMPORARY_STORE_EXTRA_ID = "temporaryStoreExtra"; +export const USDT0_LAUNCH_BANNER_DISMISSED = "usdt0LaunchBannerDismissed"; export const OVERRIDDEN_BLOCKAID_RESPONSE_ID = "overriddenBlockaidResponse"; export const COLLECTIBLES_ID = "collectibles"; export const IS_OPEN_SIDEBAR_BY_DEFAULT_ID = "isOpenSidebarByDefault"; diff --git a/extension/src/popup/assets/logo-usdt0.png b/extension/src/popup/assets/logo-usdt0.png new file mode 100644 index 0000000000..b193faf698 Binary files /dev/null and b/extension/src/popup/assets/logo-usdt0.png differ diff --git a/extension/src/popup/assets/usdt0-arcs.svg b/extension/src/popup/assets/usdt0-arcs.svg new file mode 100644 index 0000000000..4ed670073f --- /dev/null +++ b/extension/src/popup/assets/usdt0-arcs.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/extension/src/popup/assets/usdt0-lockup.svg b/extension/src/popup/assets/usdt0-lockup.svg new file mode 100644 index 0000000000..cc9591d197 --- /dev/null +++ b/extension/src/popup/assets/usdt0-lockup.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extension/src/popup/components/account/AccountHeader/index.tsx b/extension/src/popup/components/account/AccountHeader/index.tsx index 66e8c311e4..e6f9a61bd6 100644 --- a/extension/src/popup/components/account/AccountHeader/index.tsx +++ b/extension/src/popup/components/account/AccountHeader/index.tsx @@ -27,6 +27,7 @@ import { signOut } from "popup/ducks/accountServices"; import { AccountHeaderModal } from "popup/components/account/AccountHeaderModal"; import { NetworkIcon } from "popup/components/manageNetwork/NetworkIcon"; import { NetworkDetails } from "@shared/constants/stellar"; +import { Usdt0LaunchBanner } from "popup/components/account/Usdt0LaunchBanner"; import { AccountTabs } from "popup/components/account/AccountTabs"; import { MaintenanceBanner } from "popup/components/MaintenanceBanner"; import { getNetworkDisplayName } from "./getNetworkDisplayName"; @@ -416,6 +417,7 @@ export const AccountHeader = ({ + {isBackgroundActive ? createPortal( void; +} + +export const Usdt0LaunchSheet = ({ onClose }: Usdt0LaunchSheetProps) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const handleSwapClick = () => { + openTab(USDT0_SWAP_URL); + }; + + const handleReceiveClick = () => { + onClose(); + navigateTo(ROUTES.viewPublicKey, navigate); + }; + + const handleTransferClick = () => { + openTab(USDT0_TRANSFER_URL); + }; + + return ( +
+
+
+ +
+
+ {t("USDT0")} +
+
+
+ {/* Same BackButton as the QR code screen's X, supplied as a real + + } + /> +
+
+
+
+ {t("USDT0 is now on Stellar")} +
+ {/* global.scss forces `p` color to inherit (!important), which + would defeat the muted gray — render as div like the rows */} + + {t("Access USDT liquidity on Stellar with USDT0.")} + +
+
+
+
+ +
+
+ + {t("Move across networks")} + + + {t("Transfer USDT0 between Stellar and supported networks.")} + +
+
+
+
+ +
+
+ + {t("1:1 backed, unified liquidity")} + + + {t("USDT0 is backed 1:1 by USDT.")} + +
+
+
+
+
+ + + +
+
+
+ ); +}; diff --git a/extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/styles.scss b/extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/styles.scss new file mode 100644 index 0000000000..c94a0676f1 --- /dev/null +++ b/extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/styles.scss @@ -0,0 +1,236 @@ +@use "../../../../styles/utils.scss" as *; + +.Usdt0LaunchSheet { + position: relative; + height: 100%; + width: 100%; + background: var(--sds-clr-gray-01); + // Clip the oversized hero art horizontally, but keep a vertical scroll + // path so the footer CTAs stay reachable in short viewports (squat + // sidebar windows, high zoom) + overflow-x: hidden; + overflow-y: auto; + + &__background { + position: absolute; + top: 0; + left: 0; + right: 0; + height: pxToRem(429px); + pointer-events: none; + + &__gradient { + position: absolute; + top: 0; + left: 0; + right: 0; + height: pxToRem(377px); + background: linear-gradient(180deg, #002d19 0%, #161616 100%); + } + + &__arcs { + position: absolute; + top: pxToRem(77px); + left: 50%; + transform: translateX(-50%); + width: pxToRem(395px); + height: pxToRem(180px); + max-width: none; + } + + &__fade { + position: absolute; + top: pxToRem(73px); + left: 0; + right: 0; + height: pxToRem(356px); + background: linear-gradient( + 180deg, + rgba(22, 23, 23, 0) 4.7%, + #151616 44.67% + ); + } + + &__overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + height: pxToRem(356px); + background: linear-gradient( + 180deg, + rgba(22, 23, 23, 0) 4.7%, + rgba(21, 22, 22, 0.78) 44.67% + ); + } + + &__lockup { + position: absolute; + top: pxToRem(135px); + left: 50%; + transform: translateX(-50%); + width: pxToRem(165px); + height: auto; + } + } + + &__content { + position: relative; + display: flex; + flex-direction: column; + // Fills the sheet but may grow past it, scrolling the root, when the + // viewport is shorter than the content + min-height: 100%; + padding-bottom: pxToRem(24px); + gap: pxToRem(32px); + } + + // Mirrors the View app header metrics (80px tall, 24px inset) so the X + // sits exactly where the QR code screen's does + &__header { + display: flex; + align-items: center; + min-height: pxToRem(80px); + padding: 0 pxToRem(24px); + } + + // The BackButton class supplies the geometry; a + +
+ !open && setIsSheetOpen(false)} + > + e.preventDefault()} + aria-describedby={undefined} + side="bottom" + className="Usdt0LaunchBanner__sheet" + > + + {t("USDT0 is now on Stellar")} + + setIsSheetOpen(false)} /> + + + + ); +}; diff --git a/extension/src/popup/components/account/Usdt0LaunchBanner/styles.scss b/extension/src/popup/components/account/Usdt0LaunchBanner/styles.scss new file mode 100644 index 0000000000..627d442e0d --- /dev/null +++ b/extension/src/popup/components/account/Usdt0LaunchBanner/styles.scss @@ -0,0 +1,107 @@ +@use "../../../styles/utils.scss" as *; + +.Usdt0LaunchBanner { + position: relative; + background: #002d19; + border-radius: pxToRem(12px); + // No horizontal offsets: this sits in `AccountHeader__account-info__details`, + // which spans the full content column, so the banner stretches to match the + // action tiles and tab strip. + margin-bottom: pxToRem(8px); + // Pulls the banner halfway into the action tiles' 24px bottom padding — + // that padding also sets the tiles -> tabs gap when the banner is + // dismissed, so it can't be reduced globally. + margin-top: pxToRem(-12px); + display: flex; + align-items: center; + justify-content: space-between; + gap: pxToRem(12px); + transition: opacity 0.2s ease; + + &:hover { + opacity: 0.9; + } + + // The launch surface is a real