Multisig Account Management
Description
Section titled “Description”This example demonstrates how to manage multisig accounts using KMD:
- listMultisig() - List all multisig accounts in a wallet
- exportMultisig() - Get the multisig preimage information
- deleteMultisig() - Remove a multisig account from the wallet Key concepts:
- Multisig accounts can be listed to see all multisigs in a wallet
- The multisig preimage contains the original parameters: publicKeys, threshold, version
- Deleting a multisig only removes it from the wallet, not from the blockchain
- Funds in a deleted multisig address remain on the blockchain
Prerequisites
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start) - Covered operations:
- generateKey() - Generate keys to use as multisig participants
- importMultisig() - Create a multisig account from public keys
- listMultisig() - List all multisig accounts in the wallet
- exportMultisig() - Export the multisig preimage (configuration)
- deleteMultisig() - Delete a multisig account from the wallet
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example kmd_client/09-multisig-management.ts/** * Example: Multisig Account Management * * This example demonstrates how to manage multisig accounts using KMD: * - listMultisig() - List all multisig accounts in a wallet * - exportMultisig() - Get the multisig preimage information * - deleteMultisig() - Remove a multisig account from the wallet * * Key concepts: * - Multisig accounts can be listed to see all multisigs in a wallet * - The multisig preimage contains the original parameters: publicKeys, threshold, version * - Deleting a multisig only removes it from the wallet, not from the blockchain * - Funds in a deleted multisig address remain on the blockchain * * Prerequisites: * - LocalNet running (via `algokit localnet start`) * * Covered operations: * - generateKey() - Generate keys to use as multisig participants * - importMultisig() - Create a multisig account from public keys * - listMultisig() - List all multisig accounts in the wallet * - exportMultisig() - Export the multisig preimage (configuration) * - deleteMultisig() - Delete a multisig account from the wallet */
import { decodeAddress } from '@algorandfoundation/algokit-utils';import { cleanupTestWallet, createKmdClient, createTestWallet, printError, printHeader, printInfo, printStep, printSuccess,} from '../shared/utils.js';
/** * Format a byte array for display, showing first and last few bytes */function formatBytesForDisplay(bytes: Uint8Array, showFirst = 4, showLast = 4): string { const hex = Buffer.from(bytes).toString('hex'); if (bytes.length <= showFirst + showLast) { return hex; } const firstBytes = hex.slice(0, showFirst * 2); const lastBytes = hex.slice(-(showLast * 2)); return `${firstBytes}...${lastBytes}`;}
async function main() { printHeader('KMD Multisig Account Management Example');
const kmd = createKmdClient(); let walletHandleToken = ''; const walletPassword = 'test-password';
try { // ========================================================================= // Step 1: Create a Test Wallet // ========================================================================= printStep(1, 'Creating a test wallet');
const testWallet = await createTestWallet(kmd, walletPassword); walletHandleToken = testWallet.walletHandleToken;
printSuccess(`Test wallet created: ${testWallet.walletName}`); printInfo(`Wallet ID: ${testWallet.walletId}`);
// ========================================================================= // Step 2: Generate 3 Keys for Multisig Participants // ========================================================================= printStep(2, 'Generating 3 keys to use as multisig participants');
const participantAddresses: string[] = []; const numParticipants = 3;
for (let i = 1; i <= numParticipants; i++) { const result = await kmd.generateKey({ walletHandleToken }); participantAddresses.push(result.address.toString()); printInfo(`Participant ${i}: ${result.address}`); }
printSuccess(`Generated ${numParticipants} participant keys`);
// ========================================================================= // Step 3: Create a 2-of-3 Multisig Account // ========================================================================= printStep(3, 'Creating a 2-of-3 multisig account');
const publicKeys: Uint8Array[] = participantAddresses.map(addr => { const decoded = decodeAddress(addr); return decoded.publicKey; });
const threshold = 2; const multisigVersion = 1;
const multisigResult = await kmd.importMultisig({ walletHandleToken, publicKeys, threshold, multisigVersion, });
const multisigAddress = multisigResult.address.toString();
printSuccess('Multisig account created!'); printInfo(`Multisig address: ${multisigAddress}`); printInfo(`Configuration: ${threshold}-of-${numParticipants}`);
// ========================================================================= // Step 4: List All Multisig Accounts with listMultisig() // ========================================================================= printStep(4, 'Listing all multisig accounts with listMultisig()');
const listResult = await kmd.listMultisig({ walletHandleToken });
printSuccess(`Found ${listResult.addresses.length} multisig address(es) in wallet`); printInfo(''); printInfo('ListMultisigResponse fields:'); printInfo(` addresses: Array of ${listResult.addresses.length} Address(es)`); printInfo(''); printInfo('Multisig addresses in wallet:'); listResult.addresses.forEach((addr, i) => { printInfo(` ${i + 1}. ${addr}`); });
printInfo(''); printInfo('Note: listMultisig() returns all multisig addresses currently'); printInfo('imported in the wallet. Each address represents a unique multisig'); printInfo('configuration (different participants, threshold, or version).');
// ========================================================================= // Step 5: Export Multisig Preimage with exportMultisig() // ========================================================================= printStep(5, 'Exporting multisig preimage with exportMultisig()');
const exportResult = await kmd.exportMultisig({ walletHandleToken, address: multisigResult.address, });
printSuccess('Multisig preimage exported successfully!'); printInfo(''); printInfo('ExportMultisigResponse fields:'); printInfo(` multisigVersion: ${exportResult.multisigVersion}`); printInfo(` threshold: ${exportResult.threshold}`); printInfo(` publicKeys: Array of ${exportResult.publicKeys.length} Uint8Array(s)`); printInfo(''); printInfo('Exported multisig configuration:'); printInfo(` Version: ${exportResult.multisigVersion}`); printInfo(` Threshold: ${exportResult.threshold} (minimum signatures required)`); printInfo(` Public Keys:`); exportResult.publicKeys.forEach((pk, i) => { printInfo(` ${i + 1}. ${formatBytesForDisplay(pk)} (${pk.length} bytes)`); });
printInfo(''); printInfo('What is the multisig preimage?'); printInfo('-'.repeat(40)); printInfo('The preimage contains the original parameters used to create'); printInfo('the multisig address:'); printInfo(' - multisigVersion: The format version (always 1)'); printInfo(' - threshold: Minimum signatures required'); printInfo(' - publicKeys: The ordered list of participant public keys'); printInfo(''); printInfo('This information is needed to:'); printInfo(' - Reconstruct the multisig address'); printInfo(' - Import the multisig into another wallet'); printInfo(' - Verify the configuration of an existing multisig');
// ========================================================================= // Step 6: Verify Exported Info Matches Original // ========================================================================= printStep(6, 'Verifying exported info matches original parameters');
const versionMatches = exportResult.multisigVersion === multisigVersion; const thresholdMatches = exportResult.threshold === threshold; const keyCountMatches = exportResult.publicKeys.length === publicKeys.length;
// Check if all public keys match let allKeysMatch = keyCountMatches; if (keyCountMatches) { for (let i = 0; i < publicKeys.length; i++) { const originalKey = publicKeys[i]; const exportedKey = exportResult.publicKeys[i]; if (originalKey.length !== exportedKey.length) { allKeysMatch = false; break; } for (let j = 0; j < originalKey.length; j++) { if (originalKey[j] !== exportedKey[j]) { allKeysMatch = false; break; } } } }
printInfo('Verification results:'); printInfo( ` Version matches: ${versionMatches ? 'Yes' : 'No'} (expected: ${multisigVersion}, got: ${exportResult.multisigVersion})`, ); printInfo( ` Threshold matches: ${thresholdMatches ? 'Yes' : 'No'} (expected: ${threshold}, got: ${exportResult.threshold})`, ); printInfo( ` Key count matches: ${keyCountMatches ? 'Yes' : 'No'} (expected: ${publicKeys.length}, got: ${exportResult.publicKeys.length})`, ); printInfo(` All keys match: ${allKeysMatch ? 'Yes' : 'No'}`);
if (versionMatches && thresholdMatches && allKeysMatch) { printSuccess('All exported information matches the original parameters!'); }
// ========================================================================= // Step 7: Delete the Multisig Account with deleteMultisig() // ========================================================================= printStep(7, 'Deleting the multisig account with deleteMultisig()');
printInfo(`Deleting multisig: ${multisigAddress}`);
await kmd.deleteMultisig({ walletHandleToken, address: multisigResult.address, walletPassword, });
printSuccess('Multisig account deleted from wallet!'); printInfo(''); printInfo('deleteMultisig() parameters:'); printInfo(' - walletHandleToken: Session token for the wallet'); printInfo(' - address: The multisig address to delete'); printInfo(' - walletPassword: Wallet password (required for security)'); printInfo(''); printInfo('Important notes about deleteMultisig():'); printInfo(' - Only removes the multisig from the local KMD wallet'); printInfo(' - Does NOT affect the blockchain account'); printInfo(' - Any funds at the multisig address remain accessible'); printInfo(' - To spend funds, re-import the multisig with the same parameters');
// ========================================================================= // Step 8: Verify Deletion by Listing Multisig Accounts Again // ========================================================================= printStep(8, 'Verifying deletion by listing multisig accounts');
const listAfterDelete = await kmd.listMultisig({ walletHandleToken });
printInfo('Multisig accounts after deletion:'); if (listAfterDelete.addresses.length === 0) { printSuccess('No multisig accounts remaining in wallet'); } else { printInfo(`Found ${listAfterDelete.addresses.length} multisig address(es):`); listAfterDelete.addresses.forEach((addr, i) => { printInfo(` ${i + 1}. ${addr}`); }); }
// Check if the deleted address is still present const deletedAddressStillPresent = listAfterDelete.addresses.some( addr => addr.toString() === multisigAddress, );
if (deletedAddressStillPresent) { printError('The deleted multisig address is still present (unexpected)'); } else { printSuccess(`Confirmed: ${multisigAddress.slice(0, 8)}... is no longer in the wallet`); }
// ========================================================================= // Cleanup // ========================================================================= printStep(9, 'Cleaning up test wallet');
await cleanupTestWallet(kmd, walletHandleToken); walletHandleToken = ''; // Mark as cleaned up
printSuccess('Test wallet handle released');
// ========================================================================= // Summary // ========================================================================= printHeader('Summary'); printInfo('This example demonstrated multisig account management in KMD:'); printInfo(''); printInfo(' listMultisig()'); printInfo(' Parameters:'); printInfo(' - walletHandleToken: Session token for the wallet'); printInfo(' Returns:'); printInfo(' - addresses: Array of multisig Address objects'); printInfo(''); printInfo(' exportMultisig()'); printInfo(' Parameters:'); printInfo(' - walletHandleToken: Session token for the wallet'); printInfo(' - address: The multisig address to export'); printInfo(' Returns:'); printInfo(' - multisigVersion: Multisig format version (1)'); printInfo(' - threshold: Minimum signatures required'); printInfo(' - publicKeys: Array of participant public keys'); printInfo(''); printInfo(' deleteMultisig()'); printInfo(' Parameters:'); printInfo(' - walletHandleToken: Session token for the wallet'); printInfo(' - address: The multisig address to delete'); printInfo(' - walletPassword: Wallet password (required)'); printInfo(' Returns:'); printInfo(' - (void)'); printInfo(''); printInfo('Key takeaways:'); printInfo(' - listMultisig() shows all multisig accounts in the wallet'); printInfo(' - exportMultisig() retrieves the original configuration (preimage)'); printInfo(' - deleteMultisig() removes from wallet only, not blockchain'); printInfo(' - Wallet password is required for deleteMultisig() for security'); printInfo(' - Deleted multisigs can be re-imported with the same parameters'); printInfo(''); printInfo('Note: The test wallet remains in KMD (wallets cannot be deleted via API).'); } catch (error) { printError(`Error: ${error instanceof Error ? error.message : String(error)}`); printInfo(''); printInfo('Troubleshooting:'); printInfo(' - Ensure LocalNet is running: algokit localnet start'); printInfo(' - If LocalNet issues occur: algokit localnet reset'); printInfo(' - Check that KMD is accessible on port 4002');
// Cleanup on error if (walletHandleToken) { await cleanupTestWallet(kmd, walletHandleToken); }
process.exit(1); }}
main().catch(error => { console.error('Fatal error:', error); process.exit(1);});Other examples in KMD Client
Section titled “Other examples in KMD Client”- KMD Version Information
- Wallet Creation and Listing
- Wallet Session Management
- Key Generation
- Key Import and Export
- Key Listing and Deletion
- Master Key Export
- Multisig Account Setup
- Multisig Account Management
- Transaction Signing with KMD
- Multisig Transaction Signing with KMD
- Program Signing (Delegated Logic Signatures) with KMD
- Multisig Program Signing (Delegated Multisig Logic Signatures) with KMD