Squads: how Solana's standard multisig actually works on-chain
Squads is the canonical Solana multisig — Multisig PDA, threshold approvals, vault sub-accounts, and TX proposals. Here's the on-chain shape and how to use it.
Squads is the Solana ecosystem's standard multisig. Almost every protocol upgrade authority, treasury, and shared signer on mainnet is held by a Squads multisig. The current program is Squads V4, deployed at SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf.
It's well-designed and surprisingly simple once you see the account shape. Here's the on-chain view.
The account model
Three account types do the work:
// 1. The Multisig — the configuration account
pub struct Multisig {
pub create_key: Pubkey, // seed for PDA derivation
pub config_authority: Pubkey, // can change members/threshold (often the multisig itself)
pub threshold: u16, // M-of-N approval threshold
pub time_lock: u32, // seconds before approved tx can execute
pub transaction_index: u64, // monotonic counter
pub stale_transaction_index: u64, // older proposals get invalidated past this
pub rent_collector: Option<Pubkey>,
pub members: Vec<Member>, // each Member has Pubkey + Permissions
}
// 2. The Vault — a PDA the multisig signs for
// Derived from [b"multisig", multisig.key().as_ref(), b"vault", &[vault_index]]
// Holds SOL, tokens, NFTs, and is the actual on-chain "wallet"
// 3. The VaultTransaction — a pending proposal
pub struct VaultTransaction {
pub multisig: Pubkey,
pub creator: Pubkey,
pub index: u64,
pub bump: u8,
pub vault_index: u8,
pub vault_bump: u8,
pub ephemeral_signer_bumps: Vec<u8>,
pub message: VaultTransactionMessage, // the proposed Solana instructions
}
// 4. The Proposal — tracks who's approved/rejected
pub struct Proposal {
pub multisig: Pubkey,
pub transaction_index: u64,
pub status: ProposalStatus, // Draft, Active, Approved, Rejected, Executing, Executed, Cancelled
pub bump: u8,
pub approved: Vec<Pubkey>,
pub rejected: Vec<Pubkey>,
pub cancelled: Vec<Pubkey>,
}Multisigs are addressed by their create_key (an arbitrary pubkey chosen at creation, used as the seed). One multisig can have multiple vault sub-accounts indexed from 0 — typical pattern is "Treasury" (index 0) plus application-specific vaults.
Member permissions
pub struct Member {
pub key: Pubkey,
pub permissions: Permissions, // bitmask: Initiate | Vote | Execute
}
pub mod Permission {
pub const INITIATE: u8 = 1 << 0; // create new transactions
pub const VOTE: u8 = 1 << 1; // approve/reject
pub const EXECUTE: u8 = 1 << 2; // run approved transactions
}Members have fine-grained roles — you can have a member who can only initiate proposals (a bot, an integration), another who can only vote (the multisig's human signers), and a designated executor (often any approved member, or a specific operations account that pays for the execution gas).
The flow
- Create the multisig. Call
multisig_createwith members, threshold, time lock. Pay rent for the Multisig account. - Propose a transaction. A member with
INITIATEcallsvault_transaction_createwith the SVM instructions they want the multisig to execute. Thenproposal_createto open voting. - Vote. Members with
VOTEcallproposal_approveorproposal_reject. When approvals reachthreshold, status flips toApproved. - Wait for time lock. If
time_lockis set, the transaction can't execute until that many seconds have elapsed since approval. - Execute. A member with
EXECUTEcallsvault_transaction_execute. The multisig program CPIs into the proposed instructions with the vault PDA as signer.
Signing for the vault via CPI
The vault PDA is what holds the actual funds and what signs the proposed instructions. Derivation:
use solana_program::pubkey::Pubkey;
let multisig_pubkey: Pubkey = /* the Multisig account */;
let vault_index: u8 = 0;
let (vault_pda, _bump) = Pubkey::find_program_address(
&[
b"multisig",
multisig_pubkey.as_ref(),
b"vault",
&[vault_index],
],
&SQUADS_V4_PROGRAM_ID,
);When the multisig executes an approved transaction, itinvoke_signed with these vault seeds. Any program that checks is_signer on the vault PDA sees the multisig as the authorised signer.
This is the entire mechanism by which DAOs hold treasuries, protocols control upgrade authorities, and teams custody shared funds on Solana.
TypeScript SDK
import * as multisig from "@sqds/multisig"
import { Connection, Keypair, PublicKey, TransactionMessage } from "@solana/web3.js"
const conn = new Connection("https://api.mainnet-beta.solana.com")
const creator = Keypair.generate()
const createKey = Keypair.generate()
// 1. Derive the multisig PDA
const [multisigPda] = multisig.getMultisigPda({ createKey: createKey.publicKey })
// 2. Create the multisig (2-of-3 with 1-hour timelock)
await multisig.rpc.multisigCreateV2({
connection: conn,
treasury: /* program treasury for fees */,
creator,
createKey,
multisigPda,
configAuthority: null, // null = self-managed
timeLock: 60 * 60, // 1 hour
members: [
{ key: alice.publicKey, permissions: multisig.types.Permissions.all() },
{ key: bob.publicKey, permissions: multisig.types.Permissions.all() },
{ key: carol.publicKey, permissions: multisig.types.Permissions.all() },
],
threshold: 2,
rentCollector: null,
})
// 3. Get the vault PDA (index 0 = default treasury)
const [vaultPda] = multisig.getVaultPda({ multisigPda, index: 0 })
// 4. Propose a transaction (e.g. transfer SOL out of the vault)
const transferIx = SystemProgram.transfer({
fromPubkey: vaultPda,
toPubkey: recipient,
lamports: 1_000_000_000,
})
const txMessage = new TransactionMessage({
payerKey: vaultPda,
recentBlockhash: (await conn.getLatestBlockhash()).blockhash,
instructions: [transferIx],
}).compileToV0Message()
await multisig.rpc.vaultTransactionCreate({
connection: conn,
feePayer: alice,
multisigPda,
transactionIndex: 1n,
creator: alice.publicKey,
vaultIndex: 0,
ephemeralSigners: 0,
transactionMessage: txMessage,
})
// 5. Create the proposal, vote, execute — same pattern with the SDK
// multisig.rpc.proposalCreate / proposalApprove / vaultTransactionExecuteWhy this pattern wins on Solana
- Composable signer. The vault PDA is just another signer — any program that accepts a signer accepts the multisig. Stake accounts, upgrade authorities, token mint authorities, oracle authorities — all switchable to a multisig with one
SetAuthoritycall. - On-chain transparency. Every proposal, every vote, every execution is a public on-chain transaction. Squads' web UI (squads.so) is just an indexer + a builder — anyone can verify the underlying state.
- No off-chain coordination layer. Unlike Ethereum Safes, Squads V4 doesn't rely on off-chain signature aggregation. Every vote is its own transaction, posted on-chain.
References
Squads is the boring infrastructure underneath Solana's critical authorities. If you're holding a treasury, an upgrade authority, or any shared signer — this is the program.