Building the Voting Program
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.
What You’ll Learn
- How to define on-chain account layouts for proposals and vote records
- How to implement the CPI authority pattern for dWallet control
- How quorum detection triggers
approve_messagevia CPI - How to prevent double voting using PDA-based vote records
Architecture
Voter 1 ──► CastVote ──┐
Voter 2 ──► CastVote ──┤
Voter 3 ──► CastVote ──┼──► Quorum? ──► approve_message CPI ──► MessageApproval
│ │
└── VoteRecord PDAs (prevent double vote) │
▼
gRPC Sign request
│
▼
64-byte signature
1. Account Layouts
Proposal PDA (["proposal", proposal_id]) — 195 bytes
#![allow(unused)]
fn main() {
// Header
discriminator: u8, // offset 0, always 1
version: u8, // offset 1, always 1
// Fields
proposal_id: [u8; 32], // offset 2
dwallet: [u8; 32], // offset 34 — the dWallet this proposal controls
message_hash: [u8; 32],// offset 66 — keccak256 of the message to sign
user_pubkey: [u8; 32], // offset 98
signature_scheme: u8, // offset 130
creator: [u8; 32], // offset 131
yes_votes: u32, // offset 163 (LE)
no_votes: u32, // offset 167 (LE)
quorum: u32, // offset 171 (LE)
status: u8, // offset 175 — 0=Open, 1=Approved
msg_approval_bump: u8, // offset 176
bump: u8, // offset 177
_reserved: [u8; 16], // offset 178
}
VoteRecord PDA (["vote", proposal_id, voter]) — 69 bytes
#![allow(unused)]
fn main() {
discriminator: u8, // offset 0, always 2
version: u8, // offset 1
voter: [u8; 32], // offset 2
proposal_id: [u8; 32], // offset 34
vote: u8, // offset 66 — 1=yes, 0=no
bump: u8, // offset 67
}
2. CreateProposal Instruction (disc = 0)
Data: [proposal_id(32), message_hash(32), user_pubkey(32), signature_scheme(1), quorum(4), message_approval_bump(1), bump(1)] = 103 bytes
Accounts:
| # | Account | Flags | Description |
|---|---|---|---|
| 0 | Proposal PDA | writable | Created via invoke_signed |
| 1 | dWallet | readonly | The dWallet account on the dWallet program |
| 2 | Creator | signer | Proposal authority |
| 3 | Payer | writable, signer | Pays rent |
| 4 | System Program | readonly | For PDA creation |
3. CastVote Instruction (disc = 1)
Data: [proposal_id(32), vote(1), vote_record_bump(1), cpi_authority_bump(1)] = 35 bytes
Base accounts (always required):
| # | Account | Flags |
|---|---|---|
| 0 | Proposal PDA | writable |
| 1 | VoteRecord PDA | writable |
| 2 | Voter | signer |
| 3 | Payer | writable, signer |
| 4 | System Program | readonly |
CPI accounts (when quorum will be reached):
| # | Account | Flags |
|---|---|---|
| 5 | MessageApproval PDA | writable |
| 6 | dWallet | readonly |
| 7 | Voting Program | readonly |
| 8 | CPI Authority PDA | readonly |
| 9 | dWallet Program | readonly |
4. The CPI Call
When yes_votes >= quorum, the program:
#![allow(unused)]
fn main() {
let ctx = DWalletContext {
dwallet_program,
cpi_authority,
caller_program,
cpi_authority_bump,
};
ctx.approve_message(
message_approval, dwallet, payer, system_program,
message_hash, user_pubkey, signature_scheme,
message_approval_bump,
)?;
prop_data[PROP_STATUS] = STATUS_APPROVED;
}
The DWalletContext signs via invoke_signed with seeds ["__ika_cpi_authority", &[bump]], proving the voting program authorized this signing request.
Source Code
| Framework | Path |
|---|---|
| Pinocchio | chains/solana/examples/voting/pinocchio/src/lib.rs |
| Native | chains/solana/examples/voting/native/src/lib.rs |
| Anchor | chains/solana/examples/voting/anchor/src/lib.rs |