Skip to content

Fix wallet approval failures from unbounded signers#1979

Open
tomas-martins-crossmint wants to merge 10 commits into
mainfrom
tomas/wal-11063-fix-wallet-approval-failures-from-unbounded-signers
Open

Fix wallet approval failures from unbounded signers#1979
tomas-martins-crossmint wants to merge 10 commits into
mainfrom
tomas/wal-11063-fix-wallet-approval-failures-from-unbounded-signers

Conversation

@tomas-martins-crossmint

@tomas-martins-crossmint tomas-martins-crossmint commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

wallet.approve() was throwing Signer not found in pending approvals for signers that were valid and locally available, just not present in the wallet's currently-assembled signers list. This hit wallets with large signer counts, where wallet.signers() was silently truncating under backend rate limits, and separately when device-signer recovery minted a new signer mid-approval after the transaction's pending-approvals snapshot had already been fetched

Changes

  • wallet.approve() now falls back to tryAssembleLocalDeviceSigner before rejecting a signer locator it doesn't recognize, instead of throwing immediately
  • tryAssembleLocalDeviceSigner (signers/index.ts) is a shared helper that checks deviceSignerKeyStorage.hasKey() and assembles a signer if found; both the new approval fallback and the existing DeviceRecoveryService recovery path use it now, they had near-identical logic inline before this
  • mapWithConcurrency, a small worker-pool helper, replaces unbounded Promise.all in wallet.signers() and in approval signer resolution, capped at RATE_LIMIT_BATCH_SIZE; unbounded parallel requests were what tripped the rate limit and caused failed lookups to be silently dropped
  • This bounds the burst, not the total: a wallet with 102 registered signers still makes 102 requests, just paced instead of bursted. Wallets with an already-inflated signer count from the underlying duplication bug stay slow to load; cleaning up those already-registered dead signers is a separate backend problem, no bulk-removal endpoint exists to do it from here
  • Signer lookup failures are logged instead of swallowed
  • Bump Kotlin's device signer module dependency (sdk-device-signer) to 1.1.0, which includes the fix for key duplication and alias mapping loss on Android Keystore storage
  • Verified live through the RN Expo demo app, Android and iOS, reproducing both the rate-limit truncation and the stale-approval-snapshot scenario against staging

…oncurrency, simplify signer resolution signature
…DeviceRecoveryService

Wallet#resolveMissingDeviceSigner and DeviceRecoveryService#findLocalDeviceSigner each independently read deviceSignerKeyStorage and called tryAssembleLocalDeviceSigner with their own try/catch. Both now delegate to a single tryResolveLocallyAvailableSigner method, so device is the only signer type this decision has to be reimplemented for if another gains it later.
…reInternal/approveTransactionInternal

Both methods ran the same mapWithConcurrency + resolveApprovalSigner wrapper around a different sign call. Extracted collectApprovals so each call site only supplies the differing signMessage/signTransaction logic.
@changeset-bot

changeset-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 29945cf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@crossmint/wallets-sdk Minor
@crossmint/wallets-quickstart-devkit Patch
@crossmint/wallets-playground-react Patch
@crossmint/client-sdk-react-base Patch
@crossmint/client-sdk-react-native-ui Patch
@crossmint/client-sdk-react-ui Patch
@crossmint/wallets-playground-expo Patch
@crossmint/auth-ssr-nextjs-demo Patch
@crossmint/client-sdk-nextjs-starter Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…allet-approval-failures-from-unbounded-signers

# Conflicts:
#	packages/wallets/src/utils/constants.ts
#	packages/wallets/src/wallets/wallet.ts
@tomas-martins-crossmint
tomas-martins-crossmint marked this pull request as ready for review July 16, 2026 22:15
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
.changeset/fix-wallet-approval-unbounded-signers.md:1-3
The changeset declares `minor` for `@crossmint/wallets-sdk`, but all changes in this PR are bug fixes with no new public API surface — the correct semver bump is `patch`.

```suggestion
---
"@crossmint/wallets-sdk": patch
---
```

### Issue 2 of 2
packages/wallets/src/wallets/wallet.ts:1162
`collectApprovals` uses the `private` keyword while the two new helpers directly above it (`#resolveMissingDeviceSigner`, `#resolveApprovalSigner`) use `#`-syntax private fields. The rest of the class consistently uses `#` for private members. Note: applying this suggestion also requires updating the `this.collectApprovals(...)` call sites in `approveSignatureInternal` and `approveTransactionInternal` to `this.#collectApprovals(...)`.

```suggestion
    async #collectApprovals<P extends { signer: { locator: string }; message: string }>(
```

Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread .changeset/fix-wallet-approval-unbounded-signers.md
Comment thread packages/wallets/src/wallets/wallet.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "fix(wallets): use # private syntax for c..." | Re-trigger Greptile

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.

1 participant