Skip to content

fix(transaction): purge persisted signed intents when wallet identity changes (#964) - #970

Merged
greatest0fallt1me merged 1 commit into
Predictify-org:mainfrom
AKAEMM668:fix/964-purge-signed-intents
Sep 29, 2026
Merged

greatest0fallt1me merged 1 commit into
Predictify-org:mainfrom
AKAEMM668:fix/964-purge-signed-intents

Conversation

@AKAEMM668

Copy link
Copy Markdown
Contributor

Overview

Signed transaction intents were persisted to localStorage (predictify:intents:v1) for up to 24 hours with no shape validation, and the wallet lifecycle never removed them. A fully signed envelope could therefore linger on a shared device or remain reachable by any script on the origin after the wallet was disconnected or switched, and malformed/tampered records could crash useTransaction's resume path.

This PR hardens the intent store (zod validation + eager pruning), drops the signed envelope as soon as a submission hash exists, and purges intents when the wallet identity changes.

Related Issue

Closes #964

Changes

Intent store hardening — lib/transaction/intent.ts

  • [ADD] intentRecordSchema (zod) validating every field; status is constrained to the IntentStatus enum and walletAddress/key/xdrHash must be strings.
  • [MODIFY] safeGetStorage now runs every parsed record through sanitizeStore, discarding malformed or wrongly typed entries and pruning anything older than the 24h TTL, then persists the cleaned map on the first read.
  • [MODIFY] upsertIntent refuses to persist an invalid merged record and never carries signedXdr forward once submissionHash is set.
  • [ADD] clearIntentsForWallet(address) removes only the records belonging to the given address.

Wallet identity lifecycle — context/WalletContext.tsx

  • [MODIFY] updateIdentity purges the previous address's intents whenever the identity actually changes (covers account switch).
  • [MODIFY] disconnectWallet purges the disconnected address only after getKit().disconnect() succeeds.
  • [MODIFY] the reconciliation mismatch path purges intents for the persisted address before clearing persisted wallet state.

Tests

  • [MODIFY] lib/transaction/__tests__/intent.test.ts — validation drops, TTL pruning on first read, signedXdr removal, and per-wallet clearing.
  • [MODIFY] context/__tests__/WalletContext.test.tsx — intents purged on disconnect, on identity switch, and on reconciliation mismatch.

Security & failure modes

  • The signed envelope (signedXdr) is only retained for the short window between signing and submission; it is removed the moment submissionHash is set and stripped from legacy records on read.
  • Validation is fail-closed: an unparseable, mistyped, or unknown-status record is dropped rather than trusted, so the useTransaction resume path can no longer read unchecked fields.
  • A partially failed disconnect (getKit().disconnect() throws) leaves the identity and its intents intact, preserving the ability to recover the session.
  • Storage read/write failures are still swallowed and logged at debug, so a broken localStorage cannot break the wallet UI.

Verification Results

pnpm test lib/transaction context/__tests__ hooks/__tests__/useTransaction.test.tsx

PASS lib/transaction/__tests__/intent.test.ts
PASS hooks/__tests__/useTransaction.test.tsx
PASS context/__tests__/WalletContext.test.tsx
Test Suites: 3 passed, 3 total
Tests:       26 passed, 26 total

pnpm exec eslint is clean on all four changed files and tsc reports no errors in them.

Note: the issue's validation command also matches hooks/__tests__/useTransaction.hook.test.tsx, which is already failing on untouched main. That suite is stale from PR #924 and asserts a removed API (retryTransaction/canRetry); it is unrelated to this issue and was intentionally left untouched to keep this PR scoped.

Acceptance Criteria

Acceptance Criteria Status
Disconnecting the wallet removes every intent for that address from localStorage ✅ clearIntentsForWallet called on successful disconnect; covered in WalletContext.test.tsx
Records with a missing or wrongly typed status or walletAddress are discarded on read ✅ zod schema + sanitizeStore; covered in intent.test.ts
signedXdr is removed once submissionHash is set ✅ enforced in upsertIntent and stripped on read; covered in intent.test.ts
Expired intents are pruned on the first read after load ✅ safeGetStorage prunes and persists; covered in intent.test.ts
lib/transaction/__tests__/intent.test.ts and context/__tests__/WalletContext.test.tsx cover these behaviours ✅ 24 focused tests across the two suites

…dictify-org#964)

- Validate stored intent records with a zod schema and drop malformed entries
- Prune expired intents on read and persist the cleanup
- Add clearIntentsForWallet and call it on disconnect, identity switch, and reconciliation mismatch
- Drop signedXdr once a submissionHash exists
@drips-wave

drips-wave Bot commented Sep 29, 2026

Copy link
Copy Markdown

@AKAEMM668 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

@vercel

vercel Bot commented Sep 29, 2026

Copy link
Copy Markdown

@AKAEMM668 is attempting to deploy a commit to the Jagadeesh B's projects Team on Vercel.

A member of the Team first needs to authorize it.

@greatest0fallt1me
greatest0fallt1me merged commit 9906eb5 into Predictify-org:main Sep 29, 2026
1 check failed
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.

Purge persisted signed intents when wallet identity changes

2 participants