Repository navigation
feat(wallets): approve as a quorum member (EVM + Stellar quorum E1) - #2255
Open
daniil-dovgal wants to merge 21 commits into
Open
daniil-dovgal wants to merge 21 commits into
daniil-dovgal wants to merge 21 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ingApprovalError and the signature outcome Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ips quorum entries; 422 on transaction approval re-reads Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…changeset Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…member; api-key wallets sign only quorum entries; re-read only an already-complete 422 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…API; a legacy-derived server member signs with its key; changeset wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ery key's flat entry next to a quorum entry; nest test context Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 2511db4 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 |
daniil-dovgal
marked this pull request as ready for review
October 29, 2026 04:54
Contributor
[Critical impact] The PR appears safe to merge. Reviews (1) · Last reviewed commit: "fix(wallets): export TransactionAwaiting..." · Reviewed by Greptile |
…: EVM turns on supportsQuorumApprovals; Solana and Stellar keep the guard Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… unsigned, as prepareOnly does
send and EVM / Solana / Stellar sendTransaction resolve the signer once; when it is a quorum: locator the
call returns { transactionId } with hash and explorerLink undefined instead of signing and waiting (EDD 007
§7 "A quorum forces prepareOnly"). Types are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n over the early return, real server derivation Checker round 1: a client holding one of the quorum's member keys signs nothing; a creation error with a quorum signer throws as with any other signer; the server signer row derives its locator instead of stubbing it. The changeset names both errors the old call threw. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…action and the signature calls did EVM / Solana / Stellar sendTransaction and signMessage / signTypedData treated any truthy prepareOnly as set; only send required `=== true`. The shared helper now follows the majority, so an untyped caller passing a non-boolean never gets a signing call it did not expect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… send covered on a Stellar wallet Checker round 2: approve still throws for quorum members until E1, so the JSDoc and changeset no longer promise it; Solana's API rejects a quorum signer at creation, so its JSDoc drops the note and the changeset scopes to EVM and Stellar. The spec runs send on a Stellar wallet too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nly as truthy Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings m0d's changeset wording with the exact error codes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-only Brings the foundation audit fixes from m0a–m0d into m0f. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
m0f removed the isQuorumSigner import that E1's member lookup still uses; restored. send() as a quorum now returns early unsigned (m0f), so E1's send-as-quorum test signs the member through a later approve() instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ar members approve EDD 007 §7 "Approval" (crossbit-main #33357): while only quorum members can still approve, approve() returns the transaction without hash/explorerLink, or the signature without signature, instead of throwing. A wait on a non-quorum signer still throws TransactionAwaitingApprovalError. - The awaiting outcome no longer builds quorum progress, so an unreadable quorum entry cannot throw a plain Error (audit finding 11). SignatureAwaitingApprovalError, the error's quorums, toQuorumProgress and QuorumApprovalProgress go (unreleased). - Stellar turns on supportsQuorumApprovals: held members sign each auth entry's message (passkeys as hex of the 32 bytes) in one approvals request. - Stellar upgrade() returns phase 1 without a hash when a quorum must still approve it, and creates no migrate transaction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bers still sign With approve-and-wait returning instead of throwing on a quorum-only wait, completeSignerRegistration reported a resumed registration as added. It now returns the signer with its transactionId / signatureId and its own status, as prepareOnly does. The operation-id shape is shared with the prepareOnly branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Solana wallet with an api-key signer leaves quorum entries to the API; when only a quorum can still approve, approve() returns the transaction without a hash, as on every other chain. Unreachable while the API rejects Solana quorums. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Stellar approve: when another member completes the quorum first, approve() returns the landed transaction the API client reads back after the 422. - settle() moves to the test helpers; the approve, prepare-only and Stellar suites use it instead of their own copies. - The two-quorum rejection test names what the API rejects: a member shared by two different quorums (two entries of one quorum are accepted). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n a helper
- completeSignerRegistration builds the operation id, the prepared shape and the
added shape once each; prepareOnly and a quorum-only wait share the prepared
return.
- #heldSigners replaces the ternary that mixed a call with an array literal.
- The addSigner resume rows pin the signer's own status ("awaiting-approval").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open
4 of 5 tasks
4 of 5 tasks
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
First milestone of the TS SDK quorum stack, stacked on #2268. The foundation: #2214 reads quorum entries, #2215 adds the rules and errors, #2233 the signer seams, #2225 approval collection and the quorum-only poll outcome, and #2268 makes a quorum signer return
{ transactionId }unsigned.This PR lets a client approve as a quorum member on EVM and Stellar, per EDD 007 §7 "Approval" and "Role" (crossbit-main #33357, merged):
approvesigns every pending member this client holds, each over its own message: transactions on both chains, signatures (ERC-1271) on EVM.approvereturns the request withouthash(orsignature) instead of throwing.useSigneraccepts a key that is only a quorum member.Before, any quorum entry threw
QuorumSignerNotSupportedError, anduseSignerrefused member keys. Wallets without quorums behave as before. Solana keeps the guard: it has no quorums.Details
wallets/wallet.tsapproval paths: when a quorum entry is pending, the held keys come fromheldSigners()(feat(wallets): acting-signer seam, held signers and quorum API errors at call sites (quorum foundation 3/4) #2233), and every held pending member signs (§7 "never counts the threshold"; the API skips extras). If nothing held is pending, no request is sent. An api-key wallet leaves its flat entries to the API, as today.chains/adapters/stellar.ts:supportsQuorumApprovalsis on. The API sends one quorum entry per auth entry, so a member signs once per entry, all in one request; the API rejects only a member shared by two different quorums. A passkey member signs the hex of the 32-byte message, one prompt after another.wallets/services/operation-poller.ts: a quorum-only wait returns{ transactionId }withhash/explorerLinkundefined, or{ signatureId }withsignatureundefined. The types stayTransaction<false>/Signature<false>(EDD "Type honesty"), so callers checkhash == null. A wait on any other signer still throwsTransactionAwaitingApprovalError. No quorum progress is built any more, so an unreadable entry can't throw a plainError(audit finding 11).SignatureAwaitingApprovalError, the error'squorumsandQuorumApprovalProgressare gone; none was released.addSignerresuming a registration that quorum members must still approve returns the signer with itstransactionId/signatureIdand its own status, asprepareOnlydoes. Stellarupgrade()returns phase 1 without a hash and creates no migrate transaction, since the wallet is not locked yet.useSignermember keys (wallet.ts,signers/server/resolver.ts): a key resolves as a registered signer, then a recovery method, then a quorum member. A member takes its quorum's status, without thegetSignerlookup that 404s for members. A passkey member needs itsid.api/client.tsapproveTransaction: when two members race for the last slot, the API answers 422 "Already has the required number of approvals"; the client re-reads the transaction and goes on to its outcome. Every other 422 is returned unchanged.Next Steps
E2 (#2256): a member-only active signer acts as its quorum and returns early, and EVM
signMessage/signTypedDatatakesigner. E3 (#2261): signer changes approved by a quorum.Accepted:
removeSigner, the recovery-method calls and device recovery still treat any return of the wait as landed. They approve with a flat recovery signer here, so they can't reach a quorum-only wait; E3 and E5 keep that true.upgrade/migratewith a quorumsignersign held members (feat(wallets): a quorum acting signer returns the created transaction unsigned, as prepareOnly does (quorum foundation 5/5) #2268 left them out, as the EDD does). Unreachable: quorum proofs need a v2 contract, and v2 has no upgrade target.Test plan
quorumson the outcome).wallet.quorum-approve.test.ts: the rules above on transactions and signatures, api-key wallets,addSignerresume, and a Stellar block (two auth entries, passkey and email members, others pending, expiry on submit and while waiting, the last-slot race, a flat entry not held).wallet.quorum-member-signer.test.ts: every member type in recovery and delegated quorums.operation-poller.test.ts(incl. finding 11),stellar.test.ts(upgrade/migrate),chain-adapter.test.ts,resolver.test.ts, andapi/client.test.ts(422 rows; CI only, needsPREVIEW_CK_KEY).cd packages/wallets && pnpm exec vitest run src/wallets src/quorum src/utils src/chains src/signers→ 1139 passed.tsconsrc/is clean; biome reports no errors.addSignerand Solana rows are mutation-checked.Package updates
@crossmint/wallets-sdk: minor (.changeset/quorum-approve-as-member.md).