Repository navigation
feat(wallets): a member-only key acts as its quorum (EVM + Stellar quorum E2) - #2256
Open
daniil-dovgal wants to merge 14 commits into
Open
daniil-dovgal wants to merge 14 commits into
daniil-dovgal wants to merge 14 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; signer option on EVM signatures Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t the marker clear, non-member reads on every chain, a failed lookup Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… uses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h config; test address case, two delegated quorums, later recovery changes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… helper returns no recovery methods when the API lists none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 2840a07 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 |
…o daniil/ts-quorum-e2-act-as-quorum
daniil-dovgal
marked this pull request as ready for review
October 29, 2026 06:29
Contributor
[Medium impact] The change affects request creation for member-only keys across wallet chains and EVM signatures. Fix the cross-chain registration check before merging so member-only keys reliably act as their quorum. Reviews (1) · Last reviewed commit: "Merge remote-tracking branch 'origin/dan..." · Reviewed by Greptile |
… acts as its quorum The role read counted any delegated signer with the key's locator as registered. EVM signers list the chains they are registered on, so a key registered on Polygon only sent itself on Base instead of its quorum. isSignerOnChain now holds that rule for both the role read and mapApiSignerToSigner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 29, 2026
Conflicts: m0f resolves the signer once in each public method and passes it to
createTransaction; E2 made resolveSignerParams async. Each public method now awaits
it once, so isPrepareOnly sees the inferred quorum.
Fallout in wallet.quorum-acting-role.test.ts:
- a member-only key's send returns unsigned as its quorum (m0f), nothing signed;
- a quorum-only signature wait returns { signatureId, signature: undefined } (E1);
- the shared settle helper replaces the local copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Only
EDD 007 §7 "A quorum forces prepareOnly": signMessage and signTypedData resolve
the signer once and return { signatureId, signature: undefined } when it is a
quorum, named in `signer` or inferred from a key that is only a member. Members
sign later with approve().
The acting-role suite now asserts the early return without prepareOnly for the
inferred quorum on send, EVM and Stellar sendTransaction, and both signature calls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 5, 2026
Open
…ter with approve - Each creating call (send, EVM sendTransaction, signMessage, signTypedData) with a member-only key reads the wallet once: a second read that finds the key registered must not turn the quorum request into a flat one. Mutation-checked. - A member-only key signs its member entry of a send and of a signature made as its quorum through a later approve() (EDD 007 §7 use case 2). - JSDoc on send / sendTransaction names the inferred quorum too. - createAwaitingRequest joins the shared test helpers; a duplicate signature row goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The one-read table gains Stellar sendTransaction (mutation-checked) and types settle<unknown>, so the file type-checks with the test tsconfig. - The one-read and approve() rows move into the creating-call describe as "when" contexts and reuse its setup; UNSIGNED_TRANSACTION / UNSIGNED_SIGNATURE replace the repeated literals. - signMessage JSDoc and the changeset say to pass prepareOnly: true when a quorum may act, so the return type matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-call input - UNSIGNED_TRANSACTION / UNSIGNED_SIGNATURE join the test helpers; E1's suite keeps its AWAITING_* names as aliases. - The acting-role suite uses one STELLAR_CONTRACT_CALL input. - signTypedData JSDoc says to pass prepareOnly: true when a quorum may act. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…est's chain signTypedData creates its request on params.chain, which can differ from the wallet object's chain. The role read now checks registration on that chain, so a key registered there acts as itself (EDD 007 §7 Role row 1), and a key registered only on the wallet's chain acts as its quorum. Other calls keep the wallet's chain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
Second milestone of the TS SDK quorum stack, stacked on #2255 (E1).
{ transactionId }unsigned, as withprepareOnly: true.approvereturns without a hash while other members must still sign, anduseSigneraccepts a key that is only a member.Before this PR, creating calls always sent the active key as the request's
signer. A member-only key therefore got the API'sSIGNER_IS_QUORUM_MEMBER, andsignMessage/signTypedDatahad no way to name a quorum.This PR implements EDD 007 §7 "Role" and "A quorum forces
prepareOnly" for creating calls:QuorumSelectionAmbiguousError.prepareOnly: true. Members sign it later withapprove.signMessage/signTypedDatatake asigneroption, likesend.Other keys behave as before, and make no extra request.
Details
wallets/wallet.tsresolveSignerParams(the feat(wallets): acting-signer seam, held signers and quorum API errors at call sites (quorum foundation 3/4) #2233 seam, now async): an explicitsigneris sent as given. Otherwise the active key is sent, unlessuseSignerselected it as a member-only key (matched by locator). For such a key it reads the wallet's config with onegetWalletcall and runs feat(wallets): quorum rules and quorum API errors (quorum foundation 2/3) #2215'sresolveActingRole(recovery and delegated quorums): the key's quorum, the ambiguous error before any request, or the key itself when it has since become a registered signer (on the request's chain, whichsignTypedDatamay set) or recovery method, or left its quorum. A failed read, or one with no usable config, throwsWalletNotAvailableError. Only member-only keys pay for the read.isPrepareOnly. Sosend,sendTransaction(every chain),signMessageandsignTypedDatareturn early for an inferred quorum too, and the role is read once per call.useSignerremembers that locator only for a member key; any other selection clears it. Keys swapped in by device recovery orwithRecoverySignerdon't match it. Chain wallets (EVMWallet.from, …) carry the locator through the constructor.wallets/evm.ts/wallets/types.ts:SignatureInputOptionsgains optionalsigner?: SignerOption. A quorum locator requests an ERC-1271 signature as that quorum and returns{ signatureId }withsignatureundefined.upgrade/migrateare unchanged: without an explicitsignerthey send none, and the server uses the admin signer.Accepted:
preAuthIfNeeded, so an OTP can run first. preAuth can swap the active key, so the role is resolved for the key that actually signs.prepareOnly, not the signer (EDD "Type honesty"): passprepareOnly: truewhen a quorum may act.Next Steps
E3 (#2261): signer changes approved by a quorum. The four signer-change calls take
signer(sent asapprover) and return early when it is a quorum, anduseRecoveryMethodaccepts a member of a recovery quorum.Test plan
isPrepareOnly.wallets/wallet.quorum-acting-role.test.ts(41 tests):send, EVM and StellarsendTransaction,signMessageandsignTypedDataact as a delegated or recovery quorum and return unsigned, with and withoutprepareOnly; one wallet read per call; registration onsignTypedData's chain; the member key signs later throughapprove; ambiguity, explicitsigner, membership changed afteruseSigner, address case, failed or config-less reads, an uninstalled quorum; no read for other keys on every chain; Stellarupgradeunchanged.wallets/wallet.signer-params.test.ts: the EVM signature calls join feat(wallets): acting-signer seam, held signers and quorum API errors at call sites (quorum foundation 3/4) #2233's explicit-signer rows.wallets/wallet.quorum-member-signer.test.ts,utils/recovery.test.ts.signers()read, cached recovery, exact-case match, first delegated quorum only, a second role read before the early return): each fails its rows.cd packages/wallets && pnpm exec vitest run src/wallets src/quorum src/utils src/chains src/signers→ 1196 passed.tsconsrc/is clean, the changed test files type-check with a test tsconfig, and biome reports no errors.signTypedData's chain).Package updates
@crossmint/wallets-sdk: minor (.changeset/quorum-act-as-quorum.md).