feat(cli): multisig support — N-of-M wallets + token operations#85
Open
Zhaiyuqing2003 wants to merge 43 commits into
Open
feat(cli): multisig support — N-of-M wallets + token operations#85Zhaiyuqing2003 wants to merge 43 commits into
Zhaiyuqing2003 wants to merge 43 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds multisig wallet support to the TypeScript SDK/CLI and extends the CLI’s transaction flow so token operations can run through either single-signer or multisig accounts. This fits the repo’s goal of making fast-cli the primary TypeScript interface for account, transfer, and token workflows.
Changes:
- Adds
MultiSigSignerplus multisig address/signing helpers to@fastxyz/sdk. - Migrates the CLI account model to
single | multisig, adds multisig resolver/pipeline services, and introducesfast multisig+fast tokencommand groups. - Adds migrations, tests, docs, and release metadata for the new multisig/token surface.
Reviewed changes
Copilot reviewed 48 out of 49 changed files in this pull request and generated 23 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/fast-sdk/tests/unit/multisig-signer.test.ts | New SDK unit tests for multisig derivation and signing. |
| packages/fast-sdk/tests/unit/fixtures/multisig-addresses.json | Rust-derived multisig address fixtures. |
| packages/fast-sdk/src/interface/multisig-signer.ts | New multisig signer implementation and helpers. |
| packages/fast-sdk/src/index.ts | Re-exports multisig SDK APIs. |
| docs/superpowers/specs/2026-05-04-multisig-in-fast-cli-design.md | Design/spec document for multisig CLI/SDK work. |
| app/cli/vitest.config.ts | Adds CLI Vitest configuration. |
| app/cli/tests/unit/tx-pipeline.test.ts | Unit tests for shared submit pipeline. |
| app/cli/tests/unit/signer-resolver.test.ts | Unit tests for single vs multisig signer resolution. |
| app/cli/tests/unit/multisig-wallet-schema.test.ts | Unit tests for multisig wallet config parsing. |
| app/cli/tests/unit/account-store-multisig.test.ts | Unit test for multisig account persistence. |
| app/cli/tests/integration/token-operations.test.ts | Service-level token flow tests. |
| app/cli/tests/integration/multisig-happy-path.test.ts | Service-level multisig flow tests. |
| app/cli/src/services/tx-pipeline.ts | Shared build/sign/submit helper for token ops. |
| app/cli/src/services/storage/history.ts | Broadens history entry type handling. |
| app/cli/src/services/storage/account.ts | Converts account storage to tagged union and adds multisig persistence/export rules. |
| app/cli/src/services/signer-resolver.ts | Resolves account rows into Signer or MultiSigSigner. |
| app/cli/src/services/api/fast.ts | Adds pending-multisig RPC wrapper. |
| app/cli/src/schemas/multisig-wallet.ts | Defines multisig wallet JSON schema/helpers. |
| app/cli/src/schemas/history.ts | Expands history types for token operations. |
| app/cli/src/main.ts | Adds command validation/help coverage for multisig and token commands. |
| app/cli/src/errors/index.ts | Wires new multisig/account errors into CLI error union. |
| app/cli/src/errors/account.ts | Adds multisig/account-specific tagged errors. |
| app/cli/src/db/schema.ts | Updates account table shape for multisig storage. |
| app/cli/src/commands/token/mint.ts | New token mint command. |
| app/cli/src/commands/token/manage.ts | New token manage command. |
| app/cli/src/commands/token/index.ts | Exports token command set. |
| app/cli/src/commands/token/create.ts | New token create command. |
| app/cli/src/commands/token/burn.ts | New token burn command. |
| app/cli/src/commands/send.ts | Makes Fast→Fast send path multisig-aware. |
| app/cli/src/commands/pay.ts | Restricts pay to single-signer accounts. |
| app/cli/src/commands/multisig/vote.ts | New multisig vote command. |
| app/cli/src/commands/multisig/pending.ts | New pending multisig listing command. |
| app/cli/src/commands/multisig/init.ts | New multisig wallet init command. |
| app/cli/src/commands/multisig/index.ts | Exports multisig command set. |
| app/cli/src/commands/multisig/import.ts | New multisig import command. |
| app/cli/src/commands/multisig/export.ts | New multisig export command. |
| app/cli/src/commands/info/balance.ts | Adjusts balance output for multisig accounts. |
| app/cli/src/commands/index.ts | Registers new command handlers. |
| app/cli/src/commands/fund/crypto.ts | Restricts crypto funding to single-signer accounts. |
| app/cli/src/commands/account/list.ts | Adds account kind output. |
| app/cli/src/commands/account/export.ts | Blocks exporting multisig rows as private keys. |
| app/cli/src/cli.ts | Adds parsers for multisig/token CLI commands and send --as. |
| app/cli/package.json | Adds CLI test script. |
| app/cli/drizzle/meta/0001_snapshot.json | Drizzle snapshot for account schema update. |
| app/cli/drizzle/meta/_journal.json | Registers new migration in drizzle journal. |
| app/cli/drizzle/0001_bumpy_komodo.sql | Migrates accounts table to tagged union shape. |
| .changeset/multisig-signer.md | Release note for SDK/CLI multisig changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
10 tasks
Replaces the standalone Rust fastset-multisig-cli by adding multisig support to fast-cli, backed by a new MultiSigSigner abstraction in fast-sdk. Polymorphic operations (send, token create/mint/burn/manage) work for both single-signer and multisig accounts; multisig coordination commands (init, import, export, pending, vote) live under a dedicated namespace. Two-phase implementation: backbone first, token operations second. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21 tasks covering: SDK MultiSigSigner + helpers, tagged-union accounts schema migration, signer-resolver service, multisig init/import/export/pending/vote commands, polymorphic send, and a 2-of-3 happy-path integration test. Phase 2 (token operations) gets a separate plan once Phase 1 lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous Task 4 implementation embedded the snake_case BCS-input config into the camelCase + branded SignatureOrMultiSig.Type slot. Worked at runtime but failed tsc. - Brand pubkey, signature, authorized_signers, quorum, and nonce via Schema.decodeUnknown of *FromInput schemas. - Construct the camelCase config (authorizedSigners) at the envelope boundary; the BCS-input MultiSigConfig type is unchanged. - Drop redundant ensureValid() call; getSignerPublicKey already does it. - Hoist Signer/TransactionBuilder imports in the test from dynamic imports to a static top-of-file block. - Re-cast versioned tx after sender swap in test (spread loses brand). - Add byte-equivalence assertion: a single-signer Signer.signTypedData over the same versioned tx must produce the same 64-byte signature as the multisig partial. Locks in the cross-signer aggregation invariant proxy aggregation depends on.
Add 6 typed multisig error classes (MultiSigConfigInvalidError, NotAMemberError, AmbiguousMemberError, AddressDerivationMismatchError, AlreadyVotedError, WalletKindMismatchError), extend the ClientError union, push the multisig-rejection check into the storage-layer exportAccount, and replace the Task 9 placeholder InvalidUsageError multisig refusals in pay, send, fund/crypto, and account/export with WalletKindMismatchError so error codes are stable and structured. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Fast→Fast route now dispatches on account kind via resolveSigner. Single-signer flows continue to use TransactionBuilder unchanged; multisig flows initiate via MultiSigSigner.signTransaction and surface the IncompleteMultiSig submit response with a partial-signature notice (no history record until quorum is reached). The multisig refusal guard moves to the EVM-bridging branches only, since those routes still need a single-signer seed for EVM signing. A new --as flag lets multisig users pick a member when more than one local single-signer matches the wallet config.
multisigInitParser and multisigImportParser each declared a local --network flag that collided with the same option in globalOptions, which caused the parser merger to throw DuplicateOptionError on every CLI invocation. Both handlers fall back to config.network (the global flag's destination) instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final-review polish: the existing changeset only bumped @fastxyz/sdk even though @fastxyz/cli also gains the multisig command group + account schema migration. The export subcommand parser also lacked description fields on its NAME argument and --out option, leaving blank rows in --help output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six tasks: extract submitOperation pipeline helper from send.ts and add fast token create/mint/burn/manage commands plus an integration test. Each command is polymorphic (single + multisig) by routing through the helper. Phase 1's signer-resolver, error taxonomy, and SDK MultiSigSigner carry over unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document that JoinCommittee/ChangeCommittee aren't reachable via the single-signer TransactionBuilder dispatch (multisig path bypasses the switch and hands operations directly to signTransaction). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `fast token create --name <s> --decimals <n> --initial-supply <amt> [--minters <addr,...>] [--memo <s>] [--as <name>]`. Polymorphic on the active account kind via submitOperation: single-signer accounts submit on-chain, multisig accounts return an incomplete-multisig partial that cosigners can pick up via `fast multisig pending` / `multisig vote`. Active account becomes the token admin. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The validator expects the operation to carry the token's CURRENT updateId (it increments after settlement), not the next value. The plan's original guess (current + 1n) was wrong — confirmed against fastset-multisig-cli/src/main.rs and fastset validator_tests.rs where sequential management ops use update_id 0, 1, 2, ... against a token that starts at 0. Without this fix, every fast token manage call against a real network is rejected with an UpdateIdMismatch-style error. Plan amended to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the send.ts pattern across token create/mint/burn/manage so that successful token operations show up in `fast info history`. Extends the HistoryEntry.type union with token-create, token-mint, token-burn, and token-manage variants. Recording happens only on the success branch (incomplete-multisig has no certificate yet). For `token create`, the deterministic token id is computed via getTokenId(senderBytes, nonce, 0n) using the resolved signer's public key (single) or derived address bytes (multisig); the resulting hex id is also surfaced in the JSON output for convenience.
d7e03a1 to
2b1268d
Compare
214d1d9 to
7905218
Compare
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.
Replaces the standalone Rust
fastset-multisig-cliby adding multisig support to the TypeScriptfast-cli, backed by a newMultiSigSignerabstraction in@fastxyz/sdk. Polymorphic operations (send,token create/mint/burn/manage) work for both single-signer and multisig accounts; multisig coordination commands (init,import,export,pending,vote) live under a dedicated namespace.Two phases, both included here. Specs and plans are in
docs/superpowers/.Phase 1 — Multisig backbone
SDK (
@fastxyz/sdkminor):MultiSigSignerclass symmetric toSigner—signEnvelopeFor(versionedTx)for the cosigner-vote path,signTransaction(opts)for the initiate path.deriveMultiSigAddress,deriveMultiSigAddressBytes,assertAuthorizedSigner.MultiSigConfigInvalidError,NotAuthorizedSignerError.CLI (
@fastxyz/climinor):accountstable migrated to a tagged union (single | multisig) with a CHECK constraint enforcing the discriminator. Single-row backfill set tokind='single'.signer-resolverservice maps any account row →Signer | MultiSigSignerbased on kind, with--as <member>disambiguation for multisig.fast multisig init / import / export / pending / vote.fast sendFast→Fast route is now polymorphic on account kind.fast account listadds aKINDcolumn (e.g.multisig 2-of-3).Phase 2 — Token operations
submitOperationpipeline helper consolidating build/sign/submit/branch logic so the four token commands stay focused.fast token create / mint / burn / manage, polymorphic over single + multisig.--memooncreateandmanage(BCS layouts haveuser_data); not onmint/burn.token managefetches the currentupdateIdfromgetTokenInfoand submits it verbatim (validator increments after settlement — confirmed against the Rust reference).Review follow-up — 2026-05-21
multisig export --outfilesystem failures asFileIOError.0xprefixes from multisig pending/vote hashes and related error output.NotAMemberErrorwhen--asnames a non-member.multisig init.IncompleteVerifierSigsas an unfinished transaction result insubmitOperation.InvalidAddressError.__dirnameusage in Vitest files withimport.meta.urlpath resolution.Test plan
pnpm -C app/cli exec tsc --noEmitpnpm -C app/cli exec vitest run(14 files, 80 tests)pnpm -C packages/fast-sdk exec vitest run tests/unit/multisig-signer.test.ts(14 tests)pnpm build(9/9 turbo tasks)test (20)andtest (22)passedpnpm testpassed before the review follow-up patch.fast multisig --help,fast multisig {init,import,export,pending,vote} --helprender.fast token --help,fast token {create,mint,burn,manage} --helprender.send(incomplete-multisig) → cosignervote→ success.token createfrom single-signer → admin assigned correctly.token managerejected with wrongupdateId, accepted with current.Remaining tasks before merge
Breaking changes / migration
accountstable gainskind,multisig_configcolumns;evm_address,encrypted_key,encryptedbecome nullable. Existing rows backfillkind='single'automatically on first launch (drizzle migration0001_bumpy_komodo.sql).AccountInfois now a tagged union — code that touchedAccountInfodirectly needs to discriminate onkind. All in-tree consumers updated.Out of scope (deferred)
MultiSigConfigInvalidErrorto disambiguate from the CLI class with the same_tag(technical debt; runtime is fine because the CLI re-maps SDK errors).FastRpc.submitTransaction/getPendingMultisigTransactionsreturns to dropas nevercasts.MultiSigSigner.ensureValidfor concurrency.multisigConfigon read inrowToInfo.parseAmount,resolveTokenIdAndDecimals,resolvePassword) — duplication is contained for now.submitOperationdirectly).token createoutput.