Approve Messages
Pre-Alpha Disclaimer: This is an early pre-alpha release for exploring the SDK and starting development only. There is no real MPC signing — all signatures are generated by a single mock signer, not a distributed network. Do not submit any real transactions for signing or rely on any security guarantees. The dWallet keys, trust model, and signing protocol are not final; do not rely on any key material until mainnet. All interfaces, APIs, and data formats are subject to change without notice. The Solana program and all on-chain data will be wiped periodically and everything will be deleted when we transition to Ika Alpha 1. This software is provided “as is” without warranty of any kind; use is entirely at your own risk and dWallet Labs assumes no liability for any damages arising from its use.
create_proposal Instruction
The create_proposal instruction creates a Proposal PDA that references a dWallet and the message to sign.
Instruction Data
| Offset | Field | Size |
|---|---|---|
| 0 | proposal_id | 32 |
| 32 | message_hash | 32 |
| 64 | user_pubkey | 32 |
| 96 | signature_scheme | 1 |
| 97 | quorum | 4 |
| 101 | message_approval_bump | 1 |
| 102 | bump | 1 |
Total: 103 bytes.
Accounts
| # | Account | W | S | Description |
|---|---|---|---|---|
| 0 | proposal | yes | no | Proposal PDA (["proposal", proposal_id]) |
| 1 | dwallet | no | no | dWallet account |
| 2 | creator | no | yes | Proposal creator (signer) |
| 3 | payer | yes | yes | Rent payer |
| 4 | system_program | no | no | System program |
Implementation
#![allow(unused)]
fn main() {
fn create_proposal(
program_id: &Address,
accounts: &[AccountView],
data: &[u8],
) -> ProgramResult {
if data.len() < 103 {
return Err(ProgramError::InvalidInstructionData);
}
let [proposal_account, _dwallet, creator, payer, _system_program, ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
if !creator.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}
// Parse instruction data
let proposal_id: [u8; 32] = data[0..32].try_into().unwrap();
let message_hash: [u8; 32] = data[32..64].try_into().unwrap();
let user_pubkey: [u8; 32] = data[64..96].try_into().unwrap();
let signature_scheme = data[96];
let quorum = u32::from_le_bytes(data[97..101].try_into().unwrap());
let message_approval_bump = data[101];
let bump = data[102];
// Quorum must be at least 1
if quorum == 0 {
return Err(ProgramError::InvalidInstructionData);
}
// Create PDA with seeds ["proposal", proposal_id, bump]
let bump_byte = [bump];
let signer_seeds = [
pinocchio::cpi::Seed::from(b"proposal" as &[u8]),
pinocchio::cpi::Seed::from(proposal_id.as_ref()),
pinocchio::cpi::Seed::from(bump_byte.as_ref()),
];
let signer = Signer::from(&signer_seeds);
CreateAccount {
from: payer,
to: proposal_account,
lamports: minimum_balance(PROPOSAL_LEN),
space: PROPOSAL_LEN as u64,
owner: program_id,
}
.invoke_signed(&[signer])?;
// Write all proposal fields into the account data
let prop_data = unsafe { proposal_account.borrow_unchecked_mut() };
prop_data[0] = PROPOSAL_DISCRIMINATOR;
prop_data[1] = 1; // version
// ... copy proposal_id, dwallet, message_hash, etc. ...
Ok(())
}
}
The key points:
- The proposal stores the
message_hashandmessage_approval_bumpso the CPI call can construct the correct MessageApproval PDA later - The
user_pubkeyandsignature_schemeare passed through toapprove_messagewhen quorum is reached - Quorum of zero is rejected
MessageApproval PDA
When quorum is reached, the program creates a MessageApproval PDA via CPI. This PDA is derived by the dWallet program, not the voting program:
Seeds: ["message_approval", dwallet_pubkey, message_hash]
Program: DWALLET_PROGRAM_ID
Important: The
message_hashmust be computed using keccak256. This is required for the network (or mock) to derive the same PDA when committing the signature on-chain. This convention is subject to change in future releases — the pre-alpha mock always uses keccak256 regardless of thehash_schemefield in the gRPC Sign request.
The dWallet program verifies:
- The caller is a valid program (executable account)
- The CPI authority PDA matches the dWallet’s current authority
- The CPI authority is signed (via
invoke_signed)
Next Step
With proposals created, the next chapter implements vote casting and the quorum-triggered CPI.