Repository navigation
feat(wallets): signer changes approved by a recovery quorum (EVM quorum E3) - #2261
Draft
daniil-dovgal wants to merge 7 commits into
Draft
daniil-dovgal wants to merge 7 commits into
daniil-dovgal wants to merge 7 commits into
Conversation
…(red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on the four calls; useRecoveryMethod selects a member Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ay held; a member that became a recovery method approves as itself; explicit server secret resolved; no key held fails first Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m' into daniil/ts-quorum-e3-signer-changes
…ecovery method stands in for a missing active key
- withApprover takes one request ({ explicit, ignoreMember }); device recovery passes ignoreMember instead of
clearing the shared member selection, so a concurrent signer change keeps its quorum approver.
- With no active key, the selected recovery method, else the selected member, signs as the active one.
- Spec reworked for the per-chain guard from the E2 merge: signing rows run on EVM; Stellar asserts the quorum
approver and QuorumSignerNotSupportedError. Adds recover() selection rows and the server-member derivation row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m' into daniil/ts-quorum-e3-signer-changes
…ain wallets keep the member selection - approve(): with no active signer, the selected recovery method, else the selected member, signs (EDD 007 section 7 use case 6). preAuthIfNeeded splits into recover and authenticate so the stand-in starts after recovery. - EVMWallet / SolanaWallet / StellarWallet.from forward the member selection, as they forward E2's member marker. - The no-key error names how to hold a key for any explicit approver, not only a quorum. - JSDoc for the four signer-change calls and useRecoveryMethod describes quorum approvers. - Tests pin the api-key path on Stellar (the quorum entry is left to the API: awaiting error) and the stale device removal during recover() with a member selected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: fd96962 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 |
This was referenced Nov 5, 2026
Open
This branch has not been deployed
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.
Description
High level implementation overview
Third EVM milestone of the TS SDK quorum stack, stacked on #2256. The foundation (#2214, #2215, #2233, #2225) adds the quorum types, rules, signer seams and approval polling. E1 (#2255) lets a client approve as a member, and E2 (#2256) lets a member-only key act as its quorum on creating calls. Until m0f settles the public result type, "other members must still sign" arrives as E1's awaiting errors.
Before this PR, every signer change sent one recovery method as
approver(withRecoverySigner), so a recovery quorum could not approve, anduseRecoveryMethodrefused member-only keys. This PR implements EDD 007 §7 "Role" for signer changes (use cases 6 and 7): the four calls take asigneroption, sent asapprover;useRecoveryMethod(member)picks the member's recovery quorum; E1's approval loop signs the held member.flowchart LR A["addSigner / removeSigner /<br/>add / removeRecoveryMethod"] --> B["withApprover"] B --> C{"signer option?"} C -- "a recovery method" --> D["that method signs (as today)"] C -- "other (quorum)" --> E["approver as given;<br/>held keys sign (E1)"] C -- no --> F{"member selected?"} F -- yes --> G["its recovery quorum<br/>(or ambiguous error)"] F -- no --> H["withRecoverySigner (today)"]Details
wallets/services/signer-manager.tswithApprover({ explicit, ignoreMember }, operation)replaceswithRecoverySignerin the four calls:useRecoveryMethodwould (a server one given by its secret too; the secret moves into the resolver).RecoveryMethodRequiredErrorbefore any request.resolveActingRole(signer-change scope): its recovery quorum,QuorumSelectionAmbiguousError, or itself once it is a recovery method.withRecoverySigner.useRecoveryMethod(wallet.ts): a member of a recovery quorum that is not a recovery method itself is stored as a member selection (a server member keeps only its address).approvesigns its entries, and with no active key the selected key stands in, so use case 6 works on Bob's device as written. The chain wallets'from()carries the selection over, as it carries E2's marker.ignoreMember, so it keeps the recovery method as approver until E5. Otherwise the install would wait on the quorum, and recovery would delete the new device key. Other calls keep the selection, also while recovery runs.supportsQuorumApprovalsguard. A pending quorum entry there throwsQuorumSignerNotSupportedError; an api-key signer leaves it to the API as m0d does, so it gets the awaiting error. Signing held members is EVM only.RecoveryMethodRequiredError, which says how to choose.wallets/types.ts: the four option types gain an optionalsigner;prepareOnlystays required, as onsend.Accepted:
signeris sent as given. The API rejects it, because only recovery entries can approve.useRecoveryMethodtoo (as E1 does inuseSigner). Workaround:signer: <quorum>. A server member on its legacy derivation isn't matched either.from()already dropped a selected recovery method before this PR; that gets its own ticket. A server member it forwards can't sign on the chain wallet, whose server-key resolver starts empty (as for server recovery methods today).Next Steps
E4: create and manage quorums, on
createWalletand as the thing the four calls add or remove. EVMaddRecoveryMethod/removeRecoveryMethodwork once #2219 lands onmainand is merged into the stack.Test plan
signerwas ignored,useRecoveryMethodrefused members, and quorum-only wallets threwQuorumSignerNotSupportedError.wallets/wallet.quorum-signer-changes.test.ts(new, 153 tests): approver choice on every chain; on EVM, which keys sign, landing or awaiting, Bob approving with and without an active key, resume,prepareOnly; on Stellar, the guard and the api-key path;from()on each chain.wallets/wallet.quorum-member-signer.test.ts: every member type, an explicit server secret, a server member signing.wallets/wallet.test.ts:recover()with a selected member (device install and stale-device removal approved by the recovery method, device key kept, selection kept).wallets/services/signer-manager.test.ts: quorum-only rows expectRecoveryMethodRequiredError.cd packages/wallets && pnpm exec vitest run src/wallets src/quorum src/utils src/chains src/signers→ 1252 passed.tsconsrc/is clean, the changed tests type-check, and biome reports no errors.approvewithout an active key, andfrom()losing the selection.Package updates
@crossmint/wallets-sdk: minor (.changeset/quorum-signer-changes.md).