Skip to content

feat(trade): warn of launch penalty on sell confirmation - #853

Open
Ajibose wants to merge 1 commit into
accesslayerorg:devfrom
Ajibose:feat/sell-launch-penalty-warning
Open

feat(trade): warn of launch penalty on sell confirmation#853
Ajibose wants to merge 1 commit into
accesslayerorg:devfrom
Ajibose:feat/sell-launch-penalty-warning

Conversation

@Ajibose

@Ajibose Ajibose commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Closes #825

Summary

Holders who sell within 7 days of a key's creation incur a launch penalty. The sell confirmation modal now detects this and shows a prominent warning with the penalty amount before the user signs the transaction.

  • Detects whether the current ledger is within the 7-day launch window of the key's createdAtLedger
  • Shows a yellow warning box in the sell modal: "Early sell penalty applies — [X]% will be deducted from your proceeds"
  • Extends the sell fee breakdown with a "Launch penalty" line item and a "Net proceeds" total when the penalty applies
  • Warning and penalty line item are hidden once the key is past its 7-day launch window, or when no penalty is configured
  • The user can still confirm the sale after seeing the warning — it is informational, not a blocker

New files

  • src/utils/launchPenalty.utils.tsisWithinLaunchWindow (ledger-delta check against a 7-day/120,960-ledger window) and calculateLaunchPenalty (penalty + net proceeds from gross proceeds, ledgers, and launchPenaltyBps)
  • src/components/common/LaunchPenaltyWarning.tsx — the yellow warning banner shown on the sell modal
  • src/components/common/SellFeeBreakdown.tsx — sell-side fee breakdown (gross proceeds, launch penalty line, net proceeds), replacing the plain "Estimated proceeds" line that lived inline in TradeDialog

Modified files

  • src/components/common/TradeDialog.tsx — accepts createdAtLedger, currentLedger, and launchPenaltyBps props; computes the launch-penalty breakdown via useMemo from the estimated sell proceeds; renders LaunchPenaltyWarning and swaps the old inline sell-proceeds block for SellFeeBreakdown
  • src/services/course.service.ts — adds createdAtLedger and currentLedger to the Course type (the key detail API model returned by getCourse/GET /courses/:id), alongside the existing launchPenaltyBps
  • src/pages/LandingPage.tsx — wires the new createdAtLedger/currentLedger/launchPenaltyBps props from the featured creator into the existing TradeDialog usage

Implementation details

  • The 7-day launch window is expressed in ledgers (LAUNCH_WINDOW_LEDGERS = 120,960, derived from Stellar's ~5s ledger close time), matching the existing ledgerToTimestamp convention in stellarLedger.utils.ts, so the check is a plain ledger-delta comparison (currentLedger - createdAtLedger < LAUNCH_WINDOW_LEDGERS) with no extra RPC call needed.
  • currentLedger is sourced from the same key detail API response as createdAtLedger/launchPenaltyBps — this repo has no live Soroban/Horizon RPC integration yet, so the backend response is the natural place for a ledger reference point.
  • calculateLaunchPenalty is defensive: missing/invalid ledgers, a zero/undefined launchPenaltyBps, or a non-positive gross amount all resolve to applies: false with netProceedsStroops falling back to the gross amount (or 0 if gross is unavailable), so malformed data never blocks a sell or fabricates a penalty.
  • SellFeeBreakdown preserves the exact "Estimated proceeds (approximate)" / "Estimated proceeds unavailable" copy and DOM shape used by the pre-existing TradeDialog.sellPayoutDisplay.test.tsx suite, so that suite passes unmodified — the penalty and net-proceeds rows are purely additive, shown only when launchPenalty.applies is true.
  • Percentage formatting reuses the existing bpsToPercent helper from numberFormat.utils.ts for consistency with other fee displays.

Tests added

  • src/utils/__tests__/launchPenalty.utils.test.ts — unit tests for isWithinLaunchWindow (at creation, mid-window, exactly at the 7-day boundary, well past, ledger clock-skew, missing/NaN inputs) and calculateLaunchPenalty (applies within window, no-op past window, no-op with no penalty configured, no-op with missing ledger data, rounding, 0%/20% boundary penalties, gross-proceeds-unavailable fallback)
  • src/components/common/__tests__/LaunchPenaltyWarning.test.tsx — renders nothing when not visible, renders the warning text and formatted percentage when visible, formats fractional percentages, has role="alert"
  • src/components/common/__tests__/SellFeeBreakdown.test.tsx — gross-only rendering when no penalty applies, "unavailable" fallback, penalty + net proceeds rows when a penalty applies, correct formatting for a large (20%) penalty
  • src/components/common/__tests__/TradeDialog.launchPenalty.test.tsx — integration coverage inside the sell modal: warning shown within the window, hidden past the window, hidden with no penalty configured, hidden with no ledger data, never shown on the buy side, correct penalty/net-proceeds amounts in the fee breakdown as the sell quantity changes, no penalty line item past the window, and confirming the sale still works after the warning is shown

All new/changed tests pass (61 tests across the four new files, plus the pre-existing TradeDialog.sellPayoutDisplay.test.tsx and TradeDialog.a11y.test.tsx suites verified unaffected). The two pre-existing failing test categories in the full suite (WagmiProviderNotFoundError in ReferralLinkPanel.test.tsx, react-router context errors in CreatorCard.*.test.tsx, and clipboard-timeout tests in CreatorProfileHeader.copy.test.tsx) were confirmed present on dev before this change and are unrelated to this PR. Similarly, the pre-existing tsc -b errors in CreatorDetailPage.tsx (missing co-creator fields on Course) were confirmed present on dev and are untouched by this change.

How to test

  1. pnpm install && pnpm test — run the full suite, or scope to the new files:
    pnpm vitest run src/utils/__tests__/launchPenalty.utils.test.ts \
      src/components/common/__tests__/LaunchPenaltyWarning.test.tsx \
      src/components/common/__tests__/SellFeeBreakdown.test.tsx \
      src/components/common/__tests__/TradeDialog.launchPenalty.test.tsx
    
  2. pnpm dev, open the landing page, and click "Sell" on the featured creator card to open the sell confirmation modal.
  3. To see the warning locally, temporarily set createdAtLedger/currentLedger on DEMO_CREATORS[0] in LandingPage.tsx so the ledger delta is under 120,960 (e.g. createdAtLedger: 1000, currentLedger: 1100) and give it a launchPenaltyBps (e.g. 500 for 5%) — the yellow warning banner and the "Launch penalty" / "Net proceeds" rows should appear in the fee breakdown as you change the sell amount.
  4. Push currentLedger out past createdAtLedger + 120,960 (or remove the fields) to confirm the warning and penalty row disappear and the modal behaves exactly as before.

…org#825)

Detects when a sell falls within a key's 7-day launch window using its
createdAtLedger and launchPenaltyBps (from the key detail API) and shows
a prominent warning plus an updated fee breakdown with the penalty
deducted and net proceeds, so holders can acknowledge the cost before
signing.
@drips-wave

drips-wave Bot commented Aug 29, 2026

Copy link
Copy Markdown

@Ajibose Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

Add a launch penalty warning on the sell confirmation modal for keys within the 7-day launch window

1 participant