Skip to content

Implement a client-side transaction signing pipeline with hardware wallet support, signature aggregation, and structured error classification #758

Description

@Chucks1093

Summary

The current signing flow calls the wallet extension and handles all errors as a single generic failure. This issue builds a full signing pipeline that supports both software wallets (Freighter) and hardware wallets (Ledger via WebHID), aggregates multi-signature payloads where required, classifies every possible failure into a typed error with a user-actionable recovery hint, and implements retry logic with exponential backoff for transient hardware faults.

Scope

1. Signer abstraction

  • Define a Signer interface: { sign(xdr: string): Promise<string>, getPublicKey(): Promise<string>, type: 'software' | 'hardware' }
  • Implement FreighterSigner wrapping the Freighter browser extension API
  • Implement LedgerSigner wrapping the Stellar Ledger app via @ledgerhq/hw-transport-webhid
  • Expose a useSigner() hook that detects which signers are available and returns the active signer based on the user's wallet preference stored in localStorage

2. Ledger transport layer

  • Open a WebHID transport on first use and keep it alive for the session (re-open on disconnect)
  • Implement a connection health check: send a no-op APDU and verify the response before each signing request
  • If the Ledger is locked, return typed error LedgerLocked with hint 'Unlock your Ledger and open the Stellar app'
  • If the Stellar app is not open, return typed error LedgerAppNotOpen with hint 'Open the Stellar app on your Ledger'

3. Typed error classification

  • Define a discriminated union SigningError:
    • UserRejected — user dismissed the signing prompt
    • NetworkMismatch — wallet is on a different network than the app
    • LedgerLocked — Ledger device is locked
    • LedgerAppNotOpen — Stellar app not open on Ledger
    • LedgerTimeout — no response from Ledger within 30 seconds
    • TransactionTooLarge — XDR exceeds the signer's maximum size
    • SignerUnavailable — wallet extension not installed or WebHID not supported
  • Map every raw extension and WebHID error to one of the above types in a central error classifier

4. Retry pipeline with backoff

  • On LedgerTimeout: retry up to 3 times with 2s, 4s, 8s delays; show a 'Retrying…' indicator between attempts
  • On LedgerLocked or LedgerAppNotOpen: do not retry — immediately show the typed error hint and a 'Check Ledger' button that re-runs the health check
  • On UserRejected: do not retry — reset the UI immediately

5. Signing progress UI

  • Show a multi-step progress indicator during signing: 'Preparing transaction' → 'Waiting for signature' → 'Submitting to Stellar'
  • Each step transitions only when the underlying async operation completes
  • On error, the failed step turns red and displays the typed error hint inline

6. Unit tests

  • Unit tests: error classifier correctly maps every known raw error to its typed SigningError
  • Unit tests: retry logic fires correct delays and stops after 3 attempts
  • Unit tests: useSigner returns the correct signer based on localStorage preference and availability

Acceptance Criteria

  • FreighterSigner and LedgerSigner both implement the Signer interface
  • Every raw signing error mapped to a typed SigningError with a user-actionable hint
  • Ledger retried up to 3 times on timeout with exponential backoff
  • Multi-step progress indicator transitions correctly through the signing pipeline
  • LedgerLocked and LedgerAppNotOpen not retried — hint shown immediately
  • useSigner detects available signers and respects localStorage preference

ETA: 24 hours


Coordinate on Telegram

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaign

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions