Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_message via 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:

#AccountFlagsDescription
0Proposal PDAwritableCreated via invoke_signed
1dWalletreadonlyThe dWallet account on the dWallet program
2CreatorsignerProposal authority
3Payerwritable, signerPays rent
4System ProgramreadonlyFor 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):

#AccountFlags
0Proposal PDAwritable
1VoteRecord PDAwritable
2Votersigner
3Payerwritable, signer
4System Programreadonly

CPI accounts (when quorum will be reached):

#AccountFlags
5MessageApproval PDAwritable
6dWalletreadonly
7Voting Programreadonly
8CPI Authority PDAreadonly
9dWallet Programreadonly

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

FrameworkPath
Pinocchiochains/solana/examples/voting/pinocchio/src/lib.rs
Nativechains/solana/examples/voting/native/src/lib.rs
Anchorchains/solana/examples/voting/anchor/src/lib.rs