From ec5430b5c5da3cb7ee51b8115ef932ff625be6ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 23:02:24 +0000 Subject: [PATCH 1/3] v5.48.0 --- extension/package.json | 2 +- extension/public/static/manifest/v3.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/package.json b/extension/package.json index af9e975b96..f2176ef83d 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "extension", - "version": "5.47.0", + "version": "5.48.0", "license": "Apache-2.0", "prettier": "../.prettierrc.yaml", "scripts": { diff --git a/extension/public/static/manifest/v3.json b/extension/public/static/manifest/v3.json index 3f17e4fd7c..c2790f8268 100644 --- a/extension/public/static/manifest/v3.json +++ b/extension/public/static/manifest/v3.json @@ -1,7 +1,7 @@ { "name": "Freighter", - "version": "5.47.0", - "version_name": "5.47.0", + "version": "5.48.0", + "version_name": "5.48.0", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", "browser_specific_settings": { "gecko": { From 008b106841b7e94fa12ba363750ad4d49cf7bcac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Marcos=20Goulart?= <3228151+CassioMG@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:38:18 -0700 Subject: [PATCH 2/3] fix(icons): show USDT0's icon, for new and existing users (v5.48.0) (#2998) Backport of #2994 onto the v5.48.0 release branch. Cherry-picked from bbcd41de; the four touched files are byte-identical to master. Co-authored-by: Claude Fable 5 --- @shared/api/helpers/getIconFromTokenList.ts | 10 ++-- .../__tests__/getCachedAssetIconList.test.ts | 45 +++++++++++++++++ .../handlers/getCachedAssetIconList.ts | 48 ++++++++++++++++++- .../__tests__/getIconFromTokenLists.test.js | 41 +++++++++++++++- 4 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 extension/src/background/messageListener/handlers/__tests__/getCachedAssetIconList.test.ts diff --git a/@shared/api/helpers/getIconFromTokenList.ts b/@shared/api/helpers/getIconFromTokenList.ts index ae97610db8..aa42c39169 100644 --- a/@shared/api/helpers/getIconFromTokenList.ts +++ b/@shared/api/helpers/getIconFromTokenList.ts @@ -20,7 +20,11 @@ export const getIconFromTokenLists = async ({ }) => { let verifiedToken = {} as AssetListReponseItem; let canonicalAsset = undefined as string | undefined; - for (const data of assetsListsData) { + // The lists arrive in the user's configured order, which is a priority order: + // the first list carrying the asset wins. Without the labeled break below the + // outer loop runs to completion and the LAST matching list silently overwrites + // earlier, higher-priority entries. + listLoop: for (const data of assetsListsData) { const list = data.assets; if (list) { for (const record of list) { @@ -29,7 +33,7 @@ export const getIconFromTokenLists = async ({ if (record.contract && record.contract.match(regex) && record.icon) { verifiedToken = record; canonicalAsset = getCanonicalFromAsset(code, contractId); - break; + break listLoop; } } @@ -42,7 +46,7 @@ export const getIconFromTokenLists = async ({ ) { verifiedToken = record; canonicalAsset = getCanonicalFromAsset(code, issuerId); - break; + break listLoop; } } } diff --git a/extension/src/background/messageListener/handlers/__tests__/getCachedAssetIconList.test.ts b/extension/src/background/messageListener/handlers/__tests__/getCachedAssetIconList.test.ts new file mode 100644 index 0000000000..3dbbf4b402 --- /dev/null +++ b/extension/src/background/messageListener/handlers/__tests__/getCachedAssetIconList.test.ts @@ -0,0 +1,45 @@ +import { getCachedAssetIconList } from "../getCachedAssetIconList"; + +const USDT0 = "USDT0:GATISXX6BZ6NC7IKQBY37CJD4SOZL3CYZJWXEDG6JVIY4WBS6KXJHN6Q"; +const USDC = "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"; +const USDC_ICON = "https://centre.io/usdc.png"; + +const makeLocalStore = (assetIconCache: unknown) => + ({ + getItem: jest.fn(async () => assetIconCache), + setItem: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), + }) as any; + +describe("getCachedAssetIconList", () => { + it("returns the icons it has", async () => { + const result = await getCachedAssetIconList({ + localStore: makeLocalStore({ [USDC]: USDC_ICON }), + }); + + expect(result.icons).toEqual({ [USDC]: USDC_ICON }); + }); + + it("leaves out assets recorded as having no icon", async () => { + // A null here means an earlier lookup came up empty, and getAssetIcons + // reads it as "already tried, don't look again". Because this cache is on + // disk that verdict outlived the session, so an asset whose icon failed + // once — USDT0, whose LOBSTR url 403s browsers — stayed iconless forever. + // Dropping nulls turns it back into an ordinary cache miss, and the fresh + // lookup overwrites the stale entry. + const result = await getCachedAssetIconList({ + localStore: makeLocalStore({ [USDC]: USDC_ICON, [USDT0]: null }), + }); + + expect(result.icons).toEqual({ [USDC]: USDC_ICON }); + }); + + it("returns an empty map when nothing is cached", async () => { + const result = await getCachedAssetIconList({ + localStore: makeLocalStore(undefined), + }); + + expect(result.icons).toEqual({}); + }); +}); diff --git a/extension/src/background/messageListener/handlers/getCachedAssetIconList.ts b/extension/src/background/messageListener/handlers/getCachedAssetIconList.ts index e27161ffb5..000c55f196 100644 --- a/extension/src/background/messageListener/handlers/getCachedAssetIconList.ts +++ b/extension/src/background/messageListener/handlers/getCachedAssetIconList.ts @@ -10,6 +10,52 @@ export const getCachedAssetIconList = async ({ (await localStore.getItem(CACHED_ASSET_ICONS_ID)) || {}; return { - icons: assetIconCache, + // A null entry records that a lookup came up empty, and getAssetIcons reads + // one as "already tried, don't look again". That was only ever meant to last + // a session, but this cache is on disk, so the verdict outlived it: an asset + // whose icon failed once stayed iconless for good, with no way back — no + // icon means no , so nothing fires the error handler that would have + // retried. Dropping nulls here turns them back into ordinary cache misses, + // and the fresh lookup overwrites the stale entry. + // + // TODO: this is a read-side workaround, not the real fix. It hides bad data + // rather than stopping it being written, and it rules out ever storing a + // meaningful null here. + // + // Retrying is the point: it is how an asset that got stuck gets its icon + // back. But the retry is not free, and for an asset with no icon anywhere + // it repeats forever without ever succeeding. Each attempt costs a + // token-list scan and, when that finds nothing, one batched Horizon call + // covering every such issuer plus a stellar.toml fetch for each issuer that + // publishes a home domain. (USDT0 stops at the Horizon call: its issuer + // publishes no home domain and, with its master key at weight 0, never + // can.) + // + // That cost is newly paid by the flows that load balances with icons — + // swap, send, manage assets, history — which previously skipped a + // null-marked asset outright. The Account view's own icon hook already + // retried regardless, since it passes an empty cache to its lookup pass, so + // nothing changes there. And nothing suppresses the retry from one popup to + // the next: closing the popup tears it down, taking Redux — the only record + // of what this session already resolved — with it, so every open starts + // from nothing. + // + // A negative cache that survives the popup, with a TTL so it expires rather + // than latching, is the sane way to stop the endless retry — and this + // filter would silently swallow one. + // + // To fix properly, in order: + // 1. Stop persisting nulls: retryAssetIcon sends `iconUrl: null` meaning + // "clear this", so cacheAssetIcon should delete the entry rather than + // store the null. + // 2. Add a migration to clear the nulls already on disk. It cannot be + // done by (1) alone, since nothing rewrites an entry the lookup skips. + // 3. Drop this filter, so the read path goes back to being a plain + // accessor and null is free to mean something again. + // Doing (2) without (1) is not enough on its own: every later icon failure + // writes a fresh null and puts that user straight back into the bug. + icons: Object.fromEntries( + Object.entries(assetIconCache).filter(([, iconUrl]) => iconUrl), + ), }; }; diff --git a/extension/src/popup/helpers/__tests__/getIconFromTokenLists.test.js b/extension/src/popup/helpers/__tests__/getIconFromTokenLists.test.js index 49ffe0d371..5974ffb2cb 100644 --- a/extension/src/popup/helpers/__tests__/getIconFromTokenLists.test.js +++ b/extension/src/popup/helpers/__tests__/getIconFromTokenLists.test.js @@ -11,7 +11,7 @@ const VERIFIED_TOKEN_ISSUER = validAssetList.assets[0].issuer; const VERIFIED_TOKEN_CODE = validAssetList.assets[0].code; const EXPECTED_ICON_URL = validAssetList.assets[0].icon; -jest +(jest .spyOn(ExtensionMessaging, "sendMessageToBackground") .mockImplementation(() => Promise.resolve({ @@ -20,7 +20,7 @@ jest ), jest .spyOn(TokenListHelpers, "getCombinedAssetListData") - .mockImplementation(() => Promise.resolve([validAssetList])); + .mockImplementation(() => Promise.resolve([validAssetList]))); describe("getIconFromTokenLists", () => { it("should return an icon if an asset is in a token list by contract ID", async () => { @@ -55,3 +55,40 @@ describe("getIconFromTokenLists", () => { expect(canonicalAsset).toBeUndefined(); }); }); + +describe("getIconFromTokenLists list priority", () => { + // USDT0 is on the Soroswap list with a direct https icon and on the LOBSTR + // list with an ipfs.io gateway url. That gateway answers curl but returns 403 + // to a browser User-Agent, so an can never load it. Keeping whichever + // list was read last handed the browser the one url it cannot fetch. + const laterList = { + ...validAssetList, + provider: "Later Provider", + assets: [ + { + ...validAssetList.assets[0], + icon: "https://later-list.example/icon.png", + }, + ], + }; + + it("uses the icon from the first matching list, matched by issuer", async () => { + const { icon } = await getIconFromTokenLists({ + issuerId: VERIFIED_TOKEN_ISSUER, + code: VERIFIED_TOKEN_CODE, + assetsListsData: [validAssetList, laterList], + }); + + expect(icon).toEqual(EXPECTED_ICON_URL); + }); + + it("uses the icon from the first matching list, matched by contract ID", async () => { + const { icon } = await getIconFromTokenLists({ + contractId: VERIFIED_TOKEN_CONTRACT, + code: VERIFIED_TOKEN_CODE, + assetsListsData: [validAssetList, laterList], + }); + + expect(icon).toEqual(EXPECTED_ICON_URL); + }); +}); From c1d56f7eae8cb993fe326bb25ae05b52a15f61dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1ssio=20Marcos=20Goulart?= <3228151+CassioMG@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:01:50 -0700 Subject: [PATCH 3/3] feat: add USDT0 launch banner and promo sheet to Home (v5.48.0 backport) (#2997) * feat: add USDT0 launch banner and promo sheet to Home (v5.48.0) Backport of #2990 onto the v5.48.0 release branch so the promo can ship with that release. Applied as a single squashed commit; the tree for the 17 touched files is identical to the master-based branch. Co-Authored-By: Claude Fable 5 * fix: fail closed on USDT0 banner dismissal lookup, announce the dialog Backport of #2999 onto the v5.48.0 release branch, addressing the review comments left on #2990 after it merged. The three touched files match the master-based fix branch. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- @shared/api/internal.ts | 28 +++ @shared/api/types/message-request.ts | 10 + @shared/constants/services.ts | 2 + .../handlers/dismissUsdt0LaunchBanner.ts | 11 + .../handlers/getUsdt0LaunchBannerDismissed.ts | 11 + .../messageListener/popupMessageListener.ts | 12 + extension/src/constants/localStorageTypes.ts | 1 + extension/src/popup/assets/logo-usdt0.png | Bin 0 -> 15645 bytes extension/src/popup/assets/usdt0-arcs.svg | 20 ++ extension/src/popup/assets/usdt0-lockup.svg | 10 + .../account/AccountHeader/index.tsx | 2 + .../Usdt0LaunchSheet/index.tsx | 175 +++++++++++++ .../Usdt0LaunchSheet/styles.scss | 236 ++++++++++++++++++ .../account/Usdt0LaunchBanner/index.tsx | 133 ++++++++++ .../account/Usdt0LaunchBanner/styles.scss | 107 ++++++++ .../src/popup/locales/en/translation.json | 13 + .../src/popup/locales/pt/translation.json | 13 + 17 files changed, 784 insertions(+) create mode 100644 extension/src/background/messageListener/handlers/dismissUsdt0LaunchBanner.ts create mode 100644 extension/src/background/messageListener/handlers/getUsdt0LaunchBannerDismissed.ts create mode 100644 extension/src/popup/assets/logo-usdt0.png create mode 100644 extension/src/popup/assets/usdt0-arcs.svg create mode 100644 extension/src/popup/assets/usdt0-lockup.svg create mode 100644 extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/index.tsx create mode 100644 extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/styles.scss create mode 100644 extension/src/popup/components/account/Usdt0LaunchBanner/index.tsx create mode 100644 extension/src/popup/components/account/Usdt0LaunchBanner/styles.scss 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 0000000000000000000000000000000000000000..b193faf698151bd4a09b9dad221021db00bb4d4c GIT binary patch literal 15645 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 + + + + + + + + + + + + + + + + + + + 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