From 61d8b53d6726d5ef36d4c619d4eac45806100a28 Mon Sep 17 00:00:00 2001 From: aelmanaa Date: Wed, 30 Sep 2026 02:07:50 +0200 Subject: [PATCH] feat(sdk): add rpc.multisigChangeThreshold `multisigChangeThreshold` was exposed in `instructions` and `transactions` (#99) but not in `rpc`, unlike the other controlled multisig config instructions such as `multisigSetTimeLock`. Also make the unused `spendingLimit` parameter of `transactions.multisigChangeThreshold` optional, so callers no longer need to pass a placeholder key. Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/multisig/src/rpc/index.ts | 1 + .../src/rpc/multisigChangeThreshold.ts | 53 +++++++++++++++++ .../transactions/multisigChangeThreshold.ts | 3 +- tests/suites/multisig-sdk.ts | 59 +++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 sdk/multisig/src/rpc/multisigChangeThreshold.ts diff --git a/sdk/multisig/src/rpc/index.ts b/sdk/multisig/src/rpc/index.ts index 2eed0925..b4b11032 100644 --- a/sdk/multisig/src/rpc/index.ts +++ b/sdk/multisig/src/rpc/index.ts @@ -6,6 +6,7 @@ export * from "./configTransactionAccountsClose.js"; export * from "./configTransactionCreate.js"; export * from "./configTransactionExecute.js"; export * from "./multisigAddMember.js"; +export * from "./multisigChangeThreshold.js"; export * from "./multisigRemoveMember.js"; export * from "./multisigAddSpendingLimit.js"; export * from "./multisigRemoveSpendingLimit.js"; diff --git a/sdk/multisig/src/rpc/multisigChangeThreshold.ts b/sdk/multisig/src/rpc/multisigChangeThreshold.ts new file mode 100644 index 00000000..0bc15d24 --- /dev/null +++ b/sdk/multisig/src/rpc/multisigChangeThreshold.ts @@ -0,0 +1,53 @@ +import { + Connection, + PublicKey, + SendOptions, + Signer, + TransactionSignature, +} from "@solana/web3.js"; +import * as transactions from "../transactions"; +import { translateAndThrowAnchorError } from "../errors"; + +/** Change the `threshold` of a controlled multisig. */ +export async function multisigChangeThreshold({ + connection, + multisigPda, + configAuthority, + rentPayer, + newThreshold, + memo, + signers, + sendOptions, + programId, +}: { + connection: Connection; + multisigPda: PublicKey; + configAuthority: PublicKey; + /** Pays the transaction fee. */ + rentPayer: Signer; + newThreshold: number; + memo?: string; + signers?: Signer[]; + sendOptions?: SendOptions; + programId?: PublicKey; +}): Promise { + const blockhash = (await connection.getLatestBlockhash()).blockhash; + + const tx = transactions.multisigChangeThreshold({ + blockhash, + multisigPda, + configAuthority, + rentPayer: rentPayer.publicKey, + newThreshold, + memo, + programId, + }); + + tx.sign([rentPayer, ...(signers ?? [])]); + + try { + return await connection.sendTransaction(tx, sendOptions); + } catch (err) { + translateAndThrowAnchorError(err); + } +} diff --git a/sdk/multisig/src/transactions/multisigChangeThreshold.ts b/sdk/multisig/src/transactions/multisigChangeThreshold.ts index 05660558..9d20afa8 100644 --- a/sdk/multisig/src/transactions/multisigChangeThreshold.ts +++ b/sdk/multisig/src/transactions/multisigChangeThreshold.ts @@ -23,7 +23,8 @@ export function multisigChangeThreshold({ }: { blockhash: string; multisigPda: PublicKey; - spendingLimit: PublicKey; + /** Unused. */ + spendingLimit?: PublicKey; configAuthority: PublicKey; rentPayer: PublicKey; newThreshold: number; diff --git a/tests/suites/multisig-sdk.ts b/tests/suites/multisig-sdk.ts index db2da09c..5660b2ae 100644 --- a/tests/suites/multisig-sdk.ts +++ b/tests/suites/multisig-sdk.ts @@ -373,6 +373,65 @@ describe("Multisig SDK", () => { }); }); + describe("multisig_change_threshold for a controlled multisig", () => { + let multisigPda: PublicKey; + let configAuthority: Keypair; + let wrongConfigAuthority: Keypair; + before(async () => { + configAuthority = await generateFundedKeypair(connection); + wrongConfigAuthority = await generateFundedKeypair(connection); + + // Create new controlled multisig. + multisigPda = ( + await createControlledMultisig({ + connection, + createKey: Keypair.generate(), + members, + threshold: 1, + configAuthority: configAuthority.publicKey, + timeLock: 0, + programId, + }) + )[0]; + }); + + it("error: invalid authority", async () => { + const rentPayer = await generateFundedKeypair(connection); + await assert.rejects( + multisig.rpc.multisigChangeThreshold({ + connection, + multisigPda, + configAuthority: wrongConfigAuthority.publicKey, + rentPayer, + newThreshold: 2, + signers: [wrongConfigAuthority], + programId, + }), + /Attempted to perform an unauthorized action/ + ); + }); + + it("change `threshold` for the controlled multisig", async () => { + const rentPayer = await generateFundedKeypair(connection); + const signature = await multisig.rpc.multisigChangeThreshold({ + connection, + multisigPda, + configAuthority: configAuthority.publicKey, + rentPayer, + newThreshold: 2, + signers: [configAuthority], + programId, + }); + await connection.confirmTransaction(signature); + + const multisigAccount = await Multisig.fromAccountAddress( + connection, + multisigPda + ); + assert.strictEqual(multisigAccount.threshold, 2); + }); + }); + describe("multisig_set_config_authority", () => { let multisigPda: PublicKey; let configAuthority: Keypair;