How to create a custom instruction to be executed via Realms
Create a Custom Instruction
SPL Governance proposals can execute arbitrary on-chain instructions once a vote succeeds. This is the core mechanism that makes DAOs powerful: any instruction that can be called by a Solana program can be governed by a vote.
How Proposal Transactions Work
A Proposal can contain multiple ProposalTransactions, each with multiple instructions. After a successful vote, anyone can trigger execution once the hold_up_time has elapsed. The instructions are signed by the Governance PDA (specifically the DAO Wallet), giving them authority over any assets the DAO controls.
Two Transaction Types
SPL Governance v3.1.2 supports two types of proposal transactions:
Legacy Transactions (InsertTransaction) - The original format using InstructionData
Versioned Transactions (InsertVersionedTransaction) - New in v3.1.2, supports Address Lookup Tables and larger transactions
Creating a Legacy Proposal Transaction
InstructionData Format
Each instruction in a proposal transaction is encoded as:
pubstructInstructionData{/// Target program IDpubprogram_id:Pubkey,/// Account metadata for the instructionpubaccounts:Vec<AccountMetaData>,/// Instruction data bytespubdata:Vec<u8>,}pubstructAccountMetaData{pubpubkey:Pubkey,pubis_signer:bool,pubis_writable:bool,}
Step-by-Step: Adding an Instruction to a Proposal
1. Create the proposal:
2. Insert a transaction with your custom instruction:
3. Sign off the proposal to begin voting:
Creating Versioned Transactions (v3.1.2)
For larger instructions or when you need Address Lookup Tables, use versioned transactions:
Direct Insert (Small Transactions)
Buffered Insert (Large Transactions)
For transactions that exceed the Solana transaction size limit, use the buffer mechanism:
Common Custom Instruction Patterns
Program Upgrade
The most common governance action - upgrading a program:
Token Transfer from Treasury
Mint Tokens
Call Any Program
You can create instructions for any program. The key insight is that the Governance PDA (DAO Wallet) acts as a signer, so it can authorize any action where it holds authority:
Important Notes
DAO Wallet vs Governance PDA: Always use the DAO Wallet (native treasury) as the authority over assets. It is a PDA with no data, derived from the Governance account, and owned by the System program. It behaves like a regular wallet.
hold_up_time: Set this to add a delay between vote completion and execution. This gives the community time to react to controversial proposals.
Multiple transactions per option: A proposal option can have multiple transactions that execute independently.
Execution is permissionless: Once the vote passes and hold-up time elapses, anyone can trigger execution.
// 1. Create a buffer
let create_buffer_ix = create_transaction_buffer(
&governance_program_id,
&governance,
&proposal,
&token_owner_record,
&governance_authority,
&payer,
0, // buffer_index
final_buffer_hash, // SHA-256 of the complete message
final_buffer_size,
initial_chunk, // first chunk of bytes
);
// 2. Extend the buffer with remaining chunks
let extend_buffer_ix = extend_transaction_buffer(
&governance_program_id,
&governance,
&proposal,
&payer,
0, // buffer_index
next_chunk, // next chunk of bytes
);
// 3. Insert the versioned transaction from the completed buffer
let insert_from_buffer_ix = insert_versioned_transaction_from_buffer(
&governance_program_id,
&governance,
&proposal,
&token_owner_record,
&governance_authority,
&payer,
0, // option_index
0, // ephemeral_signers
0, // transaction_index
0, // buffer_index
);
let upgrade_ix = bpf_loader_upgradeable::upgrade(
&program_id_to_upgrade,
&buffer_address,
&dao_wallet, // upgrade authority = DAO wallet
&payer,
);
let transfer_ix = spl_token::instruction::transfer(
&spl_token::id(),
&dao_token_account,
&recipient_token_account,
&dao_wallet, // authority = DAO wallet
&[],
amount,
)?;
let mint_ix = spl_token::instruction::mint_to(
&spl_token::id(),
&governed_mint,
&destination_account,
&dao_wallet, // mint authority = DAO wallet
&[],
amount,
)?;
// Generic pattern for calling any program
let custom_ix = Instruction {
program_id: target_program_id,
accounts: vec![
AccountMeta::new(dao_wallet, true), // DAO wallet as signer
// ... other accounts as needed
],
data: your_instruction_data,
};