squads

Par elophanto · elophanto

Guide complet du protocole Squads - principale infrastructure de smart accounts et multisig sur Solana. Couvre Squads V4 Multisig pour la gestion de trésorerie d'équipe, le Smart Account Program pour l'abstraction de comptes et les portefeuilles programmables, ainsi que Grid pour les rails stablecoin et l'infrastructure fintech.

npx skills add https://github.com/elophanto/elophanto --skill squads

Guide de développement du Squads Protocol

Squads Protocol est l'infrastructure de smart account de premier plan de Solana, sécurisant plus de 10 milliards de dollars en actifs numériques. Ce guide couvre les trois principaux produits : Squads V4 Multisig, Smart Account Program et Grid.

Overview

Squads Protocol fournit :

  • Squads V4 Multisig - Portefeuille multi-signature pour les équipes avec propositions, vote, time locks, limites de dépenses et gestion des mises à jour de programme
  • Smart Account Program - Infrastructure d'abstraction de compte avec clés de session, passkeys, politiques programmables et débits directs
  • Grid - Infrastructure de finance ouverte pour les rails stablecoin, les plateformes néobanques et les systèmes de paiement d'entreprise

Program IDs

Program Mainnet Devnet
Squads V4 Multisig SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf
Smart Account SMRTzfY6DfH5ik3TKiyLFfXexV8uSG3d2UksSCYdunG SMRTzfY6DfH5ik3TKiyLFfXexV8uSG3d2UksSCYdunG
External Signature (Grid) ExtSgUPtP3JyKUysFw2S5fpL5fWfUPzGUQLd2bTwftXN ExtSgUPtP3JyKUysFw2S5fpL5fWfUPzGUQLd2bTwftXN

Eclipse Mainnet: | Program | Address | |---------|---------| | Squads V4 Multisig | eSQDSMLf3qxwHVHeTr9amVAGmZbRLY2rFdSURandt6f |

Quick Start

Installation

# Squads V4 Multisig SDK
npm install @sqds/multisig @solana/web3.js

# Grid SDK
npm install @sqds/grid

# Grid React Native SDK
npm install @sqds/grid-react-native

Basic Setup

import * as multisig from "@sqds/multisig";
import { Connection, Keypair, PublicKey } from "@solana/web3.js";

// Setup connection
const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");

// Load wallet
const wallet = Keypair.fromSecretKey(/* your secret key */);

// Program ID constant
const SQUADS_PROGRAM_ID = new PublicKey("SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf");

Squads V4 Multisig

Squads V4 est la dernière version du protocole multisig, avec des améliorations en matière de sécurité, de time locks, de limites de dépenses et de transactions par lot.

Core Concepts

Multisig Account : Le compte principal qui détient la configuration (membres, seuil, paramètres de time lock).

Vault : Un PDA contrôlé par le multisig où sont stockés les actifs. Chaque multisig peut avoir plusieurs vaults (indexés 0, 1, 2...).

Proposal : Une demande d'exécution d'une transaction qui nécessite l'approbation des membres du multisig.

Transaction : La ou les instructions réelles à exécuter une fois la proposition approuvée.

Permission System

Les membres peuvent avoir des permissions différentes :

import { Permission, Permissions } from "@sqds/multisig/lib/types";

// All permissions (can initiate, vote, and execute)
const fullPermissions = Permissions.all();

// Specific permissions
const voteOnly = Permissions.fromPermissions([Permission.Vote]);
const initiateAndVote = Permissions.fromPermissions([Permission.Initiate, Permission.Vote]);
const executeOnly = Permissions.fromPermissions([Permission.Execute]);
Permission Description
Initiate Peut créer des propositions
Vote Peut approuver ou rejeter des propositions
Execute Peut exécuter les propositions approuvées

Creating a Multisig

import * as multisig from "@sqds/multisig";

const { Permissions } = multisig.types;

// Generate a unique create key (one-time use)
const createKey = Keypair.generate();

// Derive the multisig PDA
const [multisigPda] = multisig.getMultisigPda({
  createKey: createKey.publicKey,
});

// Create a 2-of-3 multisig
const signature = await multisig.rpc.multisigCreateV2({
  connection,
  createKey,
  creator: wallet,
  multisigPda,
  configAuthority: null, // Immutable config
  threshold: 2,
  members: [
    { key: member1.publicKey, permissions: Permissions.all() },
    { key: member2.publicKey, permissions: Permissions.all() },
    { key: member3.publicKey, permissions: Permissions.fromPermissions([Permission.Vote]) },
  ],
  timeLock: 0, // No time lock (in seconds)
  rentCollector: null,
});

console.log("Multisig created:", multisigPda.toString());
console.log("Transaction:", signature);

Vault Operations

Les vaults sont des PDAs qui détiennent les actifs du multisig :

// Derive vault PDA (index 0 is the default vault)
const [vaultPda] = multisig.getVaultPda({
  multisigPda,
  index: 0,
});

console.log("Vault address:", vaultPda.toString());

// Check vault balance
const balance = await connection.getBalance(vaultPda);
console.log("Vault balance:", balance / 1e9, "SOL");

Important : Envoyez toujours les fonds au vault PDA, et non au compte multisig lui-même.

Creating a Vault Transaction

import { SystemProgram, LAMPORTS_PER_SOL } from "@solana/web3.js";

// Get current transaction index
const multisigAccount = await multisig.accounts.Multisig.fromAccountAddress(
  connection,
  multisigPda
);
const transactionIndex = BigInt(Number(multisigAccount.transactionIndex) + 1);

// Derive transaction PDA
const [transactionPda] = multisig.getTransactionPda({
  multisigPda,
  index: transactionIndex,
});

// Create a transfer instruction
const transferIx = SystemProgram.transfer({
  fromPubkey: vaultPda,
  toPubkey: recipientPubkey,
  lamports: 0.1 * LAMPORTS_PER_SOL,
});

// Create the vault transaction
const signature = await multisig.rpc.vaultTransactionCreate({
  connection,
  feePayer: wallet,
  multisigPda,
  transactionIndex,
  creator: wallet.publicKey,
  vaultIndex: 0,
  ephemeralSigners: 0,
  transactionMessage: new TransactionMessage({
    payerKey: vaultPda,
    recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
    instructions: [transferIx],
  }),
});

console.log("Vault transaction created:", transactionPda.toString());

Creating and Managing Proposals

// Derive proposal PDA
const [proposalPda] = multisig.getProposalPda({
  multisigPda,
  transactionIndex,
});

// Create proposal for the transaction
const createProposalSig = await multisig.rpc.proposalCreate({
  connection,
  feePayer: wallet,
  multisigPda,
  transactionIndex,
  creator: wallet,
});

console.log("Proposal created:", proposalPda.toString());

Voting on Proposals

// Approve the proposal
const approveSig = await multisig.rpc.proposalApprove({
  connection,
  feePayer: wallet,
  multisigPda,
  transactionIndex,
  member: wallet,
});

console.log("Proposal approved");

// Or reject the proposal
const rejectSig = await multisig.rpc.proposalReject({
  connection,
  feePayer: wallet,
  multisigPda,
  transactionIndex,
  member: wallet,
  memo: "Reason for rejection",
});

Executing Approved Transactions

// Check if proposal is approved and ready
const proposal = await multisig.accounts.Proposal.fromAccountAddress(
  connection,
  proposalPda
);

if (proposal.status.__kind === "Approved") {
  // Execute the vault transaction
  const executeSig = await multisig.rpc.vaultTransactionExecute({
    connection,
    feePayer: wallet,
    multisigPda,
    transactionIndex,
    member: wallet.publicKey,
  });

  console.log("Transaction executed:", executeSig);
}

Spending Limits

Les limites de dépenses permettent aux membres d'exécuter des transactions jusqu'à un certain montant sans approbation multisig complète :

// Create a spending limit
const createSpendingLimitSig = await multisig.rpc.configTransactionCreate({
  connection,
  feePayer: wallet,
  multisigPda,
  transactionIndex,
  creator: wallet.publicKey,
  actions: [{
    __kind: "AddSpendingLimit",
    createKey: spendingLimitCreateKey.publicKey,
    vaultIndex: 0,
    mint: SOL_MINT, // or token mint
    amount: BigInt(1 * LAMPORTS_PER_SOL), // 1 SOL
    period: multisig.types.Period.Day,
    members: [trustedMember.publicKey],
    destinations: [allowedDestination],
  }],
});

// Use spending limit (no proposal needed)
const useSpendingLimitSig = await multisig.rpc.spendingLimitUse({
  connection,
  feePayer: wallet,
  multisigPda,
  member: trustedMember,
  spendingLimit: spendingLimitPda,
  mint: SOL_MINT,
  vaultIndex: 0,
  amount: BigInt(0.5 * LAMPORTS_PER_SOL),
  decimals: 9,
  destination: allowedDestination,
});

Batch Transactions

Exécutez plusieurs transactions de manière atomique :

// Create a batch
const [batchPda] = multisig.getBatchPda({
  multisigPda,
  batchIndex: transactionIndex,
});

const createBatchSig = await multisig.rpc.batchCreate({
  connection,
  feePayer: wallet,
  multisigPda,
  batchIndex: transactionIndex,
  creator: wallet,
  vaultIndex: 0,
});

// Add transactions to the batch
await multisig.rpc.batchAddTransaction({
  connection,
  feePayer: wallet,
  multisigPda,
  batchIndex: transactionIndex,
  transactionIndex: 1,
  vaultIndex: 0,
  transactionMessage: /* first transaction */,
});

await multisig.rpc.batchAddTransaction({
  connection,
  feePayer: wallet,
  multisigPda,
  batchIndex: transactionIndex,
  transactionIndex: 2,
  vaultIndex: 0,
  transactionMessage: /* second transaction */,
});

// Create proposal and execute as usual

Config Transactions

Modifiez les paramètres du multisig (nécessite l'approbation d'une proposition) :

// Add a new member
const addMemberSig = await multisig.rpc.configTransactionCreate({
  connection,
  feePayer: wallet,
  multisigPda,
  transactionIndex,
  creator: wallet.publicKey,
  actions: [{
    __kind: "AddMember",
    newMember: {
      key: newMemberPubkey,
      permissions: Permissions.all(),
    },
  }],
});

// Change threshold
const changeThresholdSig = await multisig.rpc.configTransactionCreate({
  connection,
  feePayer: wallet,
  multisigPda,
  transactionIndex,
  creator: wallet.publicKey,
  actions: [{
    __kind: "ChangeThreshold",
    newThreshold: 3,
  }],
});

// Remove a member
const removeMemberSig = await multisig.rpc.configTransactionCreate({
  connection,
  feePayer: wallet,
  multisigPda,
  transactionIndex,
  creator: wallet.publicKey,
  actions: [{
    __kind: "RemoveMember",
    oldMember: memberToRemove,
  }],
});

Time Locks

Ajoutez un délai avant que les transactions approuvées ne puissent s'exécuter :

// Create multisig with time lock (1 day = 86400 seconds)
const signature = await multisig.rpc.multisigCreateV2({
  connection,
  createKey,
  creator: wallet,
  multisigPda,
  configAuthority: null,
  threshold: 2,
  members: [...],
  timeLock: 86400, // 1 day in seconds
  rentCollector: null,
});

PDA Derivation Reference

import * as multisig from "@sqds/multisig";

// Multisig PDA
const [multisigPda] = multisig.getMultisigPda({
  createKey: createKeyPubkey,
});

// Vault PDA
const [vaultPda] = multisig.getVaultPda({
  multisigPda,
  index: 0, // vault index
});

// Transaction PDA
const [transactionPda] = multisig.getTransactionPda({
  multisigPda,
  index: transactionIndex,
});

// Proposal PDA
const [proposalPda] = multisig.getProposalPda({
  multisigPda,
  transactionIndex,
});

// Batch PDA
const [batchPda] = multisig.getBatchPda({
  multisigPda,
  batchIndex,
});

// Spending Limit PDA
const [spendingLimitPda] = multisig.getSpendingLimitPda({
  multisigPda,
  createKey: spendingLimitCreateKey,
});

// Program Config PDA
const [programConfigPda] = multisig.getProgramConfigPda({});

// Ephemeral Signer PDA (for CPI calls)
const [ephemeralSignerPda] = multisig.getEphemeralSignerPda({
  transactionPda,
  ephemeralSignerIndex: 0,
});

Smart Account Program

Smart Account Program fournit des fonctionnalités d'abstraction de compte pour Solana, permettant des portefeuilles programmables avec clés de session, passkeys et exécution basée sur les politiques.

Core Concepts

Smart Account : Un portefeuille programmable contrôlé par des politiques plutôt que par des clés privées.

Session Key : Clés temporaires avec permissions limitées pour des opérations spécifiques.

Passkey : Authentification WebAuthn/FIDO2 utilisant la biométrie ou les clés matérielles.

Policy : Règles qui gouvernent les transactions pouvant être exécutées.

Features

  • Rent-free Deployment - Aucun loyer requis pour les smart accounts
  • Atomic Policy Enforcement - Tous les contrôles de politique se font de manière atomique
  • Account Compression - Stockage efficace pour plusieurs signataires
  • Extensible Policies - Des programmes de politique personnalisés peuvent être ajoutés
  • Direct Debits - Paiements récurrents automatisés
  • Subscriptions - Facturation d'abonnement gérée

REST API Base URL

https://developer-api.squads.so/api/v1

Creating a Smart Account

// Via REST API
const response = await fetch("https://developer-api.squads.so/api/v1/accounts", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${apiKey}`,
  },
  body: JSON.stringify({
    email: "user@example.com",
    // or
    signer: signerPublicKey,
  }),
});

const account = await response.json();
console.log("Smart account:", account.address);

Session Key Management

Les clés de session permettent un accès délégué avec des permissions spécifiques :

// Create a session key
const sessionResponse = await fetch(
  `https://developer-api.squads.so/api/v1/accounts/${accountId}/sessions`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      publicKey: sessionKeyPublicKey,
      expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
      permissions: ["transfer", "swap"],
      limits: {
        maxAmount: "1000000000", // 1 SOL in lamports
        dailyLimit: "5000000000", // 5 SOL daily
      },
    }),
  }
);

Passkey Authentication

// Register a passkey
const registerResponse = await fetch(
  `https://developer-api.squads.so/api/v1/accounts/${accountId}/passkeys`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      credentialId: webAuthnCredential.id,
      publicKey: webAuthnCredential.publicKey,
      attestation: webAuthnCredential.attestation,
    }),
  }
);

// Authenticate with passkey
const authResponse = await fetch(
  "https://developer-api.squads.so/api/v1/auth/passkey",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      credentialId: webAuthnCredential.id,
      assertion: webAuthnAssertion,
    }),
  }
);

Policy Configuration

// Set spending policy
const policyResponse = await fetch(
  `https://developer-api.squads.so/api/v1/accounts/${accountId}/policies`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      type: "spending_limit",
      params: {
        mint: "So11111111111111111111111111111111111111112", // SOL
        amount: "10000000000", // 10 SOL
        period: "daily",
      },
    }),
  }
);

Grid

Grid est l'infrastructure de finance ouverte de Squads pour les rails stablecoin, permettant les plateformes néobanques et les systèmes de paiement d'entreprise.

Features

  • Stablecoin Rails - Infrastructure de paiement USDC/USDT
  • Programmable Payments - Flux de trésorerie automatisés
  • Virtual Accounts - Rampes d'entrée/sortie en monnaie fiduciaire
  • KYC Integration - Outils de conformité intégrés
  • Self-Custody - Les utilisateurs conservent le contrôle de leurs actifs

API Authentication

// Email OTP authentication
const otpResponse = await fetch("https://developer-api.squads.so/api/v1/auth/email/otp", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    email: "user@example.com",
  }),
});

// Verify OTP
const verifyResponse = await fetch("https://developer-api.squads.so/api/v1/auth/email/verify", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    email: "user@example.com",
    otp: "123456",
  }),
});

const { accessToken } = await verifyResponse.json();

Account Management

// Get account details
const accountResponse = await fetch(
  `https://developer-api.squads.so/api/v1/accounts/${accountId}`,
  {
    headers: {
      "Authorization": `Bearer ${accessToken}`,
    },
  }
);

const account = await accountResponse.json();
console.log("Balance:", account.balance);
console.log("Status:", account.status);

Payment Operations

// Create a payment intent
const paymentResponse = await fetch(
  "https://developer-api.squads.so/api/v1/payments",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      amount: "100000000", // 100 USDC (6 decimals)
      currency: "USDC",
      recipient: recipientAddress,
      memo: "Payment for services",
    }),
  }
);

const payment = await paymentResponse.json();
console.log("Payment ID:", payment.id);
console.log("Status:", payment.status);

Standing Orders (Recurring Payments)

// Create a standing order
const standingOrderResponse = await fetch(
  "https://developer-api.squads.so/api/v1/standing-orders",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      amount: "50000000", // 50 USDC
      currency: "USDC",
      recipient: recipientAddress,
      frequency: "monthly",
      startDate: "2024-02-01",
      memo: "Monthly subscription",
    }),
  }
);

Spending Limits

// Set spending limit
const limitResponse = await fetch(
  `https://developer-api.squads.so/api/v1/accounts/${accountId}/limits`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      type: "daily",
      amount: "1000000000", // 1000 USDC
      currency: "USDC",
    }),
  }
);

Best Practices

Security

  1. Ne partagez jamais les clés privées - Utilisez des variables d'environnement pour les données sensibles
  2. Vérifiez les program IDs - Confirmez toujours que vous interagissez avec les programmes officiels
  3. Utilisez les time locks - Pour les trésors de grande valeur, ajoutez des time locks pour une sécurité supplémentaire
  4. Limitez les permissions - Donnez aux membres uniquement les permissions dont ils ont besoin
  5. Testez sur devnet - Testez toujours les transactions sur devnet avant mainnet

Transaction Optimization

  1. Utilisez les Address Lookup Tables - Réduisez la taille des transactions pour les opérations complexes
  2. Regroupez les transactions - Groupez les opérations connexes si possible
  3. Définissez un budget de calcul approprié - Évitez les défaillances de transaction dues aux limites de calcul
import { ComputeBudgetProgram } from "@solana/web3.js";

// Add compute budget instruction
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({
  units: 400_000,
});

// Add priority fee for faster inclusion
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
  microLamports: 10_000,
});

Error Handling

try {
  const signature = await multisig.rpc.proposalCreate({
    connection,
    feePayer: wallet,
    multisigPda,
    transactionIndex,
    creator: wallet,
  });
} catch (error) {
  if (error.message.includes("NotAMember")) {
    console.error("Wallet is not a member of this multisig");
  } else if (error.message.includes("Unauthorized")) {
    console.error("Wallet does not have required permissions");
  } else if (error.message.includes("InvalidTransactionIndex")) {
    console.error("Transaction index already used");
  } else {
    throw error;
  }
}

Fetching Account State

// Get multisig account
const multisigAccount = await multisig.accounts.Multisig.fromAccountAddress(
  connection,
  multisigPda
);

console.log("Threshold:", multisigAccount.threshold);
console.log("Members:", multisigAccount.members);
console.log("Transaction Index:", multisigAccount.transactionIndex.toString());

// Get proposal status
const proposal = await multisig.accounts.Proposal.fromAccountAddress(
  connection,
  proposalPda
);

console.log("Status:", proposal.status.__kind);
console.log("Approved:", proposal.approved.length);
console.log("Rejected:", proposal.rejected.length);

Common Patterns

Program Upgrade Multisig

Sécurisez l'autorité de mise à niveau du programme avec un multisig :

// Transfer upgrade authority to multisig vault
const transferAuthIx = createSetAuthorityInstruction(
  programDataAddress,
  currentAuthority,
  AuthorityType.UpgradeAuthority,
  vaultPda
);

// Execute via multisig proposal
// Now program upgrades require multisig approval

Treasury Management

// Create multiple vaults for different purposes
const [operationsVault] = multisig.getVaultPda({ multisigPda, index: 0 });
const [reserveVault] = multisig.getVaultPda({ multisigPda, index: 1 });
const [grantVault] = multisig.getVaultPda({ multisigPda, index: 2 });

// Set spending limits for operations vault
// Reserve vault requires full multisig approval

Validator Operations

// Manage validator identity and vote account with multisig
// Transfer validator identity to vault
// Set up spending limits for operational costs

Resources

Official Documentation

GitHub Repositories

Security Audits

  • OtterSec audit
  • Neodyme audit
  • Certora audit
  • Trail of Bits audit

Verify Program Builds

# Install solana-verify
cargo install solana-verify

# Verify Squads V4 program
solana-verify get-program-hash -u mainnet-beta SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf

Skill Structure

squads/
├── SKILL.md                              # This file
├── resources/
│   ├── program-addresses.md              # All program IDs and PDAs
│   ├── multisig-api-reference.md         # @sqds/multisig SDK reference
│   ├── smart-account-api-reference.md    # Smart Account API reference
│   └── grid-api-reference.md             # Grid REST API reference
├── examples/
│   ├── multisig/
│   │   ├── create-multisig.ts            # Create multisig with members
│   │   ├── proposals-voting.ts           # Proposals and voting
│   │   ├── vault-transactions.ts         # Vault operations
│   │   └── spending-limits.ts            # Spending limit management
│   ├── smart-account/
│   │   ├── account-creation.ts           # Smart account setup
│   │   └── session-keys.ts               # Session key management
│   └── grid/
│       ├── api-quickstart.ts             # REST API basics
│       └── payments.ts                   # Payment operations
├── templates/
│   ├── multisig-setup.ts                 # Multisig client template
│   ├── smart-account-setup.ts            # Smart account template
│   └── grid-client.ts                    # Grid API client template
└── docs/
    └── troubleshooting.md                # Common issues and solutions

Verify

  • A real RPC/SDK call was issued (mainnet, devnet, or local validator) and the response payload is captured in the transcript, not just paraphrased
  • Every transaction was simulated (simulateTransaction or equivalent) before any signing/sending step; simulation logs are attached
  • For any signed/sent transaction, the resulting signature is recorded and confirmed on chain (status returned by getSignatureStatuses or an explorer URL)
  • Slippage, priority-fee, and compute-unit limits were set explicitly with concrete numeric values, not left to library defaults
  • Account addresses, mints, and program IDs used in the run match the documented squads-multisig addresses for the targeted cluster (no mainnet/devnet mix-up)
  • Failure path was exercised at least once (insufficient balance, stale oracle, expired blockhash, etc.) and the agent's error handling produced a human-readable message

Skills similaires