Skip to content

feat(signing): render W addresses and fail closed on unreadable dApp requests - #1037

Merged
CassioMG merged 30 commits into
mainfrom
cg-p30-signing-w
Nov 6, 2026
Merged

CassioMG merged 30 commits into
mainfrom
cg-p30-signing-w

Conversation

@CassioMG

@CassioMG CassioMG commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

Signing screens now show muxed contract addresses (W…) in full, in transaction details and in auth entries. On SDK 17.4.0 this needed no display change, like extension stellar/freighter#3045; new tests pin it on every signing surface.

A dApp request (transaction or auth entry) whose details the app can't display can now only be rejected. The app builds the display data before it offers Confirm. If that fails, the request sheet shows "Unable to read this transaction", or "Unable to read this authorization" for an auth entry, with only Cancel, and the app refuses to sign the request whichever button asks it to.

A new error boundary wraps the transaction details and the whole auth-entry display, so a render error there shows the same notice instead of closing the app. On a dApp request a trip also switches the request underneath to the Cancel-only state. The details sheet stays open with its notice until the user closes it, so nothing closes on its own. On send and swap, which the app builds itself, it only shows the notice, and Confirm stays.

Closes stellar/wallet-eng-monorepo#108

Changed lines

Files Added Removed Share of changed lines
Source code 20 1,272 227 28%
Tests 19 3,307 72 64%
Other 3 408 5 8%
Total 42 4,987 304

Source code is the app code under src/, translations included. Tests are the unit tests and their fixtures, and the mock dApp's code. Other is the mock dApp's lockfile, package.json and README.

Screenshots and Videos

These are on testnet, with requests from the mock dApp.

Fail closed

A dApp transaction it can't read A render error on Transaction details Dark mode Dark mode
Malformed transaction: notice with Cancel only The details sheet stays open with its notice; closing it leaves Cancel only The details sheet keeps its notice on Android Cancel only after closing the details on Android

The auth-entry wording

Sign auth entry Transaction request
Auth entry notice saying authorization Transaction notice unchanged

W addresses render in full (no display change was needed on SDK 17.4.0)

Parameters Authorizations
W in the transaction's parameters W in the authorization's parameters
Implementation details (for agents)

Decisions (from the Protocol 30 design spec)

  • D10 (dApp requests fail closed): the display data is built up front, and if that fails the request can only be rejected. Send and swap keep Confirm, and the boundary stays as a backstop.
  • D7 (error boundary): this is mobile-only. The extension has a generic error page; before this PR, mobile had no error boundary, so a render error closed the app.
  • Why not a fallback string: addressToString keeps throwing. validateAuthEntryAddress and history rely on that throw, and making it return a placeholder would weaken auth-entry validation, which the spec lists as an anti-goal. No render path needed a display-safe wrapper.
  • W rendering: on 17.4.0, Address.fromScAddress handles W, so addressToString, scValToDisplayTokens and ScValDisplay already render it. Task 1 only adds tests, as in extension feat(signing): render W addresses and survive undecodable dApp XDR freighter#3045.

What changed

  • Display state, built up front:
    • signingDisplay.ts (new) dry-runs every conversion the details make that can throw: operation arguments, contract and deployer addresses, constructor arguments, auth invocations and their sub-invocations, and the CAP-71 signer address. Conversions that can't throw on decoded XDR aren't repeated; the module doc lists them, including the two toXdr("base64") React keys. It exports assertOperationsDisplayable, assertInvocationDisplayable and isAuthEntryDisplayable. A parse failure only leaves a breadcrumb; a decodable but undisplayable value is reported to Sentry.
    • SignTransactionDetailsState is empty | ready | undisplayable. It's built by buildSignTransactionDetailsState and useSignTransactionDetailsState, and getDisplayableDetails unwraps it.
    • useSignTransactionDetails keeps its signature and still returns details or null. The send and swap callers are unchanged: only WalletKitProvider uses the new state hook.
  • Send and swap gain one null case: useSignTransactionDetails now also returns null when the post-parse dry-run fails, and it logs that with error instead of warn, so it reaches Sentry. That's practically unreachable for transactions the app builds itself. When it happens it hides the "Transaction details" button, and Confirm is unaffected.
  • Provider guard (D10):
    • WalletKitProvider.tsx L270-L315 derives isRequestUndisplayable from three things: the transaction state, isAuthEntryDisplayable for auth entries, and a hasDetailsRenderError flag that the boundary sets.
    • isDappRequestUndisplayable applies the rule per method: empty fails closed for XDR methods, and signMessage never does.
    • handleDappRequest refuses to sign an undisplayable request and logs it. Every approval goes through it, including the Blockaid sheet's "Confirm anyway" (handleProceedAnyway), so this one check covers both.
    • The flag is cleared together with the request in handleClearDappRequest's deferred reset, so the next request starts clean.
  • Cancel-only sheets:
    • DappRequestUnreadableContent (new) renders the header, UnreadableTransactionNotice and one Cancel. Cancel is disabled while signing, matching the request buttons, so a rejection can't go out for a request that may still be signed. There are no banners, because the Blockaid banner opens a sheet that offers "Confirm anyway".
    • It's used by DappSignTransactionBottomSheetContent and DappSignAuthEntryBottomSheetContent.
    • Copy, in en and pt: "Unable to read this transaction" / "The transaction could not be decoded."
    • On auth-entry requests the notice has its own wording, "Unable to read this authorization" / "The authorization entry could not be decoded." (en, pt), on the Cancel-only sheet and in Authorization Entry Details, picked by the notice's variant. Transaction requests, send and swap keep the transaction wording.
  • Error boundary (D7):
    • ErrorBoundary (new) reports to Sentry through logger.error and shows a fallback. It resets when resetKey changes, through getDerivedStateFromProps.
    • componentDidCatch calls onError in a finally, so a throwing logger can't skip the fail-closed flag.
    • It wraps the details sheet's sections (SignTransactionDetailsBottomSheet), which covers send, swap and dApp requests, and the whole auth-entry display (DappAuthEntryDisplay). One boundary above the cards reports a broken entry once, also catches a throw outside a card's body, and its notice replaces the whole entry (8d296c61, from review).
    • On a dApp request, onError sets hasDetailsRenderError. The send flow's SendReviewBottomSheet, which feat(send): support muxed contract (W) destinations #1036 changes, is untouched here.
  • The details sheet stays open after a trip:
    • SignTransactionDetails.tsx and DappAuthEntryDetails.tsx are each split into a button and a sheet. The request sheets (transaction, auth entry) own the sheet's ref and render the sheet in the same keyed slot, details-sheet, in both layouts, so switching to DappRequestUnreadableContent doesn't unmount it.
    • After a trip the details sheet stays open, showing its own notice, until the user closes it with ✕ or the backdrop. The request underneath already offers only Cancel, so nothing closes or swaps on its own.
    • A transaction request that can't be displayed from the start has no details, so it mounts no details sheet.
    • Send's review sheet keeps the combined SignTransactionDetails (default export), now built from the two pieces.
    • The details sheets' close buttons get testIDs: sign-transaction-details-close-button and dapp-auth-entry-details-close-button.
    • No library patch. An earlier revision of this PR patched @gorhom/bottom-sheet's handlePortalOnUnmount, which leaves a sheet on screen when its owner unmounts while the sheet is still opening. With the owner kept mounted, this PR no longer reaches that path.
    • DappRequestNestedDetailsSheet.test.tsx (new) pins this for both sheets (see Tests).
  • Mock dApp: POST /session/:id/request/signXDR accepts { "fixture": "sacTransferToMuxedContract", "network", "to"? }.
    • The fixture builds a native SAC transfer from the session's account to the SEP-23 vector WA7Q…AWWC, or to to, with a source-account auth entry. It uses @stellar/stellar-sdk 17.4.0, which is pinned next to the existing stellar-base and loaded only when the fixture is requested.
    • A to that isn't an address gets a 400, since it's bad input. An xdr must be a non-empty string, as before.
    • The web UI gets a "Sign SAC transfer to W" button. engines.node becomes >=22.12.0, which CI's Node 22 satisfies.
    • The existing signAuthEntry preimage generator stays on stellar-base 14.1.0, which CI's e2e flows use.

Tests: +9 suites and +114 tests over the base (b80ca267, 334 suites and 4,831 tests). The full suite is 343 suites and 4,945 tests, all passing. Counts are Jest's, so each it.each case counts.

  • W display (no src/ change):
    • soroban.test.ts (+4): the W destination, addressToString → W…, the throw kept for unknown arms, and one scvAddress token.
    • KeyVal.test.tsx (+1): renders, reveals and copies a W argument.
    • Operations.test.tsx (+1): a W under the to label.
    • SignTransactionAuthorizations.test.tsx (+1) and DappAuthEntryDisplay.test.tsx (+7): a W in auth entries, and the auth-entry boundary, whose notice in Authorization Entry Details uses the authorization wording, catches a throw in the display's own render, and reports a broken entry once.
    • Fixtures in __tests__/support/sacTransferFixtures.ts.
  • signingDisplay.test.ts (22): the dry-run covers every throwing conversion the render makes, and Sentry vs breadcrumb is right for each failure kind.
  • signingDisplay.render.test.tsx (18, new): the pre-flight and the real render agree. For a SAC transfer to each fixture recipient (three Ws, a C, a G and an M), the transaction, a fee bump of it and its auth entry pass the pre-flight and render without tripping a boundary. A render-only throw fails all 18.
  • useSignTransactionDetails.test.ts (18): empty, ready and undisplayable cases, including a fee bump whose inner transaction can't be displayed, state stability, and the unchanged useSignTransactionDetails contract.
  • walletKitUtil.test.ts (+6): isDappRequestUndisplayable per method, and the render-error flag.
  • ErrorBoundary.test.tsx (7): fallback, a single report, onError even when reporting throws, reset on resetKey, and no remount or report when resetKey changes without an error.
  • SignTransactionDetailsBottomSheet.test.tsx (8): the notice, the Sentry report, onRenderError, notice-only on send and swap, and reset per transaction.
  • DappSignTransactionBottomSheetContent.test.tsx (6) and DappSignAuthEntryBottomSheetContent.test.tsx (6):
    • Cancel only, which rejects;
    • no details button on the Cancel-only request, while the details sheet stays mounted;
    • the notice's wording: the transaction one on a transaction request, the authorization one on an auth entry;
    • Cancel disabled while signing;
    • no Blockaid banner or "Confirm anyway" on a flagged undisplayable request;
    • the boundary callback wired through.
  • DappRequestNestedDetailsSheet.test.tsx (3, new): when a render error in the details fails the request closed, the details sheet stays mounted and open with its notice, its close button still closes it, and the request underneath offers only Cancel, for Transaction details and for Authorization Entry Details. A request that can't be displayed from the start mounts no details sheet. A stub of components/BottomSheet records mounts and unmounts; both sheet cases fail on the previous layout, which unmounted the sheet.
  • WalletKitProvider.test.tsx (6), a provider-level harness:
    • Confirm is refused on an unparseable XDR;
    • a displayable XDR is approved;
    • "Confirm anyway" is refused on a flagged undisplayable request;
    • the render-error flag survives rerenders and clears on teardown, after which the next request is approved;
    • an undisplayable auth entry is refused, and a displayable one is approved.
    • Deleting the guard or the reset makes these fail.

Verification

  • yarn test passes.
  • yarn lint:ts passes.
  • eslint and prettier report no new errors in changed files. The remaining errors are on main too:
    • eslint has 106 errors, all in untouched lines. main has 111, and the branch removed 5 of them in KeyVal.test.tsx.
    • prettier flags mock-dapp/public/index.html, which already failed on main; its 60 new lines follow the file's existing style.
  • yarn lint:translations reports no missing translations. Its 9 errors are import/order and no-continue in 4 untouched files, the same as on main.
  • Device runs on iPhone 17 Pro (iOS 26.5), Pixel_10_Pro (API 37) and iPhone SE 3rd gen (iOS 26.5) used the mock dApp on testnet and this branch's dev build with IS_E2E_TEST=true. That setting is needed because the WalletConnect origin check only accepts https dApps. (d) and (f) were re-run at 7ce3ac83, without the gorhom patch, on the iPhone 17 Pro and the Pixel_10_Pro; the second (a) ran on 9b78fa38.
    • On testnet, Blockaid can't scan these transactions, so its "unable to scan" step comes first, and the request sheet's Confirm reads "Continue" (spec §9).
Check iOS (17 Pro) Android iPhone SE
(a) SAC transfer to W: Confirm offered; to shows the full WA7Q…AWWC, tapping reveals scvAddress; same W under Authorizations → transfer; approve signs PASS, and again on the final head PASS PASS
(b) Malformed XDR: "Unable to read this transaction" and only Cancel; Cancel rejects PASS PASS PASS, no clipping
(c) Malformed and flagged (Blockaid override: Malicious): no banner, no "Confirm anyway", only Cancel. A displayable control under the same override does show the banner and "Confirm anyway" PASS PASS not run
(d) Render error injected locally in ScValDisplay for addresses, in Transaction details and in Authorization Entry Details: Confirm still offered before; after the trip the details sheet stays open with its notice; closing it shows the request with the notice and only Cancel; Cancel rejects PASS, both sheets PASS, both sheets not run
(e) After reverting: new transaction and auth-entry requests offer Confirm again and their details render PASS PASS not run
(f) Ordinary sheets without the patch: Transaction details and Authorization Entry Details on readable requests, and Home's account sheet, each close with ✕ and with the backdrop; the request sheet comes back PASS PASS for Transaction details; the rest not re-run not run
  • The (d) captures under "Screenshots and Videos" come from these runs.
  • After 8d296c61, on the iPhone SE (3rd generation) simulator, iOS 27.2, with a throw forced locally in the auth-entry display's own render (reverted): Authorization Entry Details shows only "Unable to read this authorization", in place of the whole entry, and closing it leaves the request with the notice and only Cancel.
  • The wording change ran on the iPhone 17 Pro at f654834a. After (d)'s injected trip in Authorization Entry Details, the auth-entry sheet reads "Unable to read this authorization", where it read "Unable to read this transaction" before, and a malformed signXDR still reads "Unable to read this transaction". The screenshots are under "Screenshots and Videos" at the top.
  • Every Cancel above reached the dApp as WalletConnect error 5000 "User rejected the request", except in two re-run (d) cases, iOS Transaction details and Android Authorization Entry Details, where the request expired while LogBox was being dismissed. The mock dApp's /response prints it as error, not rejected, because it only recognises thrown Error objects. That mapping predates this PR, and the same happens for a displayable request.
  • Debug builds also show React Native's full-screen LogBox "Render Error" when a boundary catches; the (d) runs dismiss it first, and the details sheet is still open with its notice behind it.

Follow-ups / out of scope

  • Reporting @gorhom/bottom-sheet's handlePortalOnUnmount early return upstream: a modal whose owner unmounts while it is still opening is left on screen. This PR no longer reaches it, but other screens could.
  • Duplicate Sentry events for an undecodable invocation node: the pre-flight and the unmemoized render can each report it.
  • The import cycle ducks/walletKit ↔ helpers/walletKitUtil. The provider test un-mocks the duck because of it.
  • The mock dApp's withAddress (CAP-71) auth-preimage variant still throws, because stellar-base 14.1.0 lacks that arm. Nothing in CI uses it. Porting the generator to SDK 17 would fix it.
  • The mock dApp's /response reports wallet rejections as error (pre-existing).

🤖 Generated with Claude Code

CassioMG and others added 22 commits October 29, 2026 22:57
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Cover the Protocol 30 SDK surface the wallet depends on: the SEP-23
muxed contract (W) strkey vector, ScAddress arm 5 round-trips, the
CAP-87 ML-DSA cost types (86-94) and the CAP-88 millisecond
StellarValue arms. All four fail against the 17.0.1 SDK.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Pin which transaction memos fold into a muxed (M...) address on stellar-sdk 17.4.0. Decimal uint64 ids fold; hex, padded, empty and out-of-range values do not. The tests run against the real SDK, with only services/backend mocked.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
fast-text-encoding, the app's global TextDecoder, throws when it is asked
for { fatal: true }. SDK 17.4.0 asks for it in its bounded-fetch adapter, so
federation addresses did not resolve and stellar.toml asset icons did not
load on device. The SDK also asks for it in XdrString.toStringStrict(), so
scValToNative returned raw bytes instead of strings for symbols and strings.

Wrap the global TextDecoder so a UTF-8 decoder built with { fatal: true }
validates its input with a WHATWG-conformant check and throws a TypeError on
malformed bytes. Every other decoder is built and used as before, and the
wrapper does nothing where TextDecoder already supports fatal, as on Node
and in Jest.

The polyfill requires fast-text-encoding itself and installs Buffer first,
as shim.js does later, so the decoder keeps decoding through Buffer.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A TextDecoder built with { fatal: true } and the default ignoreBOM now drops
one leading U+FEFF from its output, as the native decoder does.
fast-text-encoding never strips it, so a stellar.toml or federation response
that starts with a BOM failed to parse on device: smol-toml and JSON.parse
both reject the BOM.

The SDK's bounded fetch builds its decoder with the default ignoreBOM, so it
gets the stripping. XdrString.toStringStrict() and the WASM name parser pass
ignoreBOM: true and keep the BOM. A strict decoder now reports the ignoreBOM
option it honors. Non-fatal decoders are unchanged and still keep a BOM.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A TextDecoder built with { fatal: true } now reads only the window of a
DataView or non-Uint8Array typed array that byteOffset and byteLength
describe, as the native decoder does. It used to validate and decode the
whole backing buffer, as fast-text-encoding does. Every caller in the app
passes a Uint8Array, which is unchanged, so this brings the strict path in
line with the native decoder rather than changing what the app decodes.
Non-fatal decoders still hand their input straight to the base.

The UTF8_LABEL comment now says which labels the strict path treats as
UTF-8, and that the Buffer-backed base accepts only utf-8 and utf8. The
tests gain a view-window check against the native decoder, a parity check for
decode() with no input on a non-fatal decoder, and a JSDoc on the hex helper.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The decodeUtf8At comment said React Native's TextDecoder throws on fatal,
which stopped being true with the strict TextDecoder polyfill. It now says
why the function exists: the escape renderer needs one code point and its
width at an index, which a decoder of a whole buffer cannot give.

The strkey scrubbing tests add a letter before, a letter after and one more
base32 character after a W key, so the leading anchor, the trailing anchor
and the exact length are each pinned on their own. The memo folding tests
split the non-folding memos by reason: not a canonical decimal id, and not a
uint64. The SDK compatibility suite's header and top-level title now cover
SDK 17 and Protocols 27 and 30. No production code changes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@gorhom/bottom-sheet 5.2.6's handlePortalOnUnmount returns early while a
modal is still opening, because currentIndexRef only updates once an
animation completes. A details sheet whose request fails closed as it
opens was left on screen with a dead close button, and the request sheet
it minimized was never restored. Patch it to unmount such a modal, and
pin both details sheets with the real gorhom modal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stellarSdkV15.test.ts has covered more than SDK 15 since it gained the
Protocol 27 (CAP-71) block and the Protocol 30 block, so it becomes
stellarSdkCompat.test.ts, with a version-free top-level describe.

It also gains a block for the Horizon submission errors of the SDK 16+
HTTP client. SDK 16 replaced axios with fetch, and 16.1.0 / 17.0.0 made
submitTransaction reject with TransactionFailedError or BadResponseError.
The app reads those errors through isHorizonError: response.status for
the 504 retry and the log-severity split, and
response.data.extras.result_codes in the transaction builder and
useManageTokens. Every app test built that error by hand, so nothing
pinned it to the real SDK. The new tests drive the real Horizon.Server
against a stubbed fetch for a 400 with result codes, a 429 without them,
and a 504. If the SDK stops exposing response.data, the first two fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sign-auth-entry request that can't be displayed now reads "Unable to
read this authorization" / "The authorization entry could not be
decoded.", on its Cancel-only sheet and in Authorization Entry Details.
Transaction requests and the send and swap Transaction details keep the
transaction wording. UnreadableTransactionNotice takes an optional
variant, which DappRequestUnreadableContent passes through.

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

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Android Emulator preview APK is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-9a1c072efd73b33be9ce
Backend: V1 prod + V2 staging (no sandbox configured for @CassioMG). SDF collaborators only — install instructions in the release description.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

iOS Simulator preview build is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-8ce16b4121340beaf686
Backend: V1 prod + V2 staging (no sandbox configured for @CassioMG). SDF collaborators only — install instructions in the release description.

CassioMG and others added 2 commits October 30, 2026 19:15
A render error in Transaction details or Authorization Entry Details
switched the request sheet to the Cancel-only notice. That unmounted
the details button, which owned the nested sheet, so the sheet had to
close itself mid-view, and that needed a patch to @gorhom/bottom-sheet:
gorhom left a sheet that was still opening on screen when its owner
unmounted.

Now the details sheet stays open, showing its own notice, until the
user closes it, and the request underneath already offers only Cancel.
Each details component is split into its button and its sheet, and the
request sheets keep the sheet in the same keyed slot in both layouts,
so the switch no longer unmounts it. The gorhom patch is dropped.

Send's review sheet keeps the combined component, now built from the
two pieces. The details sheets' close buttons get testIDs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
17.4.0 is now older than the 7-day npmMinimalAgeGate, so the
temporary npmPreapprovedPackages entry is no longer needed.

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

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.

🔵 Needs a closer look

Defer approval because this security-sensitive change spans WalletConnect signing guards and nested native sheet/error-boundary lifecycles, requiring independent iOS and Android validation of fail-closed behavior, rejection and cancellation handling, and teardown without signing bypasses.

No open issues 🎉

1 file not reviewed
  • mock-dapp/package-lock.json: Generated file
What changed in this PR

This pull request hardens dApp signing flows by preflighting transaction and authorization display data, failing closed when requests are unreadable, and adding full muxed contract (W…) address coverage.

Changes:

  • Adds displayability checks, error boundaries, localized unreadable notices, and Cancel-only states for unsafe dApp requests.
  • Preserves nested details sheets after render failures and prevents signing when display data is unavailable.
  • Adds W address fixtures/tests and mock dApp support for SAC transfers.

Review note: the swap review still renders its “Transaction details” button when useSignTransactionDetails returns null; the button and sheet should be gated together.

This pull request requires changes.

File Summary
src/​providers/​WalletKitProvider.tsx Integrates displayability state, render-error handling, and approval blocking.
src/​i18n/​locales/​pt/​translations.json Adds Portuguese unreadable transaction and authorization copy.
src/​i18n/​locales/​en/​translations.json Adds English unreadable transaction and authorization copy.
src/​helpers/​walletKitUtil.ts Defines method-specific fail-closed request logic.
src/​helpers/​signingDisplay.ts Preflights transaction and authorization display conversions.
src/​components/​screens/​WalletKit/​DappSignTransactionBottomSheetContent.tsx Adds Cancel-only transaction requests and persistent details-sheet handling.
src/​components/​screens/​WalletKit/​DappSignAuthEntryBottomSheetContent.tsx Adds Cancel-only authorization requests and details-sheet handling.
src/​components/​screens/​WalletKit/​DappRequestUnreadableContent.tsx Renders unreadable notices with a Cancel action.
src/​components/​screens/​WalletKit/​DappRequestBottomSheetContent.tsx Propagates unreadable-request and render-error state.
src/​components/​screens/​WalletKit/​DappAuthEntryDisplay.tsx Adds error boundaries around authorization invocation bodies.
src/​components/​screens/​WalletKit/​DappAuthEntryDetailsBottomSheet.tsx Propagates authorization detail render errors.
src/​components/​screens/​WalletKit/​DappAuthEntryDetails.tsx Separates the authorization details trigger from its persistent sheet.
src/​components/​screens/​SignTransactionDetails/​types/​index.ts Adds empty, ready, and undisplayable detail states.
src/​components/​screens/​SignTransactionDetails/​SignTransactionDetails.tsx Separates the transaction details trigger from its sheet.
src/​components/​screens/​SignTransactionDetails/​index.ts Exports the new transaction detail components.
src/​components/​screens/​SignTransactionDetails/​hooks/​useSignTransactionDetails.ts Builds display state and runs transaction preflight checks.
src/​components/​screens/​SignTransactionDetails/​helpers/​detailsState.ts Extracts displayable details from the state model.
src/​components/​screens/​SignTransactionDetails/​components/​UnreadableTransactionNotice.tsx Provides transaction and authorization unreadable notices.
src/​components/​screens/​SignTransactionDetails/​components/​SignTransactionDetailsBottomSheet.tsx Adds render-error handling and fallback notices.
src/​components/​ErrorBoundary.tsx Adds reusable reporting and resettable render-error handling.
mock-dapp/​src/​routes.ts Supports generated SAC transfer signing requests.
mock-dapp/​src/​fixtures/​sacTransferToMuxedContract.ts Builds native SAC transfers to muxed contract addresses.
mock-dapp/​README.md Documents the SAC transfer fixture and Node requirement.
mock-dapp/​public/​index.html Adds the SAC transfer-to-W UI action.
mock-dapp/​package.json Adds Stellar SDK 17.4.0 and updates the Node engine.
mock-dapp/​package-lock.json Locks the new SDK and dependencies.
__tests__/​support/​sacTransferFixtures.ts Provides reusable SAC transaction and authorization fixtures.
__tests__/​providers/​WalletKitProvider.test.tsx Tests fail-closed approval behavior and reset handling.
__tests__/​helpers/​walletKitUtil.test.ts Tests method-specific unreadable-request decisions.
__tests__/​helpers/​soroban.test.ts Verifies W conversion and display-token behavior.
__tests__/​helpers/​signingDisplay.test.ts Tests display preflight coverage and reporting.
__tests__/​helpers/​signingDisplay.render.test.tsx Compares preflight results with actual rendering.
__tests__/​components/​screens/​WalletKit/​DappSignTransactionBottomSheetContent.test.tsx Tests transaction Cancel-only states and details wiring.
__tests__/​components/​screens/​WalletKit/​DappSignAuthEntryBottomSheetContent.test.tsx Tests authorization Cancel-only states and wording.
__tests__/​components/​screens/​WalletKit/​DappRequestNestedDetailsSheet.test.tsx Verifies nested details-sheet persistence after render errors.
__tests__/​components/​screens/​WalletKit/​DappAuthEntryDisplay.test.tsx Tests authorization rendering, W values, and boundary behavior.
__tests__/​components/​screens/​SignTransactionDetails/​hooks/​useSignTransactionDetails.test.ts Tests detail states, preflight failures, memoization, and fee bumps.
__tests__/​components/​screens/​SignTransactionDetails/​components/​SignTransactionDetailsBottomSheet.test.tsx Tests detail fallbacks, reporting, reset behavior, and callbacks.
__tests__/​components/​screens/​SignTransactionDetails/​components/​SignTransactionAuthorizations.test.tsx Verifies full W rendering in authorization arguments.
__tests__/​components/​screens/​SignTransactionDetails/​components/​Operations.test.tsx Verifies full W rendering under operation parameters.
__tests__/​components/​screens/​SignTransactionDetails/​components/​KeyVal.test.tsx Verifies display, expansion, and copying of W values.
__tests__/​components/​ErrorBoundary.test.tsx Covers fallback rendering, reporting, callbacks, and reset semantics.

Review effort: Lite

CassioMG and others added 4 commits November 2, 2026 15:35
Brings in b4489ad, which re-comments the stellar-sdk age-gate bypass in
.yarnrc.yml, and 9e5e0d2, which renames the SDK compatibility tests.
This branch started before both, so it still had the bypass on.

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

The scrubber tests checked a W with one extra character but not one with
a character missing: a 68-character run must stay untouched, since only a
69-character M or W is a key. buildMuxedContract's comment says it drops
leading zeros from the id; nothing asserted it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md and the dependencies guide still said Node 20, but
package.json's engines field asks for >= 22.12.0 and every workflow runs
Node 22. Prettier, run by the pre-commit hook, also re-pads the Best
Practices table in AGENTS.md, which wasn't formatted before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in 900fd03 (two missing tests: a too-short W in the scrubber,
buildMuxedContract's leading zeros) and b80ca26 (Node 22 in the docs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CassioMG
CassioMG requested a review from a team November 3, 2026 21:01
The boundary sat inside each card's expanded body. Collapsing and
re-expanding a broken card remounted it, so the same error was reported
again, and a throw outside the card bodies escaped it: the signer, a
card title, or renderDetailContent itself, which runs while the display
renders. One boundary around the display reports a broken entry once
and catches all of those. When it trips, the notice replaces the whole
entry instead of one card's body, as in the transaction details sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from cg-p30-sdk-bump to main November 6, 2026 23:18
Brings in #1034 (squash-merged as 3999960) now that this PR targets
main. package.json and yarn.lock take main's versions: this PR doesn't
change them, and main's carry #1034's later typescript-wallet-sdk-km
6.0.0 bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CassioMG
CassioMG merged commit fa85619 into main Nov 6, 2026
37 checks passed
@CassioMG
CassioMG deleted the cg-p30-signing-w branch November 6, 2026 23:48
CassioMG added a commit that referenced this pull request Nov 6, 2026
Brings in #1037 (squash-merged as fa85619).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CassioMG added a commit that referenced this pull request Nov 7, 2026
Brings in #1037 (squash-merged as fa85619). Both PRs added fixture
imports at the top of soroban.test.ts; the resolution keeps all of
them. The translation files merged cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

3 participants