Repository navigation
fix(wallets): load a wallet and list its signers with one request - #2289
Merged
albertoelias-crossmint merged 6 commits intoNov 6, 2026
Merged
Conversation
…ts key getWallet awaits signer initialization. For a wallet with a device signer, that ran after GET wallet, one step after another: load the key iframe, look up the key, then GET the signer for its status. When the key was not yet mapped to the wallet address it also re-fetched the wallet and every signer. The wallet response already carries each delegated signer exactly as GET signer returns it, so initialization now reads the status from there, and the key storage starts loading alongside the wallet request. Wallets built without a fresh response (EVMWallet.from and friends) still fetch. Measured against staging with a browser's CORS preflights: 1.39 s -> 0.51 s when the key is mapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
signers() fetched the wallet, then GET signer once per delegated signer to read its status. The wallet response already carries each signer exactly as GET signer returns it (chains on EVM, the hydrated transaction on Solana and Stellar), so map those directly. Recovery and signer lookups that call signers() now cost one request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: efa7ce3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
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 |
albertoelias-crossmint
enabled auto-merge (squash)
November 5, 2026 23:28
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
[Medium impact] The PR appears safe to merge; no actionable issues were found. Reviews (1) · Last reviewed commit: "test(wallets): stub saveSignerLocator in..." · Reviewed by Greptile |
guilleasz-crossmint
approved these changes
Nov 6, 2026
…type The factory passes the wallet response's signers through an internal args type, so the exported Wallet constructor signature is unchanged. The signer-init tests move into wallet-factory.test.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
albertoelias-crossmint
deleted the
albertoe/wallets-init-fewer-requests
branch
November 6, 2026 15:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(wallets): load a wallet and list its signers with one request
Problem
getWalletin the browser resolves only after signer initialization. That initialization fetched data the wallet response already had. After GET wallet it loaded the device-key iframe, then sent GET signer to read the device signer's status. If the key was not yet linked to the wallet address, it also sent GET wallet again plus one GET signer per delegated signer. Each extra URL also costs a CORS preflight, so on staging a page reload waited 1.3 s for the wallet, and up to 3.4 s for a device with no key.wallet.signers()had the same pattern: GET wallet, then GET signer per signer, only to read each signer's status.What this PR does
config.delegatedSignersentry in the GET wallet response is the same object GET signer returns:chainson EVM, and the signer's transaction on Solana and Stellar. I compared the two responses on staging for EVM and Stellar wallets and they were byte-identical. For Solana, the backend repository fills in each signer's transaction when it builds the wallet response.WalletFactorypasses those signers to theWallet. Signer initialization reads the device-key candidates and the signer status from them (SignerManager.assemble,DeviceRecoveryService.resolveAvailability). It keeps the same filter assigners(): only signers approved on this chain.EVMWallet.from) receive no signers, so they still fetch the status. A status from an older response is never used.getWalletcalls a new optionalDeviceSignerKeyStorage.preload()before its request. The iframe storage starts loading the iframe in it, so the iframe loads during the request instead of after it. Other storages inherit a no-op.wallet.signers()maps each signer's status from its one GET wallet response.flowchart LR subgraph Before A1[GET wallet] --> B1[load iframe] --> C1[GET signer] B1 -. key not linked .-> D1[GET wallet + GET signer x N] end subgraph After A2[GET wallet, iframe loading in parallel] --> C2[status from the response] endResult
Browser
getWalleton staging, median of 9 runs. The benchmark runs the real SDK against staging; it simulates the iframe at its measured 220 ms cold load and sends a real preflight before each new URL.*About 0.2 s high: the benchmark's second GET wallet uses a different URL than a browser would, which adds a preflight.
wallet.signers()goes from 1 + N requests to 1.What to check when reviewing
apiSignerstrust that response for the signer status during init. This holds as long as the GET wallet and GET signer serializers stay the same. If they diverge, the signer-init tests inwallet-factory.test.tsstill pass, because they mock the API.signers()test for partial GET signer failures is gone, since those requests no longer happen.saveSignerLocatorkey-storage write at the end of init. That is an iframe RPC, not an API request.Test plan
pnpm --filter @crossmint/wallets-sdk test:vitest: 780 passed.src/api/client.test.tsfails to load withoutPREVIEW_CK_KEY, same as on main.wallet-factory.test.tsand 3 of thesigners()tests. They cover a mapped key, an unmapped key, a signer awaiting approval, a signer approved only on another chain, andpreloadrunning before the request.biome checkandtsc --noEmiton the wallets package are clean.preloadbody (the wallets package has no DOM test environment), and a logged-in run in a real browser.🤖 Generated with Claude Code