Skip to content

feat(ethena): Ethena launch promo and Home banner - #1070

Open
CassioMG wants to merge 15 commits into
mainfrom
cg-ethena-launch
Open

CassioMG wants to merge 15 commits into
mainfrom
cg-ethena-launch

Conversation

@CassioMG

@CassioMG CassioMG commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

Adds the Ethena launch: a dismissible "Meet Ethena on Stellar" banner on Home and a full-screen promo for USDe and sUSDe. The promo opens from the banner, and once on its own for existing wallets after they update. From it, Swap opens Sushi's swap page for the token in a new Discover tab, Receive shows the account's QR code, and "Learn more about Ethena" opens Ethena's docs. The one-time showing follows the rebrand announcement's rule, as the extension does in stellar/freighter#3095: fresh installs and new or imported wallets only get the banner, and a launch that shows the rebrand announcement leaves the promo for the next launch.

Follow-ups

  • Sushi is adding token0 and token1 params to its Stellar swap page soon; until that ships, Swap opens on Sushi's default pair with USDT0 as destination.

Video samples (light theme variation shown at the end)

ethena-launch-ios.mov
ethena-launch-android.mov
Implementation details (for agents)

What changed

  • When the promo shows itself (src/helpers/ethenaLaunchPromo.ts):
    • A new storage key holds pending or seen. Each install decides it once at launch, before a wallet can be created: pending when accounts are already stored (an update), seen otherwise (a fresh install). This is the rebrand announcement's rule, and hasExistingAccounts is now exported from helpers/rebrandAnnouncement.
    • A failed read counts as seen, so the promo never shows by mistake.
    • The key survives clearNonSensitiveData, which also runs on sign-up and import, and the wipe path marks it seen next to the rebrand's status.
  • RootNavigator (eligibility, auto-show):
    • It decides eligibility right after the rebrand's. A launch that owes the rebrand announcement doesn't owe the promo, so it stays pending until the next launch.
    • useEthenaLaunchPromoAutoShow opens it once per launch when canShowEthenaLaunchPromo holds: the wallet is unlocked, the lock overlay is down, and the splash has finished (SplashOverlay gains an onFinished prop). None of these may be up: the wallet setup screen, the rebrand announcement, the force-update notice or the maintenance screen.
    • Signing out drops the pending answer, as it does for the rebrand.
  • Route: ETHENA_LAUNCH_SCREEN in the authenticated RootStack, with { source: "launch" | "banner" } and each platform's own fade: ETHENA_LAUNCH_FADE_MS is iOS's default 500ms (passed as animationDuration, so the entrance stays in step) and Android's fixed 150ms. There's no deep link.
  • Promo (src/components/screens/EthenaLaunchScreen/):
    • When it opens, it marks the promo seen and tracks ethena_launch.promo_viewed with its source.
    • A sticky top bar holds the ✕, and the dark HeaderFade shows once the page has scrolled. Android's back button closes it too.
    • The hero's entrance (useEthenaLaunchEntrance) uses the design's motion timings and curves. It's handed to the UI thread as the promo mounts and starts as the fade-in ends, so a busy JS thread (Home loading after an unlock) can't hold it back. Reduce Motion skips it.
    • "Scroll to discover tokens" scrolls until the cue sits 5pt below the bar (scrollCue.ts).
    • Swap adds a Discover tab on the token's Sushi URL, hides the tab overview and pops back to the Discover tab. Both Swap buttons are hidden while discover_enabled is off.
    • Receive pushes the QR screen, on its Receive tab, on top of the promo. Learn more opens https://docs.ethena.fi/ in the in-app browser. A 1-second guard ignores double taps.
    • Its colors are pinned to the dark palette (ethenaLaunchConfig.ts). For that, Button and HeaderFade gain an optional colorScheme prop, and callers that don't pass it are unchanged.
  • Banner (HomeScreen/EthenaLaunchBanner.tsx, hooks/useEthenaLaunchBanner.ts):
    • It sits between the action row and the Tokens tabs (HomeScreen).
    • It stays hidden while its dismissal loads, and if reading it fails, as in the extension.
    • Its ✕ stores the dismissal and tracks ethena_launch.banner_dismissed. Resetting the wallet clears the dismissal along with the rest of the non-sensitive data.
    • Its glow takes the card's full height. An Image otherwise keeps a bundled asset's own height, which left a strip under the glow on Android, whose text renders taller.
  • Analytics: ethena_launch.promo_viewed, ethena_launch.swap_tapped and ethena_launch.receive_tapped (both with token), ethena_launch.learn_more_tapped and ethena_launch.banner_dismissed. Discover tabs opened from the promo use the new ethena_launch_promo source.
  • Copy: English and Portuguese. The Portuguese comes from the extension.
  • Assets:
    • The wave is a bitmap at @1x, @2x and @3x (615 KB in all), rendered from the design's SVG and cropped to its box. Drawn as SVG, its ~200 gradient strokes held the UI thread for over a second on 3x screens.
    • The USDe and sUSDe logos are SVGs. The coins and the banner's glow are PNGs at @1x, @2x and @3x.

Product decisions

  1. The one-time showing copies the rebrand announcement's rule, and the rebrand announcement goes first.
  2. Swap uses these exact Sushi URLs. Sushi is still adding support for the parameters, so its page doesn't read them yet.
    • USDe: https://www.sushi.com/stellar/swap?token0=CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA&token1=CCNOV6XVI2O5BSTA3232GIRUQULDEBV7IWGKM6QKI6II5OIIQTU24JJN
    • sUSDe: https://www.sushi.com/stellar/swap?token0=CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA&token1=CACSKWC753QVGC4J6YPDBIR2BEHFVWIAE7CBSS3HOVYMN623OAIQYE2M
  3. Receive shows the QR code with no trustline handling. USDe and sUSDe are classic assets, so receiving them needs a trustline, which is left to the user.
  4. The top bar stays put while the page scrolls. The cue reads "Scroll to discover tokens", and tapping it stops it 5pt below the bar.
  5. The look doesn't depend on the theme, except for the banner's outline in dark mode.
  6. The promo keeps each platform's own fade: 500ms on iOS, 150ms on Android.

Parity with stellar/freighter#3095: the same one-time rule and the same Sushi URLs, opened in a new Discover tab instead of a browser tab. Mobile has no "Transfer to Stellar" (Stargate) action, as in the mobile design, and its cue also scrolls when tapped.

Verification

  • yarn test passes: 355 suites (1 skipped) and 5,242 tests.
  • tsc is clean, and ESLint and Prettier are clean on every changed file. yarn check as a whole still fails on 106 ESLint errors in 18 files this PR doesn't change, as it does on main.
  • New tests cover:
    • the eligibility helper: fresh install, update, a stored value kept, storage failures;
    • the auto-show gate and hook, and a wipe marking the promo seen;
    • the banner's hook and component;
    • the promo: each action's destination and URL, Swap hidden with Discover off, close, the double-tap guard, the cue's scroll target;
    • the entrance, Reduce Motion included;
    • SplashOverlay's onFinished, and the new Button and HeaderFade prop.
  • Devices, on debug builds:
    • iPhone 18 Pro simulator: the recordings above. The update launch is simulated by removing the status key while a wallet is present.
    • iPhone SE (3rd generation) simulator: the same flows and Learn more. Also a launch that owes the rebrand announcement (only the rebrand that launch, the promo on the next one), a fresh install through onboarding (no promo, banner only), and the cue's spacing on a short screen.
    • Pixel 10 Pro emulator: the update launch, the cue, Receive, Swap → Discover, the banner in both themes, and Android's back button.
    • Opening speed, measured on the iPhone 18 Pro with timing probes and frame-by-frame recordings:
      • banner tap → the promo starts appearing: about 1.5s before, about 0.1s now (iOS then fades it in over its default 500ms);
      • unlock → the entrance: a 2–4.5s black page before, about 0.1–0.2s now.
  • E2E:
    • In CI on this PR, all 13 flows pass on both iOS and Android.
    • Locally on the Pixel 10 Pro emulator, CreateWallet, SendClassicToken and SwapClassicToken pass too.
    • On the local iOS 27.2 simulator, the flows can't get past the recovery phrase paste, because of a simulator bug that CI's iOS 26.2 doesn't have.

Notes

  • Each Swap tap opens a new Discover tab, as the extension opens a new browser tab.
  • The banner appears just after Home's first render, so the token list shifts down once.
  • A Discover deep link that cold-starts the app gets the one-time promo on top of it.
  • No automated test covers the banner opening the promo or the rebrand-first rule, because RootNavigator has no test harness. Both were checked on devices.
  • The banner's colors live in the promo's config file.
  • Unrelated and pre-existing: an Android debug build can't start while the system's reduce-motion setting is on, because of the order Reanimated initializes its logger in. It happens without this PR too, and release builds are unaffected.
  • The draft Earn PR feat(earn): Earn deposit flow #1059 also edits Home's action row, so whichever merges second gets a small conflict.

Before merge

  • Revert the temporary every-launch commit (911ab2b0, reverted in a42d5794).
  • Design review on device.
  • E2E green on both platforms.

🤖 Generated with Claude Code

CassioMG and others added 8 commits November 13, 2026 17:52
…ssal

The promo follows the rebrand announcement's rule: wallets that exist
when this version first runs are owed it once; fresh installs and new
or imported wallets are not. The status survives wallet wipes, and a
wipe marks it seen so onboarding never ends on it. The Home banner's
dismissal is stored separately and fails closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Ethena launch promo and its Home banner look the same in light and
dark mode. An optional colorScheme prop lets Button and HeaderFade draw
one scheme's colors whatever the device's is, so the promo can use the
standard secondary and tertiary buttons and the main tabs' header fade
in their dark form. Existing callers don't pass it and are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Meet Ethena on Stellar": the wave and coins, the intro, a scroll cue,
then USDe and sUSDe, each with Swap and Receive, and Learn more about
Ethena. It's a root-stack screen that fades in, black with light text
in both themes, under a transparent bar with a close button.

- Swap opens Sushi's XLM-to-token pair in a new Discover tab and
  returns to the Discover tab. It's hidden while Discover is off.
- Receive opens the account's QR code over the promo.
- Learn more opens Ethena's docs in the in-app browser.
- A second tap within a second is ignored, so a double tap can't open
  two tabs or screens.
- The promo marks itself seen and tracks the view on mount.

The coins are pngquant-compressed PNGs at 1x, 2x and 3x; the wave and
token logos are SVGs exported from the design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The promo's one-second entrance plays once the screen has faded in:
the wave slides in from the left and the coins rise as both fade in
over 800ms, the title and subtitle fade in from 200ms and the scroll
cue from 400ms. Under Reduce Motion every part starts in place.

Tapping the scroll cue brings it to rest 60pt below the bar, with the
tokens under it. The bar picks up the main tabs' header fade, in its
dark form, once the page has scrolled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e splash

At launch the promo's eligibility is settled next to the rebrand
announcement's, and a launch that owes the rebrand announcement shows
only that, leaving the promo for the next launch. The promo opens once
Home is up and nothing covers it: after the splash has finished (which
the splash overlay now reports), and never over the wallet setup screen
or the rebrand announcement. A hard lock and unlock in the same launch
doesn't bring it back, and signing out drops the pending answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Meet Ethena on Stellar" sits between Home's action row and the token
tabs and opens the launch promo. It looks the same in both themes, with
the design's blue glow on the right, and dark mode adds a thin outline
(an overlay, so nothing moves) to set it off from the black page.

The ✕ dismisses it for good; the banner stays hidden while that choice
loads and if it can't be read, so a dismissed banner never flashes back.
The card and the ✕ are sibling buttons so a screen reader reaches both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a 667pt iPhone SE the promo's first screen is about as tall as its
content, so the flexible space above the scroll cue collapsed and the
cue sat right under the subtitle. The space now never drops below the
app's default 24pt; taller screens, where it's much larger, don't change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The launch-time gate let the promo open in two cases where nobody would
see it. While the force-update notice or the maintenance screen stands
in for the navigator, the navigation was dropped and the launch lost its
promo. Under the lock overlay, which keeps the authenticated tree
mounted, the entrance played behind the overlay. The gate is now a small
tested function that also waits for an unlocked wallet and for those
screens to go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Android Emulator preview APK is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-d8d94d7e26c35dc1837e
Backend: sandbox (CassioMG). SDF collaborators only — install instructions in the release description.

@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

iOS Simulator preview build is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-2f6f02222ae1733c66d0
Backend: sandbox (CassioMG). SDF collaborators only — install instructions in the release description.

CassioMG and others added 7 commits November 13, 2026 21:50
…n iOS

Review feedback. Tapping "Scroll to discover tokens" now stops the cue
20pt below the top bar instead of 60pt. The promo's route also fades in
and out over 150ms on iOS, as it does on Android, instead of iOS's
500ms default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Temporary, for a UX check of the automatic showing. Revert before merge.

Outside E2E builds, every launch now owes the promo, so it opens after
the splash, or right after unlocking when the launch starts locked. It
also opens right after creating or importing a wallet. E2E builds keep
the real rule, so CI's flows aren't affected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An Image sizes a bundled asset to the asset's own height unless its
style sets one, so the glow stayed 78pt tall. The card is about that
tall on iOS, but Android renders its text taller, which left a dark
strip under the glow. The glow now takes the card's full height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review feedback: tapping "Scroll to discover tokens" now stops the cue
5pt below the top bar, down from 20pt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…read

On the iPhone 18 Pro simulator the promo took about 1.5s to appear
after a banner tap. Drawing the wave's ~200 gradient strokes as SVG
held the UI thread for over a second on 3x screens. The wave is now a
bitmap of the same art (@1x, @2x and @3x, cropped to its box), and the
promo appears about 0.25s after the tap.

After an unlock, the JS thread stays busy for a few seconds while Home
loads. The entrance waited for it to deliver the transition's end, so
the art sat on a black page. The entrance is now handed to the UI
thread as the promo mounts and starts as its 150ms fade-in ends. The
fade's length is shared with the route's options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Now that nothing holds the promo's opening back, iOS fades it in and out
over its default 500ms again, which looks better there than Android's
150ms. Android keeps its 150ms fade. The entrance still starts as the
fade-in ends, on each platform.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts commit 911ab2b.

The UX check is done, so the promo is back to opening once, for wallets
that existed before the update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CassioMG
CassioMG marked this pull request as ready for review November 14, 2026 09:05
@CassioMG
CassioMG requested a lite review from Copilot November 14, 2026 09:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The eligibility decision is not terminal when the initial status or account check fails, allowing a later retry to incorrectly show the promo for an existing wallet.

1 open issue
What changed in this PR

Adds the Ethena launch experience to the mobile wallet, including a dismissible Home banner and a full-screen USDe/sUSDe promotion with launch eligibility, token actions, analytics, localization, and supporting assets.

Changes:

  • Adds persistent eligibility, banner dismissal, rebrand sequencing, wallet-wipe handling, splash synchronization, and navigation integration.
  • Adds the Ethena promotional screen with Sushi Discover, Receive QR, Learn more, animations, and dark-pinned styling.
  • Adds English and Portuguese translations, artwork, analytics events, and comprehensive unit and component tests.
File Description
src/​services/​storage/​helpers.ts Preserves Ethena launch eligibility while clearing non-sensitive wallet data.
src/​services/​analytics/​discover.ts Adds the Ethena promo source for Discover analytics.
src/​navigators/​RootNavigator.tsx Integrates Ethena eligibility, automatic display gating, splash coordination, and routing.
src/​i18n/​locales/​pt/​translations.json Adds Portuguese Ethena promo and banner copy.
src/​i18n/​locales/​en/​translations.json Adds English Ethena promo and banner copy.
src/​hooks/​useEthenaLaunchPromoAutoShow.ts Implements promo eligibility gating and one-display-per-launch behavior.
src/​hooks/​useEthenaLaunchBanner.ts Loads and persists Home banner visibility and dismissal state.
src/​helpers/​rebrandAnnouncement.ts Exports existing-account detection for shared launch eligibility logic.
src/​helpers/​ethenaLaunchPromo.ts Implements Ethena promo status, eligibility, and banner dismissal persistence.
src/​ducks/​auth.ts Marks the promo as seen during wallet reset.
src/​config/​routes.ts Adds the Ethena route, promo source type, and navigation parameters.
src/​config/​constants.ts Defines Ethena storage keys and lifecycle documentation.
src/​config/​analyticsConfig.ts Adds Ethena promo and banner analytics events.
src/​components/​SplashAnimation/​SplashOverlay.tsx Exposes a callback for one-time splash completion handling.
src/​components/​sds/​Button/​index.tsx Supports explicitly pinned button color schemes.
src/​components/​screens/​HomeScreen/​HomeScreen.tsx Places the Ethena banner between the Home action row and token tabs.
src/​components/​screens/​HomeScreen/​EthenaLaunchBanner.tsx Implements the dismissible Ethena Home banner and its artwork.
src/​components/​screens/​EthenaLaunchScreen/​useEthenaLaunchEntrance.ts Provides the promo entrance animation and reduced-motion behavior.
src/​components/​screens/​EthenaLaunchScreen/​scrollCue.ts Calculates the scroll target for the token discovery cue.
src/​components/​screens/​EthenaLaunchScreen/​index.ts Exports the Ethena launch screen.
src/​components/​screens/​EthenaLaunchScreen/​EthenaTokenSection.tsx Renders Ethena token details and Swap and Receive actions.
src/​components/​screens/​EthenaLaunchScreen/​EthenaLaunchScreen.tsx Implements the full-screen promo, navigation actions, and analytics.
src/​components/​screens/​EthenaLaunchScreen/​EthenaLaunchHero.tsx Renders the promo hero artwork, copy, and scroll cue.
src/​components/​screens/​EthenaLaunchScreen/​ethenaLaunchConfig.ts Defines Ethena tokens, URLs, artwork, colors, and animation timing.
src/​components/​layout/​HeaderFade.tsx Supports header fades pinned to a specific color scheme.
src/​assets/​logos/​usde.svg Adds the USDe logo asset.
src/​assets/​logos/​susde.svg Adds the sUSDe logo asset.
src/​assets/​images/​index.ts Registers the Ethena promotional image assets.
tests/​services/​analytics/​discover.test.ts Tests the Ethena promo Discover analytics source.
tests/​hooks/​useEthenaLaunchPromoAutoShow.test.tsx Tests promo eligibility gating and one-time automatic display.
tests/​hooks/​useEthenaLaunchBanner.test.tsx Tests banner loading, dismissal, persistence, and analytics.
tests/​helpers/​ethenaLaunchPromo.test.ts Tests promo eligibility, persistence, storage failures, dismissal, and wallet lifecycle behavior.
tests/​ducks/​auth.test.ts Verifies that wallet wipes mark the promo as seen.
tests/​config/​theme.test.ts Tests registration of the pinned Ethena theme and logo assets.
tests/​components/​SplashAnimation/​SplashOverlay.test.tsx Tests the splash completion callback.
tests/​components/​sds/​Button.test.tsx Tests pinned button color schemes.
tests/​components/​screens/​HomeScreen/​EthenaLaunchBanner.test.tsx Tests banner rendering, actions, styling, themes, and glow sizing.
tests/​components/​screens/​EthenaLaunchScreen/​useEthenaLaunchEntrance.test.tsx Tests promo animation timing and reduced-motion behavior.
tests/​components/​screens/​EthenaLaunchScreen/​scrollCue.test.ts Tests scroll cue target calculations.
tests/​components/​screens/​EthenaLaunchScreen/​EthenaLaunchScreen.test.tsx Tests promo actions, navigation, analytics, styling, and scrolling.
tests/​components/​screens/​EthenaLaunchScreen/​ethenaLaunchConfig.test.ts Tests platform-specific promo transition timing.
tests/​components/​layout/​HeaderFade.test.tsx Tests pinned dark header fades across device themes.

Review effort: Lite

Comment thread src/helpers/ethenaLaunchPromo.ts Resolved
@CassioMG
CassioMG requested a review from a team November 14, 2026 09:21
@CassioMG CassioMG self-assigned this Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants