Skip to content

Feat/wallet signing ux - #299

Open
N-thnI wants to merge 8 commits into
Goldii-locks:mainfrom
N-thnI:feat/wallet-signing-ux
Open

Feat/wallet signing ux#299
N-thnI wants to merge 8 commits into
Goldii-locks:mainfrom
N-thnI:feat/wallet-signing-ux

Conversation

@N-thnI

@N-thnI N-thnI commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

closes #254
closes #224
closes #217
closes #250

Summary

Four wallet-flow improvements: fee warnings on the signature timeout alert,
interactive states on the wallet badge, timeout clocks in the wallet selector,
and session persistence for the transaction signer

Changes

signature_timeout_alert — fee warning banners

None of these four modules existed as files, so each follows the repo's
established per-module convention (app/lib/<module>.ts + components + tests),
modeled on the closest existing analogue.

  • app/lib/signature_timeout_alert.ts extracts the estimated fee from a Soroban
    (minResourceFee) or classic (fee) simulation result and buckets it into
    none / elevated / exceeded / error against a 1,000,000-stroop bound.
  • SignatureTimeoutFeeWarningBanner renders it with severity-driven design
    tokens; SignatureTimeoutAlert composes it with the timeout notice and an
    optional retry action.
  • assertSignatureFeeWithinBounds throws, for callers that want to hard-stop
    rather than render a banner.

wallet_badge — hover, focus-visible and disabled states

  • New optional onClick makes the badge actionable; it then renders as a
    <button> with hover:bg-surface-card, an accent focus-visible ring offset
    against surface-page, and disabled:opacity-50 / disabled:cursor-not-allowed.
  • disabled:hover:* variants neutralise hover so a disabled badge stays
    visually inert.
  • New disabled prop; the badge also auto-disables while status="loading".
  • The disconnect control gains matching states, and sits beside the badge when
    the badge is itself a button (buttons cannot nest).
  • Status token classes, data-testid and data-status are unchanged, so both
    existing WalletBadge suites still pass untouched.

wallet_selector_modal — transaction timeout clocks

  • signWithWalletSelectorTimeout races the provider callback against a 60s
    default bound.
  • On expiry it aborts through an AbortSignal handed to the provider, zeroes
    and nulls the request payload, and rejects with WalletSelectorTimeoutError.
  • A pending-operation registry tracks in-flight requests and is emptied on
    every exit path — success, provider error, timeout — so no controller or
    buffer outlives its request.
  • abortAllWalletSelectorOperations() covers modal close / unmount.

transaction_signer_component — session persistence

  • Versioned envelope (address, walletId, networkPassphrase, connectedAt)
    in localStorage.
  • Parsing validates the schema version, the Stellar address format and every
    field, and rejects any payload carrying a secret key, seed, mnemonic or auth
    token
    . Invalid payloads are dropped from storage so the next reload starts
    clean.
  • TransactionSignerSessionStore mirrors the session in memory, rehydrates on
    construction, and exposes restore() / clear() / overrideStorage().
  • Storage access is guarded throughout, so SSR and blocked-storage browsers
    degrade to an empty session instead of throwing.

Testing

153 new tests across four files:

  • signature_timeout_alert_fee_warning.test.tsx — fee extraction, severity
    bucketing, the exceeded/elevated/error boundaries, banner rendering and
    console warnings.
  • wallet-badge-interactive-states.test.tsx — class assertions for each state
    plus real behaviour: keyboard focus, Enter activation, disabled skipped by
    tab order, handlers not firing while disabled.
  • wallet_selector_modal_timeout.test.ts — abort-on-timeout, memory clearing,
    registry emptied on all three exit paths, concurrent requests.
  • transaction_signer_persistence.test.ts — round-trip across reload, corrupt
    and version-mismatched payloads, sensitive-field rejection, storage failures.

Suite goes from 74 files / 1059 tests to 78 files / 1212 tests, all passing.
tsc --noEmit and eslint both clean.

N-thnI and others added 4 commits August 26, 2026 18:40
…e warning banners

Adds fee inspection to signature_timeout_alert so the user sees what a
pending signature will cost before approving it.

- app/lib/signature_timeout_alert.ts extracts the estimated fee from a
  Soroban (minResourceFee) or classic (fee) simulation result and buckets
  it into none/elevated/exceeded/error severities against a standard
  1_000_000 stroop bound.
- SignatureTimeoutFeeWarningBanner renders the warning with severity-driven
  design tokens; SignatureTimeoutAlert composes it with the timeout notice
  and an optional retry action.
- assertSignatureFeeWithinBounds throws for callers that want to hard-stop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gives WalletBadge real interactive affordances built from Tailwind
utilities and the repo design tokens.

- New optional onClick makes the badge actionable; it then renders as a
  <button> with hover:bg-surface-card / hover:border-accent-soft, an
  accent focus-visible ring offset against surface-page, and
  disabled:opacity-50 / disabled:cursor-not-allowed. The disabled:hover:*
  variants neutralise hover so a disabled badge stays visually inert.
- New disabled prop; the badge also auto-disables while status is loading.
- The disconnect control gains matching hover, focus-visible and disabled
  styling, and sits beside the badge when the badge is itself a button.
- Status token classes, data-testid and data-status are unchanged, so the
  existing WalletBadge suites still pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wallet providers picked from the selector list can hang indefinitely, so
every signature request is now raced against a timeout clock.

- signWithWalletSelectorTimeout races the provider callback against a
  60s default bound. On expiry it aborts the operation through an
  AbortSignal handed to the provider, zeroes and nulls the request payload,
  and rejects with WalletSelectorTimeoutError.
- A pending-operation registry tracks in-flight requests and is emptied on
  every exit path (success, provider error, timeout) so no controller or
  buffer outlives its request.
- abortAllWalletSelectorOperations cancels everything in flight for the
  modal-close / unmount path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Persists the signer's active address so a page reload no longer drops the
user back to a disconnected state.

- Versioned envelope (address, walletId, networkPassphrase, connectedAt)
  written to localStorage under transaction_signer_active_session.
- Parsing validates the schema version, the Stellar address format and
  every field, and rejects any payload carrying a secret key, seed,
  mnemonic or auth token. Invalid payloads are dropped from storage so the
  next reload starts clean.
- TransactionSignerSessionStore mirrors the session in memory, rehydrates
  on construction, and exposes restore()/clear()/overrideStorage().
- Storage access is guarded throughout, so SSR and blocked-storage
  browsers degrade to an empty session instead of throwing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Aug 26, 2026

Copy link
Copy Markdown

@N-thnI 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

N-thnI and others added 4 commits August 27, 2026 08:59
…n merge

main independently added modules with the same names this branch created --
app/lib/signature_timeout_alert.ts (Goldii-locks#243-Goldii-locks#247), app/lib/wallet_selector_modal.ts
and SignatureTimeoutAlert.tsx -- with entirely different APIs. The merge
resolved each file to main's version, which dropped this branch's
implementations while keeping its tests, leaving 38 type errors.

Re-applies the lost work additively so both feature sets coexist. No name
collides between the two APIs, so nothing of main's is overwritten this time:

  * signature_timeout_alert.ts gains the simulation fee-inspection API
    (inspectSignatureFee, the severity buckets, the stroop helpers and the
    two error types) appended after main's timeout/rejection/availability
    sections, reusing its existing LOG_PREFIX.
  * wallet_selector_modal.ts gains the timeout-clock API
    (signWithWalletSelectorTimeout, the abort registry and the memory
    clearing) appended after main's network-mismatch, caching and loading
    sections.

The standalone alert wrapper this branch had added is dropped: main's
SignatureTimeoutAlert now owns that name and covers the timeout UI. The fee
banner is instead rendered *inside* it behind two new optional props
(simulation, feeLimitStroops), which is closer to the original issue -- the
simulation is inspected inside signature_timeout_alert -- and leaves main's
existing props and tests untouched. The alert also stays visible for a fee
warning alone, not just a timeout or network mismatch.

The fee-warning suite now stubs WalletContext, because the alert pulls in the
Stellar Wallets Kit at module scope and this suite is about fee inspection.

tsc clean; the four suites on this branch pass (152 tests).
…lect

@stellar/freighter-api ships only a CommonJS bundle -- its package.json
declares `main` but no `module` or `exports` ESM entry -- so Vite could not
statically resolve the named imports that freighter_connector.ts and the
Wallets Kit rely on. Any suite whose import graph reached WalletContext died
at collection with:

  SyntaxError: The requested module '@stellar/freighter-api' does not
  provide an export named 'getAddress'

The export does exist; it is only reachable through CJS interop. Adding both
packages to server.deps.inline lets Vite transform the bundle and synthesise
the named exports.

This is pre-existing breakage on main, not from this branch's work -- both
affected suites fail the same way on origin with this branch's changes
stashed. Fixing it here because it blocks CI on the PR.

Effect: freighter_connector.component.test.tsx now passes in full, and
signature_timeout_alert.test.tsx actually executes instead of silently not
running -- which surfaces one genuine pre-existing failure in it. Suite goes
from 1445 to 1469 tests collected.

Still failing, all pre-existing and all in main's own suites, none touched by
this branch: 4 in freighter_multisig_hook.test.ts (envelope parsing) and 1
loader-counter test in signature_timeout_alert.test.tsx. Left for their
owners rather than fixed blind here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant