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

dWallet Developer Guide

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.

dWallet enables smart contracts to control signing keys on any blockchain. Your program determines what gets signed – the Ika network performs the distributed signing via 2PC-MPC.

How It Works

  1. Create a dWallet – the Ika network runs DKG and produces a public key
  2. Your program controls it – transfer the dWallet authority to your program’s CPI authority PDA
  3. Approve messages – when conditions are met, your program CPI-calls approve_message
  4. Network signs – the Ika validator network produces the signature via 2PC-MPC
  5. Signature stored on-chain – anyone can read the MessageApproval account to get the signature
#![allow(unused)]
fn main() {
// Your program decides when to sign
fn cast_vote(ctx: &DWalletContext, proposal: &Proposal) -> ProgramResult {
    if proposal.yes_votes >= proposal.quorum {
        ctx.approve_message(
            message_approval, dwallet, payer, system_program,
            proposal.message_hash, user_pubkey, signature_scheme, bump,
        )?;
    }
    Ok(())
}
}

What You’ll Learn

  • Getting Started: Install dependencies, create your first dWallet-controlled program
  • Tutorial: Build a voting app where quorum triggers signing
  • On-Chain Integration: dWallet accounts, message approval, CPI framework, gas deposits
  • gRPC API: SubmitTransaction, request/response types
  • Testing: Mollusk, LiteSVM, and E2E testing
  • Reference: Instructions, accounts, events

Installation

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.

Prerequisites

  • Rust (edition 2024): curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • Solana CLI 3.x+: sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

Add Dependencies

For Pinocchio Programs

[dependencies]
ika-dwallet-pinocchio = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
pinocchio = "0.10"
pinocchio-system = "0.5"

For Anchor Programs

[dependencies]
ika-dwallet-anchor = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
anchor-lang = "1"

Requires Anchor CLI 1.x for build/deploy tooling. See the Anchor framework guide for usage details.

For Off-Chain Clients (gRPC)

[dependencies]
ika-grpc = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
ika-dwallet-types = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

For SDK Types (Account Readers, PDA Helpers)

[dependencies]
ika-sdk-types = { package = "ika-solana-sdk-types", git = "https://github.com/dwallet-labs/ika-pre-alpha" }

Pre-Alpha Environment

ResourceEndpoint
dWallet gRPChttps://pre-alpha-dev-1.ika.ika-network.net:443
Solana RPChttps://api.devnet.solana.com
Program IDTODO: will be updated after deployment

No local validator or MPC node setup needed – just connect to devnet and start building.

Quick Start

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.

Build your first dWallet-controlled program in 5 minutes.

1. Create a Solana Program

[package]
name = "my-dwallet-program"
version = "0.1.0"
edition = "2024"

[dependencies]
ika-dwallet-pinocchio = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
pinocchio = "0.10"
pinocchio-system = "0.5"

[lib]
crate-type = ["cdylib", "lib"]

2. Set Up the CPI Context

#![allow(unused)]
#![no_std]
fn main() {
extern crate alloc;

use pinocchio::{entrypoint, AccountView, Address, ProgramResult};
use ika_dwallet_pinocchio::DWalletContext;

entrypoint!(process_instruction);
pinocchio::nostd_panic_handler!();

pub const ID: Address = Address::new_from_array([5u8; 32]);
}

The DWalletContext provides CPI methods for interacting with the dWallet program:

#![allow(unused)]
fn main() {
let ctx = DWalletContext {
    dwallet_program,
    cpi_authority,
    caller_program,
    cpi_authority_bump,
};
}

3. Approve a Message

When your program’s conditions are met, call approve_message via CPI:

#![allow(unused)]
fn main() {
ctx.approve_message(
    message_approval,   // writable PDA to create
    dwallet,            // the dWallet account
    payer,              // rent payer
    system_program,     // system program
    message_hash,       // 32-byte hash of the message to sign
    user_pubkey,        // 32-byte user public key
    signature_scheme,   // 0=Ed25519, 1=Secp256k1, 2=Secp256r1
    bump,               // MessageApproval PDA bump
)?;
}

This creates a MessageApproval PDA on-chain. The Ika network detects it and produces a signature.

4. Transfer dWallet Authority

Before your program can approve messages, the dWallet’s authority must point to your program’s CPI authority PDA:

#![allow(unused)]
fn main() {
// Derive the CPI authority PDA
// Seeds: [b"__ika_cpi_authority"], program_id = YOUR_PROGRAM_ID
let (cpi_authority, _bump) = Address::find_program_address(
    &[b"__ika_cpi_authority"],
    &your_program_id,
);

// Transfer ownership (called by current authority, typically the dWallet creator)
ctx.transfer_dwallet(dwallet, cpi_authority.as_array())?;
}

5. Read the Signature

After the network signs, the MessageApproval account contains the signature:

OffsetFieldSize
139status1
140signature_len2
142signatureup to 128

Status values:

  • 0 = Pending (awaiting signature)
  • 1 = Signed (signature available)

What Happens Under the Hood

  1. Your program calls approve_message via CPI -> creates a MessageApproval PDA (status = Pending)
  2. The Ika network detects the MessageApproval account
  3. The NOA (Network Operated Authority) signs the message using 2PC-MPC
  4. The NOA calls CommitSignature to write the signature on-chain (status = Signed)
  5. Anyone can read the signature from the MessageApproval account

In test mode with the mock signer, step 3 uses a single Ed25519 keypair instead of real MPC.

Pre-Alpha Environment

ResourceEndpoint
dWallet gRPChttps://pre-alpha-dev-1.ika.ika-network.net:443
Solana NetworkDevnet (https://api.devnet.solana.com)
Program IDTODO: will be updated after deployment

Core Concepts

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.

dWallet

A dWallet is a distributed signing key controlled by a Solana account. The on-chain DWallet account stores the public key, curve type, and authority. The private key never exists in one place – it is split between the user and the Ika validator network via 2PC-MPC (two-party computation with multi-party computation).

DWallet account (on Solana):
  authority(32)        -- who can approve signing
  public_key(65)       -- the dWallet's public key (padded to 65 bytes)
  public_key_len(1)    -- actual public key length (32 or 33)
  curve(1)             -- Secp256k1(0), Secp256r1(1), Curve25519(2), Ristretto(3)
  is_imported(1)       -- whether the key was imported (vs created via DKG)

A dWallet can sign transactions on any blockchain – Bitcoin, Ethereum, Solana, etc. The curve and signature algorithm determine which chains are compatible.

Authority

The authority of a dWallet controls who can approve messages for signing. It can be:

  • A user wallet (direct signer) – the user calls approve_message directly
  • A CPI authority PDA – a program controls the dWallet and approves messages via CPI

Transferring authority is done via the TransferOwnership instruction.

CPI Authority PDA

Every program that wants to control a dWallet derives a CPI authority PDA:

Seeds: [b"__ika_cpi_authority"]
Program: YOUR_PROGRAM_ID

When a dWallet’s authority is set to your program’s CPI authority PDA, only your program can approve messages for that dWallet. The dWallet program verifies the CPI call chain to ensure the correct program is calling.

Message Approval

A MessageApproval is a PDA that represents a request to sign a specific message. When your program calls approve_message, it creates this PDA:

MessageApproval PDA:
  Seeds: ["message_approval", dwallet_pubkey, message_hash]
  Program: DWALLET_PROGRAM_ID

Fields:
  dwallet(32)            -- the dWallet to sign with
  message_hash(32)       -- hash of the message to sign
  user_pubkey(32)        -- user's public key
  signature_scheme(1)    -- Ed25519(0), Secp256k1(1), Secp256r1(2)
  caller_program(32)     -- which program approved this
  cpi_authority(32)      -- the CPI authority PDA that signed
  status(1)              -- Pending(0) or Signed(1)
  signature_len(2)       -- length of signature bytes
  signature(128)         -- the produced signature (padded)

The Ika network monitors for new MessageApproval accounts and produces signatures for those with status = Pending.

NOA (Network Operated Authority)

The NOA is a special keypair operated by the Ika network. In the pre-alpha, this is a single mock signer. In production, the NOA’s actions are backed by MPC consensus across all validators.

The NOA:

  • Initializes the dWallet program state (DWalletCoordinator, NetworkEncryptionKey)
  • Commits new dWallets after DKG (CommitDWallet)
  • Commits signatures after signing (CommitSignature)
  • Allocates presigns

Presign

A presign is a precomputed partial signature that speeds up the signing process. Presigns are generated in advance and consumed during signing.

There are two types:

  • Global presigns – can be used with any dWallet (allocated via Presign request)
  • dWallet-specific presigns – bound to a specific dWallet (allocated via PresignForDWallet request)

Presigns are managed via the gRPC API and are invisible to on-chain programs.

Gas Deposit

Programs that use dWallet instructions need sufficient SOL for rent. The payer account in each instruction pays for PDA creation rent. There is no separate deposit system in the pre-alpha – standard Solana rent rules apply.

Supported Curves and Signature Algorithms

CurveIDDescriptionMock Support
Secp256k10Bitcoin, EthereumYes
Secp256r11WebAuthn, secure enclavesNot yet
Curve255192Solana, Sui, general Ed25519Yes
Ristretto3Substrate, PolkadotNot yet
AlgorithmCompatible CurvesMock Support
ECDSASecp256k1Secp256k1Yes
ECDSASecp256r1Secp256r1Not yet
TaprootSecp256k1Not yet
EdDSACurve25519Yes
SchnorrkelSubstrateRistrettoNot yet
Hash SchemeDescriptionMock Support
Keccak256Ethereum, general purposeIgnored (mock signs directly)
SHA256Bitcoin, general purposeIgnored (mock signs directly)
DoubleSHA256Bitcoin transaction signingIgnored (mock signs directly)
SHA512Ed25519 signingIgnored (mock signs directly)
MerlinSchnorrkel/SubstrateIgnored (mock signs directly)

Note: In the pre-alpha mock, signature_algorithm and hash_scheme are accepted in the BCS payload but the mock ignores them — it signs directly with the raw key. The full Ika network will enforce the correct algorithm and hash scheme.

DKG (Distributed Key Generation)

DKG is the process of creating a new dWallet. The user and the Ika network jointly generate a key pair such that:

  • The user holds one share of the private key
  • The network collectively holds the other share
  • Neither party alone can produce a signature

The on-chain flow:

  1. User submits DKG request via gRPC
  2. Network runs 2PC-MPC DKG protocol
  3. NOA calls CommitDWallet to create the on-chain dWallet account
  4. The dWallet’s authority is set to the requesting user

Tutorial: Voting dWallet

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.

This tutorial builds a complete voting-controlled dWallet program on Solana. A proposal specifies a message to sign; when enough “yes” votes reach quorum, the program automatically approves the message for signing via CPI.

What You Will Build

A Solana program with two instructions:

InstructionDiscriminatorDescription
create_proposal0Creates a proposal PDA with a target dWallet, message hash, and quorum
cast_vote1Records a vote; when quorum is reached, CPI-calls approve_message

How It Works

  1. A dWallet is created and its authority transferred to the voting program’s CPI authority PDA
  2. The creator submits a proposal referencing the dWallet, message hash, and required quorum
  3. Voters cast yes/no votes – each vote creates a VoteRecord PDA (prevents double voting)
  4. When yes votes reach quorum, the program automatically CPI-calls approve_message on the dWallet program
  5. The Ika network detects the MessageApproval and produces a signature
  6. The proposal status changes to Approved

Key Concepts Covered

  • CPI authority pattern – transferring dWallet control to a program
  • DWalletContext – the CPI wrapper for calling dWallet instructions
  • approve_message – creating a MessageApproval PDA to trigger signing
  • PDA-based vote records – preventing double voting via account existence
  • Mollusk tests – unit testing individual instructions
  • E2E tests – full lifecycle against Solana devnet and the pre-alpha gRPC service

Source Code

The complete example is at chains/solana/examples/voting/.

voting/
  src/lib.rs          -- program logic (2 instructions)
  tests/mollusk.rs    -- Mollusk instruction-level tests
  e2e/src/main.rs     -- full E2E demo
  Cargo.toml

Prerequisites

Create the 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.

Cargo.toml

[package]
name = "ika-example-voting"
version = "0.1.0"
edition = "2024"

[dependencies]
ika-dwallet-pinocchio = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
pinocchio = "0.10"
pinocchio-system = "0.5"

[dev-dependencies]
mollusk-svm = "0.2"
solana-account = "2"
solana-instruction = "2"
solana-pubkey = "2"

[lib]
crate-type = ["cdylib", "lib"]

Key crates:

  • ika-dwallet-pinocchioDWalletContext CPI wrapper and CPI_AUTHORITY_SEED
  • pinocchio – zero-copy Solana program framework
  • pinocchio-systemCreateAccount CPI helper

lib.rs Skeleton

#![allow(unused)]
#![no_std]
fn main() {
extern crate alloc;

use pinocchio::{
    cpi::Signer,
    entrypoint,
    error::ProgramError,
    AccountView, Address, ProgramResult,
};
use pinocchio_system::instructions::CreateAccount;
use ika_dwallet_pinocchio::DWalletContext;

entrypoint!(process_instruction);
pinocchio::nostd_panic_handler!();

pub const ID: Address = Address::new_from_array([5u8; 32]);
}

Account Discriminators

#![allow(unused)]
fn main() {
const PROPOSAL_DISCRIMINATOR: u8 = 1;
const VOTE_RECORD_DISCRIMINATOR: u8 = 2;

const STATUS_OPEN: u8 = 0;
const STATUS_APPROVED: u8 = 1;
}

Proposal Account Layout

The Proposal PDA stores the dWallet reference, message hash, vote counts, and quorum:

Proposal PDA (seeds: ["proposal", proposal_id]):
  195 bytes total (2-byte header + 193 bytes data)
OffsetFieldSizeDescription
0discriminator11
1version11
2proposal_id32Unique proposal identifier
34dwallet32dWallet account pubkey
66message_hash32Hash of the message to sign
98user_pubkey32User public key for signing
130signature_scheme1Ed25519(0), Secp256k1(1), Secp256r1(2)
131creator32Proposal creator pubkey
163yes_votes4Yes vote count (LE u32)
167no_votes4No vote count (LE u32)
171quorum4Required yes votes (LE u32)
175status1Open(0) or Approved(1)
176msg_approval_bump1MessageApproval PDA bump
177bump1Proposal PDA bump
178_reserved16Reserved for future use

VoteRecord Account Layout

The VoteRecord PDA prevents double voting. Its existence proves the voter has already voted.

VoteRecord PDA (seeds: ["vote", proposal_id, voter]):
  69 bytes total (2-byte header + 67 bytes data)
OffsetFieldSizeDescription
0discriminator12
1version11
2voter32Voter pubkey
34proposal_id32Associated proposal
66vote1Yes(1) or No(0)
67bump1VoteRecord PDA bump

Instruction Dispatch

#![allow(unused)]
fn main() {
pub fn process_instruction(
    program_id: &Address,
    accounts: &[AccountView],
    data: &[u8],
) -> ProgramResult {
    let (discriminator, rest) = data
        .split_first()
        .ok_or(ProgramError::InvalidInstructionData)?;

    match *discriminator {
        0 => create_proposal(program_id, accounts, rest),
        1 => cast_vote(program_id, accounts, rest),
        _ => Err(ProgramError::InvalidInstructionData),
    }
}
}

Rent Calculation

The dWallet program uses a simple rent formula:

#![allow(unused)]
fn main() {
fn minimum_balance(data_len: usize) -> u64 {
    (data_len as u64 + 128) * 6960
}
}

Next Step

With the program skeleton in place, the next chapter implements the create_proposal instruction and the message approval flow.

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

OffsetFieldSize
0proposal_id32
32message_hash32
64user_pubkey32
96signature_scheme1
97quorum4
101message_approval_bump1
102bump1

Total: 103 bytes.

Accounts

#AccountWSDescription
0proposalyesnoProposal PDA (["proposal", proposal_id])
1dwalletnonodWallet account
2creatornoyesProposal creator (signer)
3payeryesyesRent payer
4system_programnonoSystem 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_hash and message_approval_bump so the CPI call can construct the correct MessageApproval PDA later
  • The user_pubkey and signature_scheme are passed through to approve_message when 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_hash must 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 the hash_scheme field in the gRPC Sign request.

The dWallet program verifies:

  1. The caller is a valid program (executable account)
  2. The CPI authority PDA matches the dWallet’s current authority
  3. The CPI authority is signed (via invoke_signed)

Next Step

With proposals created, the next chapter implements vote casting and the quorum-triggered CPI.

Cast Votes

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.

cast_vote Instruction

The cast_vote instruction records a vote and – when quorum is reached – triggers the CPI call to approve_message.

Instruction Data

OffsetFieldSize
0proposal_id32
32vote1
33vote_record_bump1
34cpi_authority_bump1

Total: 35 bytes. The vote field is 1 for yes and 0 for no.

Accounts (No Quorum Path)

#AccountWSDescription
0proposalyesnoProposal PDA
1vote_recordyesnoVoteRecord PDA (["vote", proposal_id, voter])
2voternoyesVoter (signer)
3payeryesyesRent payer
4system_programnonoSystem program

Additional Accounts (When Quorum Reached)

When the vote triggers quorum, 5 additional accounts are required:

#AccountWSDescription
5message_approvalyesnoMessageApproval PDA (to create via CPI)
6dwalletnonodWallet account
7caller_programnonoThis voting program (executable)
8cpi_authoritynonoCPI authority PDA (signer via invoke_signed)
9dwallet_programnonodWallet program

Implementation

The core logic:

#![allow(unused)]
fn main() {
fn cast_vote(
    program_id: &Address,
    accounts: &[AccountView],
    data: &[u8],
) -> ProgramResult {
    let proposal_id: [u8; 32] = data[0..32].try_into().unwrap();
    let vote = data[32];
    let vote_record_bump = data[33];
    let cpi_authority_bump = data[34];

    // 1. Verify proposal is open
    // 2. Create VoteRecord PDA (fails if already exists = double vote prevention)
    // 3. Update yes_votes or no_votes on the proposal
    // 4. If yes_votes >= quorum, trigger CPI

    // ... vote counting ...

    if yes_votes >= quorum {
        // Need additional accounts for CPI
        let message_approval = &accounts[5];
        let dwallet = &accounts[6];
        let caller_program = &accounts[7];
        let cpi_authority = &accounts[8];
        let dwallet_program = &accounts[9];

        // Build DWalletContext and call approve_message
        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,
        )?;

        // Mark proposal as approved
        prop_data[PROP_STATUS] = STATUS_APPROVED;
    }

    Ok(())
}
}

Double Vote Prevention

The VoteRecord PDA uses seeds ["vote", proposal_id, voter]. Since CreateAccount will fail if the account already exists (non-zero lamports), a voter cannot vote twice on the same proposal.

The CPI Call Chain

When quorum triggers approve_message, the call chain is:

Voting Program
  └── invoke_signed (CPI authority PDA signs)
        └── dWallet Program: approve_message
              ├── Verifies caller_program is executable
              ├── Verifies cpi_authority = PDA(["__ika_cpi_authority"], caller_program)
              ├── Verifies dwallet.authority == cpi_authority
              └── Creates MessageApproval PDA

The DWalletContext handles all of this – building the instruction data, assembling accounts, and calling invoke_signed with the correct seeds.

Client-Side Account Assembly

When constructing the transaction client-side, you need to know whether this vote will trigger quorum. If it will, include the extra 5 accounts:

#![allow(unused)]
fn main() {
let mut accounts = vec![
    AccountMeta::new(proposal_pda, false),
    AccountMeta::new(vote_record_pda, false),
    AccountMeta::new_readonly(voter.pubkey(), true),
    AccountMeta::new(payer.pubkey(), true),
    AccountMeta::new_readonly(system_program::id(), false),
];

// Include CPI accounts if this vote reaches quorum
if current_yes_votes + 1 >= quorum {
    accounts.extend_from_slice(&[
        AccountMeta::new(message_approval_pda, false),
        AccountMeta::new_readonly(dwallet_pda, false),
        AccountMeta::new_readonly(voting_program_id, false),
        AccountMeta::new_readonly(cpi_authority, false),
        AccountMeta::new_readonly(dwallet_program_id, false),
    ]);
}
}

Next Step

With voting and approval working, the next chapter shows how to verify the resulting signature.

Verify Signature

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.

Reading the Signature

After the Ika network signs, the MessageApproval account is updated with the signature. You can read it from any client.

MessageApproval Account Layout

OffsetFieldSizeDescription
0discriminator114
1version11
2dwallet32dWallet pubkey
34message_hash32Message hash that was signed
66user_pubkey32User public key
98signature_scheme1Ed25519(0), Secp256k1(1), Secp256r1(2)
99caller_program32Program that approved
131cpi_authority32CPI authority PDA
163(internal fields)
139status1Pending(0) or Signed(1)
140signature_len2Signature byte count (LE u16)
142signature128Signature bytes (padded)

Total: 287 bytes (2 + 285).

Polling for Signature Completion

#![allow(unused)]
fn main() {
use solana_rpc_client::rpc_client::RpcClient;

fn wait_for_signature(client: &RpcClient, message_approval: &Pubkey) -> Vec<u8> {
    loop {
        let data = client.get_account(message_approval).unwrap().data;

        let status = data[139];
        if status == 1 { // Signed
            let sig_len = u16::from_le_bytes(
                data[140..142].try_into().unwrap()
            ) as usize;
            return data[142..142 + sig_len].to_vec();
        }

        std::thread::sleep(Duration::from_millis(500));
    }
}
}

Signature Verification

The signature can be verified against the dWallet’s public key using standard cryptographic libraries. The verification algorithm depends on the signature scheme:

#![allow(unused)]
fn main() {
// Ed25519 verification example
use ed25519_dalek::{Signature, VerifyingKey};

let verifying_key = VerifyingKey::from_bytes(&dwallet_public_key)?;
let signature = Signature::from_bytes(&signature_bytes)?;
verifying_key.verify_strict(&message_hash, &signature)?;
}

For Secp256k1 (Bitcoin/Ethereum):

#![allow(unused)]
fn main() {
use secp256k1::{Message, PublicKey, Secp256k1, ecdsa::Signature};

let secp = Secp256k1::verification_only();
let pubkey = PublicKey::from_slice(&dwallet_public_key)?;
let message = Message::from_digest(message_hash);
let signature = Signature::from_compact(&signature_bytes)?;
secp.verify_ecdsa(&message, &signature, &pubkey)?;
}

E2E Flow Summary

The complete lifecycle from the E2E demo:

1. Create dWallet (CommitDWallet)                    → dWallet PDA created
2. Transfer authority to CPI PDA                     → dWallet.authority = CPI PDA
3. Create proposal (message_hash, quorum=3)          → Proposal PDA created
4. Vote 1: Alice votes YES                           → VoteRecord created, yes_votes=1
5. Vote 2: Bob votes YES                             → VoteRecord created, yes_votes=2
6. Vote 3: Charlie votes YES (triggers quorum)       → MessageApproval created (Pending)
7. NOA signs and commits                             → MessageApproval updated (Signed)
8. Read signature from MessageApproval               → 64-byte Ed25519 signature

Next Step

The next chapter covers testing the voting program at different levels.

Testing

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.

The voting example includes tests at two levels: Mollusk (instruction-level) and E2E (full lifecycle).

Mollusk Tests

Mollusk tests verify individual instructions in isolation. No validator needed.

#![allow(unused)]
fn main() {
use mollusk_svm::Mollusk;
use solana_instruction::Instruction;

fn setup() -> (Mollusk, Pubkey) {
    let program_id = Pubkey::new_unique();
    let mollusk = Mollusk::new(&program_id, PROGRAM_PATH);
    (mollusk, program_id)
}

#[test]
fn test_create_proposal_success() {
    let (mollusk, program_id) = setup();
    let creator = Pubkey::new_unique();
    let proposal_id = [0x01u8; 32];

    let (proposal_pda, proposal_bump) =
        Pubkey::find_program_address(&[b"proposal", &proposal_id], &program_id);

    let ix = build_create_proposal_ix(/* ... */);
    let result = mollusk.process_instruction(&ix, &accounts);

    assert!(result.program_result.is_ok());
    let prop_data = &result.resulting_accounts[0].1.data;
    assert_eq!(prop_data[0], 1); // discriminator
    assert_eq!(read_u32(prop_data, 171), 3); // quorum
}
}

What to Test with Mollusk

TestWhat It Verifies
test_create_proposal_successProposal PDA created with correct fields
test_create_proposal_already_existsFails when proposal account is non-empty
test_cast_vote_yes_successVote recorded, yes_votes incremented
test_cast_vote_no_successNo vote recorded, no_votes incremented
test_cast_vote_double_vote_failsSecond vote by same voter fails
test_cast_vote_closed_proposal_failsVoting on approved proposal fails

Mollusk is fast (no network, no runtime startup) and tests instruction logic in isolation. However, it cannot test the CPI path (quorum triggering approve_message) because it runs a single program.

E2E Tests

The E2E demo runs the full lifecycle against Solana devnet and the pre-alpha dWallet gRPC service at pre-alpha-dev-1.ika.ika-network.net:443:

cargo run -p e2e-voting -- <DWALLET_PROGRAM_ID> <VOTING_PROGRAM_ID>

The E2E test performs all 7 steps:

  1. Wait for mock to initialize program state (DWalletCoordinator + NEK)
  2. Create dWallet via CommitDWallet
  3. Transfer authority to voting program’s CPI PDA
  4. Create a voting proposal (quorum = 3)
  5. Cast 3 YES votes (last triggers approve_message CPI)
  6. Verify MessageApproval PDA exists with status = Pending
  7. Sign with mock and verify signature on-chain

Assertions

The E2E test verifies:

  • dWallet authority matches CPI PDA after transfer
  • Proposal yes_votes = 3 and status = Approved after quorum
  • MessageApproval exists with correct dwallet and message_hash
  • Signature is committed and readable

See chains/solana/examples/voting/e2e/src/main.rs for the full implementation.

Running Tests

# Mollusk tests (fast, no validator)
cargo test -p ika-example-voting

# E2E (runs against devnet)
cd chains/solana/examples/voting/e2e
cargo run -- <DWALLET_PROGRAM_ID> <VOTING_PROGRAM_ID>

dWallet Accounts

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.

Overview

A dWallet is an on-chain account that represents a distributed signing key. It is created through Distributed Key Generation (DKG) and stored as a PDA owned by the dWallet program.

DWallet Account Layout

DWallet PDA:
  Seeds: ["dwallet", curve_byte, public_key_bytes]
  Program: DWALLET_PROGRAM_ID
OffsetFieldSizeDescription
0discriminator1Account type identifier
1version11
2authority32Who can approve messages (user or CPI PDA)
34public_key65dWallet public key (padded to 65 bytes)
99public_key_len1Actual public key length (32 or 33)
100curve1Curve type identifier
101is_imported1Whether the key was imported vs created

The authority field determines who can call approve_message for this dWallet:

  • A user pubkey – the user signs the approve_message instruction directly
  • A CPI authority PDA – a program controls the dWallet via CPI

Creating a dWallet

dWallets are created through the gRPC API, not directly on-chain. The flow:

  1. User sends a DKG request via gRPC with their key share
  2. The Ika network runs the 2PC-MPC DKG protocol
  3. The NOA calls CommitDWallet on-chain to create the dWallet account
  4. The dWallet’s authority is set to the user
#![allow(unused)]
fn main() {
// Client-side: request DKG via gRPC
let request = DWalletRequest::DKG {
    dwallet_network_encryption_public_key: nek_bytes,
    curve: DWalletCurve::Secp256k1,
    centralized_public_key_share_and_proof: user_share,
    encrypted_centralized_secret_share_and_proof: encrypted_share,
    encryption_key: enc_key,
    user_public_output: user_output,
    signer_public_key: signer_pk,
};
}

Transferring Authority

To give a program control over a dWallet, transfer its authority to the program’s CPI authority PDA:

#![allow(unused)]
fn main() {
// Derive the CPI authority PDA for your program
let (cpi_authority, _) = Pubkey::find_program_address(
    &[b"__ika_cpi_authority"],
    &your_program_id,
);

// TransferOwnership instruction (called by current authority)
let ix = Instruction::new_with_bytes(
    dwallet_program_id,
    &transfer_data, // [IX_TRANSFER_OWNERSHIP, new_authority(32)]
    vec![
        AccountMeta::new_readonly(current_authority, true), // signer
        AccountMeta::new(dwallet_pda, false),               // writable
    ],
);
}

After transfer, the dWallet’s authority field equals the CPI authority PDA, and only the owning program can approve messages.

Via CPI (Program-to-Program Transfer)

If a program already controls a dWallet, it can transfer authority to another program’s CPI PDA:

#![allow(unused)]
fn main() {
let ctx = DWalletContext {
    dwallet_program,
    cpi_authority,
    caller_program,
    cpi_authority_bump,
};

ctx.transfer_dwallet(dwallet, new_authority)?;
}

Supported Curves

CurveIDKey SizeChains
Secp256k1033 bytes (compressed)Bitcoin, Ethereum, BSC
Secp256r1133 bytes (compressed)WebAuthn, Apple Secure Enclave
Curve25519232 bytesSolana, Sui, general Ed25519
Ristretto332 bytesSubstrate, Polkadot

Reading dWallet Data Off-Chain

The ika-solana-sdk-types crate provides PDA derivation helpers:

#![allow(unused)]
fn main() {
use ika_sdk_types::pda::*;

let (system_state, _) = find_system_state_address(&program_id);
let (validator, _) = find_validator_address(&program_id, &identity);
let (validator_list, _) = find_validator_list_address(&program_id);
}

Message Approval

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.

Overview

Message approval is the core mechanism for requesting signatures from the Ika network. When you call approve_message, it creates a MessageApproval PDA on-chain. The network detects this account and produces a signature.

MessageApproval Account

MessageApproval PDA:
  Seeds: ["message_approval", dwallet_pubkey, message_hash]
  Program: DWALLET_PROGRAM_ID
  Total: 287 bytes (2 + 285)

The message_hash must be the keccak256 hash of the message you want signed:

#![allow(unused)]
fn main() {
let message_hash = solana_sdk::keccak::hash(message).to_bytes();
}
import { keccak_256 } from "@noble/hashes/sha3.js";
const messageHash = keccak_256(message);

This is consistent across all examples, the mock, and the gRPC service. Using any other hash function will result in a PDA mismatch when the network tries to commit the signature on-chain.

OffsetFieldSizeDescription
0discriminator114
1version11
2dwallet32dWallet account pubkey
34message_hash32Hash of the message to sign
66user_pubkey32User public key
98signature_scheme1Ed25519(0), Secp256k1(1), Secp256r1(2)
99caller_program32Program that created this approval
131cpi_authority32CPI authority PDA that signed
139status1Pending(0) or Signed(1)
140signature_len2Length of the signature (LE u16)
142signature128Signature bytes (padded)

Approval Flow

Direct Approval (User Signer)

When the dWallet’s authority is a user wallet:

User signs approve_message instruction
  → dWallet program verifies user == dwallet.authority
  → Creates MessageApproval PDA (status = Pending)

CPI Approval (Program Signer)

When the dWallet’s authority is a CPI authority PDA:

Your program calls DWalletContext::approve_message
  → invoke_signed with CPI authority seeds
  → dWallet program verifies:
      - caller_program is executable
      - cpi_authority == PDA(["__ika_cpi_authority"], caller_program)
      - dwallet.authority == cpi_authority
  → Creates MessageApproval PDA (status = Pending)

approve_message Instruction

Discriminator: 8

Instruction Data (67 bytes):

OffsetFieldSize
0discriminator1
1bump1
2message_hash32
34user_pubkey32
66signature_scheme1

Accounts (CPI path):

#AccountWSDescription
0message_approvalyesnoMessageApproval PDA (must be empty)
1dwalletnonodWallet account
2caller_programnonoCalling program (executable)
3cpi_authoritynoyesCPI authority PDA (signed via invoke_signed)
4payeryesyesRent payer
5system_programnonoSystem program

Signature Lifecycle

  1. Pending: Your program calls approve_message → MessageApproval created, status = 0, signature_len = 0
  2. gRPC Sign: You send a Sign request via gRPC with ApprovalProof referencing the on-chain approval. The network returns the 64-byte signature directly and commits it on-chain via CommitSignature.
  3. Signed: status = 1, signature bytes written at offset 142, readable by anyone.
Your program calls approve_message (CPI)
  → MessageApproval PDA created (status = Pending)
  → You send gRPC Sign request with ApprovalProof
  → Network signs and returns signature via gRPC
  → Network calls CommitSignature on-chain
  → status = Signed, signature available at offset 142

The signature is available both from the gRPC response and on-chain in the MessageApproval account.

CommitSignature Instruction

Called by the NOA to write the signature into the MessageApproval account.

Discriminator: 43

Instruction Data:

OffsetFieldSize
0discriminator1
1signature_len2
3signature128

Accounts:

#AccountWSDescription
0message_approvalyesnoMessageApproval PDA
1neknonoNetworkEncryptionKey PDA
2noanoyesNOA signer

Reading the Signature

#![allow(unused)]
fn main() {
let data = client.get_account(&message_approval_pda)?.data;

let status = data[139];
if status == 1 {
    let sig_len = u16::from_le_bytes(data[140..142].try_into().unwrap()) as usize;
    let signature = &data[142..142 + sig_len];
    // Use the signature
}
}

Idempotency

The same (dwallet, message_hash) pair always derives the same MessageApproval PDA. Attempting to create a MessageApproval that already exists will fail (the account is non-empty). This prevents duplicate signing requests.

CPI Framework

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.

DWalletContext

The ika-dwallet-pinocchio crate provides DWalletContext for calling dWallet instructions via CPI from Pinocchio programs.

#![allow(unused)]
fn main() {
use ika_dwallet_pinocchio::DWalletContext;

let ctx = DWalletContext {
    dwallet_program: &dwallet_program_account,
    cpi_authority: &cpi_authority_account,
    caller_program: &my_program_account,
    cpi_authority_bump: bump,
};
}
FieldTypeDescription
dwallet_program&AccountViewThe dWallet program account
cpi_authority&AccountViewYour program’s CPI authority PDA
caller_program&AccountViewYour program’s account (must be executable)
cpi_authority_bumpu8Bump seed for the CPI authority PDA

CPI Authority PDA

Every program derives its CPI authority from a single seed:

#![allow(unused)]
fn main() {
pub const CPI_AUTHORITY_SEED: &[u8] = b"__ika_cpi_authority";

// Derivation:
let (cpi_authority, bump) = Address::find_program_address(
    &[CPI_AUTHORITY_SEED],
    &your_program_id,
);
}

The dWallet program verifies this derivation during CPI calls.

Available Methods

approve_message

Creates a MessageApproval PDA requesting a signature.

#![allow(unused)]
fn main() {
ctx.approve_message(
    message_approval,   // writable, empty -- PDA to create
    dwallet,            // readonly -- the dWallet account
    payer,              // writable, signer -- rent payer
    system_program,     // readonly -- system program
    message_hash,       // [u8; 32] -- hash of message to sign
    user_pubkey,        // [u8; 32] -- user public key
    signature_scheme,   // u8 -- Ed25519(0), Secp256k1(1), Secp256r1(2)
    bump,               // u8 -- MessageApproval PDA bump
)?;
}

CPI instruction data: [8, bump, message_hash(32), user_pubkey(32), signature_scheme] = 67 bytes.

CPI accounts:

#AccountWS
0message_approvalyesno
1dwalletnono
2caller_programnono
3cpi_authoritynoyes
4payeryesyes
5system_programnono

transfer_dwallet

Transfers dWallet authority to a new pubkey.

#![allow(unused)]
fn main() {
ctx.transfer_dwallet(
    dwallet,         // writable -- the dWallet account
    new_authority,   // [u8; 32] -- new authority pubkey
)?;
}

CPI instruction data: [24, new_authority(32)] = 33 bytes.

CPI accounts:

#AccountWS
0caller_programnono
1cpi_authoritynoyes
2dwalletyesno

transfer_future_sign

Transfers the completion authority of a PartialUserSignature.

#![allow(unused)]
fn main() {
ctx.transfer_future_sign(
    partial_user_sig,          // writable -- partial signature account
    new_completion_authority,  // [u8; 32] -- new authority pubkey
)?;
}

CPI instruction data: [42, new_completion_authority(32)] = 33 bytes.

CPI accounts:

#AccountWS
0partial_user_sigyesno
1caller_programnono
2cpi_authoritynoyes

Signing Mechanism

All CPI methods use invoke_signed with the CPI authority seeds:

#![allow(unused)]
fn main() {
let bump_byte = [self.cpi_authority_bump];
let signer_seeds: [Seed; 2] = [
    Seed::from(CPI_AUTHORITY_SEED),
    Seed::from(&bump_byte),
];
let signer = Signer::from(&signer_seeds);

invoke_signed(&instruction, &accounts, &[signer])
}

The dWallet program verifies:

  1. caller_program is executable
  2. cpi_authority matches PDA(["__ika_cpi_authority"], caller_program)
  3. dwallet.authority == cpi_authority (for approve_message and transfer_dwallet)

Instruction Discriminators

InstructionDiscriminator
approve_message8
transfer_ownership24
transfer_future_sign42

Gas Deposits

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.

Pre-Alpha Rent Model

In the pre-alpha, dWallet accounts use standard Solana rent. There is no separate deposit or fee system – the payer account in each instruction pays the rent for newly created PDAs.

Rent Calculation

The dWallet program uses a simplified rent formula:

#![allow(unused)]
fn main() {
fn minimum_balance(data_len: usize) -> u64 {
    (data_len as u64 + 128) * 6960
}
}

This approximation of the Solana rent-exempt minimum is used for all PDA creation.

Rent Costs by Account Type

AccountSize (bytes)Approximate Rent (lamports)
DWallet~104~1,615,680
MessageApproval287~2,890,800
Proposal (voting example)195~2,250,480
VoteRecord (voting example)69~1,371,480

Payer Account

Every instruction that creates a PDA requires a payer account:

  • Must be writable and signer
  • Must have sufficient lamports to cover rent
  • Is debited via CreateAccount system instruction

In the voting example, the payer is typically the transaction fee payer:

#![allow(unused)]
fn main() {
CreateAccount {
    from: payer,
    to: proposal_account,
    lamports: minimum_balance(PROPOSAL_LEN),
    space: PROPOSAL_LEN as u64,
    owner: program_id,
}
.invoke_signed(&[signer])?;
}

Future: Production Gas Model

In production, the Ika network will have a gas model for signing operations. This may include:

  • Presign allocation fees
  • Signing operation fees
  • Staking requirements for validators

The exact model is not finalized. The pre-alpha uses no signing fees – only standard Solana rent for account creation.

SubmitTransaction

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.

Overview

SubmitTransaction is the primary gRPC RPC for all dWallet operations. It accepts a UserSignedRequest and returns a TransactionResponse.

The request type is determined by the DWalletRequest enum variant inside the BCS-serialized payload. This means a single RPC endpoint handles DKG, signing, presigning, and other operations.

Service Definition

service DWalletService {
  rpc SubmitTransaction(UserSignedRequest) returns (TransactionResponse);
  rpc GetPresigns(GetPresignsRequest) returns (GetPresignsResponse);
  rpc GetPresignsForDWallet(GetPresignsForDWalletRequest) returns (GetPresignsResponse);
}

UserSignedRequest

All mutation requests are wrapped in UserSignedRequest:

message UserSignedRequest {
  bytes user_signature = 1;      // BCS-serialized UserSignature enum
  bytes signed_request_data = 2; // BCS-serialized SignedRequestData
}
FieldTypeDescription
user_signaturebytesBCS-serialized UserSignature enum (signature + public key + scheme)
signed_request_databytesBCS-serialized SignedRequestData (the signed payload)

The user_signature covers the signed_request_data bytes – validators independently verify the signature.

Authentication

The UserSignature enum is self-contained: it carries both the signature bytes and the public key bytes, with the variant determining the scheme:

#![allow(unused)]
fn main() {
pub enum UserSignature {
    Ed25519 {
        signature: Vec<u8>,   // 64 bytes
        public_key: Vec<u8>,  // 32 bytes
    },
    Secp256k1 {
        signature: Vec<u8>,   // 64 bytes
        public_key: Vec<u8>,  // 33 bytes (compressed)
    },
    Secp256r1 {
        signature: Vec<u8>,   // 64 bytes
        public_key: Vec<u8>,  // 33 bytes (compressed)
    },
}
}

Signed Payload

The SignedRequestData struct contains the operation to perform:

#![allow(unused)]
fn main() {
pub struct SignedRequestData {
    pub session_identifier_preimage: [u8; 32],
    pub epoch: u64,
    pub chain_id: ChainId,
    pub intended_chain_sender: Vec<u8>,
    pub request: DWalletRequest,
}
}
FieldDescription
session_identifier_preimageRandom 32 bytes (uniqueness nonce)
epochCurrent Ika epoch (prevents cross-epoch replay)
chain_idSolana or Sui
intended_chain_senderUser’s address on the target chain
requestThe DWalletRequest enum variant

TransactionResponse

message TransactionResponse {
  bytes response_data = 1; // BCS-serialized TransactionResponseData
}

Deserialize response_data into TransactionResponseData to get the result:

#![allow(unused)]
fn main() {
pub enum TransactionResponseData {
    Signature { signature: Vec<u8> },
    Attestation {
        attestation_data: Vec<u8>,
        network_signature: Vec<u8>,
        network_pubkey: Vec<u8>,
        epoch: u64,
    },
    Presign {
        presign_id: Vec<u8>,
        presign_data: Vec<u8>,
        epoch: u64,
    },
    Error { message: String },
}
}

Client Usage

#![allow(unused)]
fn main() {
use ika_grpc::d_wallet_service_client::DWalletServiceClient;
use ika_grpc::UserSignedRequest;

let mut client = DWalletServiceClient::connect(
    "https://pre-alpha-dev-1.ika.ika-network.net:443"
).await?;

let resp = client.submit_transaction(UserSignedRequest {
    user_signature: bcs::to_bytes(&user_sig)?,
    signed_request_data: bcs::to_bytes(&signed_data)?,
}).await?;

let tx_response = resp.into_inner();
let result: TransactionResponseData = bcs::from_bytes(&tx_response.response_data)?;
}

Query RPCs

GetPresigns

Get all global presigns for a user.

message GetPresignsRequest {
  bytes user_pubkey = 1;
}

GetPresignsForDWallet

Get all presigns for a specific dWallet.

message GetPresignsForDWalletRequest {
  bytes user_pubkey = 1;
  bytes dwallet_id = 2;
}

GetPresignsResponse

message GetPresignsResponse {
  repeated PresignInfo presigns = 1;
}

message PresignInfo {
  bytes presign_id = 1;
  bytes dwallet_id = 2;
  uint32 curve = 3;
  uint32 signature_scheme = 4;
  uint64 epoch = 5;
}

Request Types

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.

DWalletRequest Enum

All operations are encoded as variants of the DWalletRequest enum, BCS-serialized inside SignedRequestData.request.

#![allow(unused)]
fn main() {
pub enum DWalletRequest {
    DKG { ... },                            // Supported in mock
    DKGWithPublicShare { ... },             // Supported in mock
    Sign { ... },                           // Supported in mock
    ImportedKeySign { ... },                // Supported in mock (same as Sign)
    Presign { ... },                        // Supported in mock
    PresignForDWallet { ... },              // Supported in mock
    ImportedKeyVerification { ... },        // Not yet implemented
    ReEncryptShare { ... },                 // Not yet implemented
    MakeSharePublic { ... },                // Not yet implemented
    FutureSign { ... },                     // Not yet implemented
    SignWithPartialUserSig { ... },         // Not yet implemented
    ImportedKeySignWithPartialUserSig { ... }, // Not yet implemented
}
}

Mock Support

RequestMock StatusNotes
DKGSupportedSecp256k1 and Curve25519 (Ed25519). Auto-commits dWallet on-chain and transfers authority to intended_chain_sender.
DKGWithPublicShareSupportedSame as DKG in mock.
SignSupportedSigns with Ed25519 or Secp256k1 key (based on DKG curve). signature_algorithm and hash_scheme are accepted but ignored by mock.
ImportedKeySignSupportedSame as Sign in mock.
PresignSupportedReturns random presign data.
PresignForDWalletSupportedSame as Presign in mock.
ImportedKeyVerificationNot implementedReturns error.
ReEncryptShareNot implementedReturns error.
MakeSharePublicNot implementedReturns error.
FutureSignNot implementedReturns error.
SignWithPartialUserSigNot implementedReturns error.
ImportedKeySignWithPartialUserSigNot implementedReturns error.

Supported Curves

CurveDKGSignNotes
Secp256k1YesYesBitcoin, Ethereum
Secp256r1NoNoComing soon
Curve25519YesYesSolana, Sui (Ed25519)
RistrettoNoNoComing soon

DKG

Create a new dWallet via Distributed Key Generation.

#![allow(unused)]
fn main() {
DWalletRequest::DKG {
    dwallet_network_encryption_public_key: Vec<u8>,
    curve: DWalletCurve,
    centralized_public_key_share_and_proof: Vec<u8>,
    encrypted_centralized_secret_share_and_proof: Vec<u8>,
    encryption_key: Vec<u8>,
    user_public_output: Vec<u8>,
    signer_public_key: Vec<u8>,
}
}
FieldDescription
dwallet_network_encryption_public_keyNetwork encryption key (from on-chain NEK account)
curveTarget curve (Secp256k1, Secp256r1, Curve25519, Ristretto)
centralized_public_key_share_and_proofUser’s public key share + ZK proof
encrypted_centralized_secret_share_and_proofEncrypted user secret share
encryption_keyEncryption key for the secret share
user_public_outputUser’s DKG public output
signer_public_keyUser’s signer key

Response: TransactionResponseData::Attestation with the DKG output and NOA attestation.

DKGWithPublicShare

Alternative DKG variant where the user provides a public share instead of an encrypted secret.

#![allow(unused)]
fn main() {
DWalletRequest::DKGWithPublicShare {
    dwallet_network_encryption_public_key: Vec<u8>,
    curve: DWalletCurve,
    centralized_public_key_share_and_proof: Vec<u8>,
    public_user_secret_key_share: Vec<u8>,
    signer_public_key: Vec<u8>,
}
}

Sign

Sign a message using an existing dWallet.

#![allow(unused)]
fn main() {
DWalletRequest::Sign {
    message: Vec<u8>,
    curve: DWalletCurve,
    signature_algorithm: DWalletSignatureAlgorithm,
    hash_scheme: DWalletHashScheme,
    presign_id: Vec<u8>,
    message_centralized_signature: Vec<u8>,
    approval_proof: ApprovalProof,
}
}
FieldDescription
messageRaw message bytes to sign
curvedWallet curve
signature_algorithmDWalletSignatureAlgorithm enum (see below)
hash_schemeDWalletHashScheme enum (see below)
presign_idID of a previously allocated presign
message_centralized_signatureUser’s partial signature
approval_proofOn-chain proof of message approval

Response: TransactionResponseData::Signature with the completed signature.

ApprovalProof

The approval proof ties the gRPC signing request to an on-chain MessageApproval:

#![allow(unused)]
fn main() {
pub enum ApprovalProof {
    Solana {
        transaction_signature: Vec<u8>, // Solana tx signature
        slot: u64,                       // Slot of the transaction
    },
    Sui {
        effects_certificate: Vec<u8>,    // Sui effects certificate
    },
}
}

Presign

Allocate a global presign (usable with any dWallet).

#![allow(unused)]
fn main() {
DWalletRequest::Presign {
    curve: DWalletCurve,
    signature_algorithm: DWalletSignatureAlgorithm,
}
}

Response: TransactionResponseData::Presign with presign ID and data.

PresignForDWallet

Allocate a presign bound to a specific dWallet.

#![allow(unused)]
fn main() {
DWalletRequest::PresignForDWallet {
    dwallet_id: Vec<u8>,
    curve: DWalletCurve,
    signature_algorithm: DWalletSignatureAlgorithm,
}
}

Cryptographic Parameter Enums

DWalletCurve

VariantDescription
Secp256k1Bitcoin, Ethereum
Secp256r1WebAuthn, secure enclaves
Curve25519Solana, Sui, Ed25519
RistrettoSubstrate, Polkadot

DWalletSignatureAlgorithm

VariantIndexUse For
ECDSASecp256k10Secp256k1 (Ethereum, Bitcoin)
ECDSASecp256r11Secp256r1 (WebAuthn)
Taproot2Secp256k1 (Bitcoin Taproot)
EdDSA3Curve25519 (Solana, Sui)
SchnorrkelSubstrate4Ristretto (Substrate, Polkadot)

DWalletHashScheme

VariantIndexUse For
Keccak2560Ethereum, general purpose
SHA2561Bitcoin, general purpose
DoubleSHA2562Bitcoin transaction signing
SHA5123Ed25519 signing
Merlin4Schnorrkel/Substrate

ChainId

VariantDescription
SolanaSolana blockchain
SuiSui blockchain

SignatureScheme

VariantValueKey Size
Ed25519032 bytes
Secp256k1133 bytes
Secp256r1233 bytes

Response Types

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.

TransactionResponseData

The SubmitTransaction RPC returns a TransactionResponse containing BCS-serialized TransactionResponseData:

#![allow(unused)]
fn main() {
pub enum TransactionResponseData {
    Signature { signature: Vec<u8> },
    Attestation {
        attestation_data: Vec<u8>,
        network_signature: Vec<u8>,
        network_pubkey: Vec<u8>,
        epoch: u64,
    },
    Presign {
        presign_id: Vec<u8>,
        presign_data: Vec<u8>,
        epoch: u64,
    },
    Error { message: String },
}
}

Response Variants

Signature

Returned for Sign and ImportedKeySign requests.

#![allow(unused)]
fn main() {
TransactionResponseData::Signature {
    signature: Vec<u8>,  // The completed signature bytes
}
}
FieldTypeDescription
signatureVec<u8>The completed digital signature

The signature is always 64 bytes:

  • ECDSASecp256k1 / ECDSASecp256r1: 64 bytes (r || s)
  • Taproot: 64 bytes (Schnorr signature)
  • EdDSA: 64 bytes (Ed25519 signature)
  • SchnorrkelSubstrate: 64 bytes

Attestation

Returned for DKG and DKGWithPublicShare requests.

#![allow(unused)]
fn main() {
TransactionResponseData::Attestation {
    attestation_data: Vec<u8>,     // DKG output data
    network_signature: Vec<u8>,    // NOA signature over the attestation
    network_pubkey: Vec<u8>,       // NOA public key
    epoch: u64,                     // Epoch of the attestation
}
}
FieldTypeDescription
attestation_dataVec<u8>The DKG output (public key, proofs, etc.)
network_signatureVec<u8>NOA’s signature attesting to the output
network_pubkeyVec<u8>NOA’s public key for verification
epochu64Ika epoch when the attestation was produced

The attestation_data + network_signature are passed to the CommitDWallet on-chain instruction to create the dWallet account.

Presign

Returned for Presign and PresignForDWallet requests.

#![allow(unused)]
fn main() {
TransactionResponseData::Presign {
    presign_id: Vec<u8>,     // Unique presign identifier
    presign_data: Vec<u8>,   // Precomputed signing data
    epoch: u64,               // Epoch of the presign
}
}
FieldTypeDescription
presign_idVec<u8>Unique identifier for this presign
presign_dataVec<u8>Precomputed data used during signing
epochu64Ika epoch when the presign was created

Store the presign_id – you will pass it to Sign requests later.

Error

Returned when the operation fails.

#![allow(unused)]
fn main() {
TransactionResponseData::Error {
    message: String,  // Human-readable error description
}
}

Always check for the Error variant before processing the response.

Deserialization Example

#![allow(unused)]
fn main() {
use ika_dwallet_types::TransactionResponseData;

let response = client.submit_transaction(request).await?;
let result: TransactionResponseData = bcs::from_bytes(&response.into_inner().response_data)?;

match result {
    TransactionResponseData::Signature { signature } => {
        println!("Got signature: {} bytes", signature.len());
    }
    TransactionResponseData::Attestation { attestation_data, network_signature, .. } => {
        println!("DKG complete, attestation: {} bytes", attestation_data.len());
        // Submit CommitDWallet on-chain with attestation_data + network_signature
    }
    TransactionResponseData::Presign { presign_id, .. } => {
        println!("Presign allocated: {}", hex::encode(&presign_id));
    }
    TransactionResponseData::Error { message } => {
        eprintln!("Error: {message}");
    }
}
}

PresignInfo (Query Response)

Returned by GetPresigns and GetPresignsForDWallet:

#![allow(unused)]
fn main() {
// Proto message
message PresignInfo {
  bytes presign_id = 1;
  bytes dwallet_id = 2;
  uint32 curve = 3;
  uint32 signature_scheme = 4;
  uint64 epoch = 5;
}
}
FieldTypeDescription
presign_idbytesUnique presign identifier
dwallet_idbytesAssociated dWallet (empty for global presigns)
curveu32Curve identifier
signature_schemeu32Signature scheme identifier
epochu64Epoch when allocated

Mollusk Tests

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.

Overview

Mollusk is the fastest way to test individual instructions in isolation. It runs a single instruction against pre-built account state – no validator, no network, no startup cost.

Mollusk is best for:

  • Verifying instruction data parsing
  • Checking signer and account validation
  • Testing discriminator handling
  • Validating PDA creation and field writes
  • Testing error conditions (double votes, closed proposals, missing signers)

Mollusk cannot test CPI calls (e.g., quorum triggering approve_message), because it runs a single program in isolation.

Setup

[dev-dependencies]
mollusk-svm = "0.2"
solana-account = "2"
solana-instruction = "2"
solana-pubkey = "2"
#![allow(unused)]
fn main() {
use mollusk_svm::Mollusk;
use solana_account::Account;
use solana_instruction::{AccountMeta, Instruction};
use solana_pubkey::Pubkey;

const PROGRAM_PATH: &str = concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../target/deploy/ika_example_voting"
);

fn setup() -> (Mollusk, Pubkey) {
    let program_id = Pubkey::new_unique();
    let mollusk = Mollusk::new(&program_id, PROGRAM_PATH);
    (mollusk, program_id)
}
}

Account Helpers

Pre-build account state for test inputs:

#![allow(unused)]
fn main() {
fn funded_account() -> Account {
    Account {
        lamports: 10_000_000_000,
        data: vec![],
        owner: SYSTEM_PROGRAM_ID,
        executable: false,
        rent_epoch: 0,
    }
}

fn program_account(owner: &Pubkey, data: Vec<u8>) -> Account {
    Account {
        lamports: ((data.len() as u64 + 128) * 6960).max(1),
        data,
        owner: *owner,
        executable: false,
        rent_epoch: 0,
    }
}

fn empty_account() -> Account {
    Account {
        lamports: 0,
        data: vec![],
        owner: SYSTEM_PROGRAM_ID,
        executable: false,
        rent_epoch: 0,
    }
}
}

Writing a Test

1. Build the Instruction

#![allow(unused)]
fn main() {
fn build_create_proposal_ix(
    program_id: &Pubkey,
    proposal: &Pubkey,
    dwallet: &Pubkey,
    creator: &Pubkey,
    payer: &Pubkey,
    proposal_id: [u8; 32],
    message_hash: [u8; 32],
    quorum: u32,
    bump: u8,
) -> Instruction {
    let mut ix_data = Vec::with_capacity(104);
    ix_data.push(0); // discriminator
    ix_data.extend_from_slice(&proposal_id);
    ix_data.extend_from_slice(&message_hash);
    ix_data.extend_from_slice(&[0u8; 32]); // user_pubkey
    ix_data.push(0); // signature_scheme
    ix_data.extend_from_slice(&quorum.to_le_bytes());
    ix_data.push(0); // message_approval_bump
    ix_data.push(bump);

    Instruction {
        program_id: *program_id,
        accounts: vec![
            AccountMeta::new(*proposal, false),
            AccountMeta::new_readonly(*dwallet, false),
            AccountMeta::new_readonly(*creator, true),
            AccountMeta::new(*payer, true),
            AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
        ],
        data: ix_data,
    }
}
}

2. Process and Assert

#![allow(unused)]
fn main() {
#[test]
fn test_create_proposal_success() {
    let (mollusk, program_id) = setup();
    let creator = Pubkey::new_unique();
    let payer = Pubkey::new_unique();
    let proposal_id = [0x01u8; 32];

    let (proposal_pda, bump) =
        Pubkey::find_program_address(&[b"proposal", &proposal_id], &program_id);

    let ix = build_create_proposal_ix(
        &program_id, &proposal_pda, &Pubkey::new_unique(),
        &creator, &payer, proposal_id, [0x42u8; 32], 3, bump,
    );

    let result = mollusk.process_instruction(
        &ix,
        &[
            (proposal_pda, empty_account()),
            (Pubkey::new_unique(), funded_account()),
            (creator, funded_account()),
            (payer, funded_account()),
            (SYSTEM_PROGRAM_ID, system_program_account()),
        ],
    );

    assert!(result.program_result.is_ok());

    let prop_data = &result.resulting_accounts[0].1.data;
    assert_eq!(prop_data[0], 1); // discriminator
    assert_eq!(prop_data[1], 1); // version
}
}

Test Patterns

Verify Error Conditions

#![allow(unused)]
fn main() {
#[test]
fn test_double_vote_fails() {
    let (mollusk, program_id) = setup();
    // Pre-populate VoteRecord (voter already voted)
    let existing_vr = build_vote_record_data(&voter, &proposal_id, 1, vr_bump);

    let result = mollusk.process_instruction(
        &ix,
        &[
            (proposal_pda, program_account(&program_id, proposal_data)),
            (vote_record_pda, program_account(&program_id, existing_vr)),
            // ...
        ],
    );

    assert!(result.program_result.is_err());
}
}

Verify Field Values

#![allow(unused)]
fn main() {
let prop_data = &result.resulting_accounts[0].1.data;
assert_eq!(read_u32(prop_data, 163), 1, "yes_votes = 1");
assert_eq!(read_u32(prop_data, 167), 0, "no_votes = 0");
assert_eq!(prop_data[175], 0, "status = Open");
}

Running Mollusk Tests

cargo test -p ika-example-voting

Tests run in milliseconds – no validator startup required.

LiteSVM Tests

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.

Overview

LiteSVM provides in-process Solana runtime testing. Unlike Mollusk, LiteSVM can:

  • Deploy multiple programs
  • Test CPI calls (e.g., your program calling the dWallet program)
  • Process multiple transactions in sequence
  • Simulate the full transaction lifecycle

LiteSVM is ideal for testing the integration between your program and the dWallet program, including the CPI authority pattern and approve_message calls.

Setup

[dev-dependencies]
litesvm = "0.4"
solana-sdk = "2"

Basic Test Structure

#![allow(unused)]
fn main() {
use litesvm::LiteSVM;
use solana_sdk::{
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
    signature::Keypair,
    signer::Signer,
    transaction::Transaction,
};

#[test]
fn test_voting_with_cpi() {
    let mut svm = LiteSVM::new();

    // Deploy the dWallet program
    let dwallet_program_id = Pubkey::new_unique();
    svm.deploy_program(
        dwallet_program_id,
        include_bytes!("path/to/ika_dwallet_program.so"),
    );

    // Deploy the voting program
    let voting_program_id = Pubkey::new_unique();
    svm.deploy_program(
        voting_program_id,
        include_bytes!("path/to/ika_example_voting.so"),
    );

    // Fund accounts
    let payer = Keypair::new();
    svm.airdrop(&payer.pubkey(), 10_000_000_000).unwrap();

    // ... test transactions ...
}
}

Testing the CPI Path

The key advantage of LiteSVM over Mollusk is testing the quorum -> approve_message CPI path:

#![allow(unused)]
fn main() {
// Step 1: Initialize dWallet program state (mock the NOA setup)
// Step 2: Create a dWallet
// Step 3: Transfer authority to voting program CPI PDA
// Step 4: Create a proposal
// Step 5: Cast votes until quorum triggers approve_message CPI

// After the final vote, verify MessageApproval was created
let ma_data = svm.get_account(&message_approval_pda).unwrap().data;
assert_eq!(ma_data[0], 14); // MessageApproval discriminator
assert_eq!(ma_data[139], 0); // status = Pending
}

When to Use LiteSVM vs Mollusk

FeatureMolluskLiteSVM
SpeedFastestFast
CPI testingNoYes
Multi-programNoYes
Account persistenceNoYes
Transaction sequencingNoYes
Error granularityInstruction levelTransaction level

Use Mollusk for unit-testing individual instructions. Use LiteSVM when you need to test cross-program interactions or multi-step flows.

Tips

  • Pre-populate accounts via svm.set_account() to skip setup transactions
  • Use svm.get_account() to read account data after transactions
  • Deploy both the dWallet program and your program to test the full CPI flow
  • The CPI authority PDA derivation must use b"__ika_cpi_authority" as the seed

E2E Tests

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.

Overview

E2E tests run the full dWallet lifecycle against Solana devnet and the pre-alpha dWallet gRPC service. This tests the complete flow including on-chain program execution, CPI, and signing.

Prerequisites

ResourceEndpoint
dWallet gRPChttps://pre-alpha-dev-1.ika.ika-network.net:443
Solana RPChttps://api.devnet.solana.com

Deploy your program to devnet, then run:

cargo run -p e2e-voting -- <DWALLET_PROGRAM_ID> <VOTING_PROGRAM_ID>

Override endpoints via environment variables:

RPC_URL=https://api.devnet.solana.com \
GRPC_URL=pre-alpha-dev-1.ika.ika-network.net:443 \
cargo run -p e2e-voting -- <DWALLET_PROGRAM_ID> <VOTING_PROGRAM_ID>

E2E Flow

The voting E2E demo performs 7 steps:

Step 1: Wait for Program Initialization

The mock signer creates:

  • DWalletCoordinator PDA (["dwallet_coordinator"]) – 116 bytes
  • NetworkEncryptionKey PDA (["network_encryption_key", noa_pubkey]) – 164 bytes
#![allow(unused)]
fn main() {
let (coordinator_pda, _) =
    Pubkey::find_program_address(&[b"dwallet_coordinator"], &dwallet_program_id);

poll_until(&client, &coordinator_pda, |data| {
    data.len() >= 116 && data[0] == 1 // DISC_COORDINATOR
}, Duration::from_secs(30));
}

Step 2: Create dWallet

The NOA commits a dWallet via CommitDWallet (discriminator 31):

#![allow(unused)]
fn main() {
let (dwallet_pda, dwallet_bump) = Pubkey::find_program_address(
    &[b"dwallet", &[curve], &public_key],
    &dwallet_program_id,
);
}

Step 3: Transfer Authority

Transfer dWallet authority to the voting program’s CPI PDA:

#![allow(unused)]
fn main() {
let (cpi_authority, _) = Pubkey::find_program_address(
    &[b"__ika_cpi_authority"],
    &voting_program_id,
);
}

Step 4: Create Proposal

Create a proposal with quorum = 3:

#![allow(unused)]
fn main() {
let message = b"Transfer 100 USDC to treasury";
let message_hash = keccak256(message);
}

Step 5: Cast 3 Votes

Three voters (Alice, Bob, Charlie) each cast YES. Charlie’s vote reaches quorum and triggers the approve_message CPI.

The last vote transaction includes 10 accounts (5 base + 5 CPI accounts).

Step 6: Verify MessageApproval

#![allow(unused)]
fn main() {
let ma_data = client.get_account(&message_approval_pda)?.data;
assert_eq!(ma_data[0], 14);  // discriminator
assert_eq!(ma_data[139], 0); // status = Pending
}

Step 7: Sign and Verify

The mock signer signs the message and calls CommitSignature (discriminator 43):

#![allow(unused)]
fn main() {
let signed_data = client.get_account(&message_approval_pda)?.data;
let sig_len = u16::from_le_bytes(signed_data[140..142].try_into().unwrap()) as usize;
let signature = &signed_data[142..142 + sig_len];
}

Key PDA Seeds

PDASeedsProgram
DWalletCoordinator["dwallet_coordinator"]dWallet
NetworkEncryptionKey["network_encryption_key", noa_pubkey]dWallet
DWallet["dwallet", curve, public_key]dWallet
MessageApproval["message_approval", dwallet, message_hash]dWallet
CPI Authority["__ika_cpi_authority"]Your program
Proposal["proposal", proposal_id]Voting
VoteRecord["vote", proposal_id, voter]Voting

Key Discriminators

InstructionDiscriminator
CreateProposal (voting)0
CastVote (voting)1
ApproveMessage (dWallet)8
TransferOwnership (dWallet)24
CommitDWallet (dWallet)31
CommitSignature (dWallet)43

Running the E2E Test

cargo run -p e2e-voting -- <DWALLET_PROGRAM_ID> <VOTING_PROGRAM_ID>

Expected output:

=== dWallet Voting E2E Demo ===

[Setup] Funding payer...
  > Payer: <pubkey>
[1/7] Creating dWallet via CommitDWallet...
  > dWallet created: <pubkey>
[2/7] Transferring dWallet authority to voting program...
  > Authority transferred to CPI PDA: <pubkey>
[3/7] Creating voting proposal (quorum=3)...
  > Proposal: <pubkey>
[4/7] Vote 1/3: Alice casts YES...
[4/7] Vote 2/3: Bob casts YES...
[4/7] Vote 3/3: Charlie casts YES...
  > Proposal approved (yes_votes=3)
[5/7] Verifying MessageApproval on-chain...
  > MessageApproval: <pubkey>
[6/7] Signing message with NOA key and committing on-chain...
  > Signature committed on-chain!
[7/7] Reading signature from MessageApproval...
  > Signature: <hex>

=== E2E Test Passed! ===

Pinocchio Framework

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.

The ika-dwallet-pinocchio crate provides a Pinocchio-native CPI SDK for the dWallet program. Pinocchio is the highest-performance Solana program framework — #![no_std], zero-copy, minimal CU overhead.

Dependencies

[dependencies]
ika-dwallet-pinocchio = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
pinocchio = "0.10"
pinocchio-system = "0.5"

[lib]
crate-type = ["cdylib", "lib"]

DWalletContext

#![allow(unused)]
fn main() {
use ika_dwallet_pinocchio::DWalletContext;

let ctx = DWalletContext {
    dwallet_program: &dwallet_program_account,
    cpi_authority: &cpi_authority_account,
    caller_program: &my_program_account,
    cpi_authority_bump: bump,
};
}
FieldTypeDescription
dwallet_program&AccountViewThe dWallet program account
cpi_authority&AccountViewYour program’s CPI authority PDA
caller_program&AccountViewYour program’s account (must be executable)
cpi_authority_bumpu8Bump seed for the CPI authority PDA

CPI Authority PDA

Every program that controls a dWallet derives a single CPI authority PDA:

#![allow(unused)]
fn main() {
use ika_dwallet_pinocchio::CPI_AUTHORITY_SEED;

// Derive at runtime:
let (cpi_authority, bump) = pinocchio::Address::find_program_address(
    &[CPI_AUTHORITY_SEED],
    program_id,
);
}

Methods

approve_message

Creates a MessageApproval PDA requesting a signature from the Ika network.

#![allow(unused)]
fn main() {
ctx.approve_message(
    message_approval,   // &AccountView — PDA to create
    dwallet,            // &AccountView — the dWallet
    payer,              // &AccountView — pays rent
    system_program,     // &AccountView
    message_hash,       // [u8; 32]
    user_pubkey,        // [u8; 32]
    signature_scheme,   // u8: 0=Ed25519, 1=Secp256k1, 2=Secp256r1
    bump,               // u8 — MessageApproval PDA bump
)?;
}

transfer_dwallet

Transfers dWallet authority to a new pubkey (or another program’s CPI PDA).

#![allow(unused)]
fn main() {
ctx.transfer_dwallet(dwallet, &new_authority_bytes)?;
}

transfer_future_sign

Transfers the completion authority of a PartialUserSignature.

#![allow(unused)]
fn main() {
ctx.transfer_future_sign(partial_user_sig, &new_authority_bytes)?;
}

Example: Voting dWallet

Source: chains/solana/examples/voting/pinocchio/

#![allow(unused)]
#![no_std]
fn main() {
extern crate alloc;

use pinocchio::{entrypoint, AccountView, Address, ProgramResult};
use ika_dwallet_pinocchio::DWalletContext;

entrypoint!(process_instruction);
pinocchio::nostd_panic_handler!();

fn process_instruction(
    program_id: &Address,
    accounts: &[AccountView],
    data: &[u8],
) -> ProgramResult {
    match data[0] {
        0 => create_proposal(program_id, accounts, &data[1..]),
        1 => cast_vote(program_id, accounts, &data[1..]),
        _ => Err(pinocchio::error::ProgramError::InvalidInstructionData),
    }
}
}

When quorum is reached in cast_vote, the program constructs a DWalletContext and calls approve_message — authorizing the Ika network to sign.

When to Use Pinocchio

ConsiderationPinocchioNativeAnchor
CU efficiencyBestGoodGood
Binary sizeSmallestMediumLargest
no_std supportYesNoNo
Account validationManualManualDeclarative
Learning curveSteepestMediumEasiest

Choose Pinocchio when you need maximum CU efficiency, smallest binary size, or no_std compatibility.

Native Framework (solana-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.

The ika-dwallet-native crate provides a CPI SDK using Solana’s standard solana-program crate. No framework lock-in — just raw AccountInfo and invoke_signed.

Dependencies

[dependencies]
ika-dwallet-native = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
solana-program = "2.2"
solana-system-interface = "1"

[lib]
crate-type = ["cdylib", "lib"]

DWalletContext

#![allow(unused)]
fn main() {
use ika_dwallet_native::DWalletContext;

let ctx = DWalletContext {
    dwallet_program: &accounts.dwallet_program,
    cpi_authority: &accounts.cpi_authority,
    caller_program: &accounts.program,
    cpi_authority_bump: bump,
};
}
FieldTypeDescription
dwallet_program&AccountInfo<'info>The dWallet program account
cpi_authority&AccountInfo<'info>Your program’s CPI authority PDA
caller_program&AccountInfo<'info>Your program’s account (must be executable)
cpi_authority_bumpu8Bump seed for the CPI authority PDA

CPI Authority PDA

#![allow(unused)]
fn main() {
use ika_dwallet_native::CPI_AUTHORITY_SEED;
use solana_program::pubkey::Pubkey;

let (cpi_authority, bump) = Pubkey::find_program_address(
    &[CPI_AUTHORITY_SEED],
    &your_program_id,
);
}

Methods

approve_message

#![allow(unused)]
fn main() {
ctx.approve_message(
    &message_approval,  // &AccountInfo — PDA to create
    &dwallet,           // &AccountInfo — the dWallet
    &payer,             // &AccountInfo — pays rent
    &system_program,    // &AccountInfo
    message_hash,       // [u8; 32]
    user_pubkey,        // [u8; 32]
    signature_scheme,   // u8
    bump,               // u8 — MessageApproval PDA bump
)?;
}

transfer_dwallet

#![allow(unused)]
fn main() {
ctx.transfer_dwallet(&dwallet, &new_authority)?;
}

transfer_future_sign

#![allow(unused)]
fn main() {
ctx.transfer_future_sign(&partial_user_sig, &new_authority)?;
}

Example: Voting dWallet

Source: chains/solana/examples/voting/native/

#![allow(unused)]
fn main() {
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    program::invoke_signed,
    program_error::ProgramError,
    pubkey::Pubkey,
    rent::Rent,
    sysvar::Sysvar,
};
use ika_dwallet_native::DWalletContext;

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    match instruction_data[0] {
        0 => create_proposal(program_id, accounts, &instruction_data[1..]),
        1 => cast_vote(program_id, accounts, &instruction_data[1..]),
        _ => Err(ProgramError::InvalidInstructionData),
    }
}
}

Uses next_account_info() for account iteration, Rent::get()?.minimum_balance() for rent, and system_instruction::create_account + invoke_signed for PDA creation.

When quorum is reached, the program constructs a DWalletContext and calls approve_message.

When to Use Native

ConsiderationPinocchioNativeAnchor
CU efficiencyBestGoodGood
std libraryNo (no_std)YesYes
Framework dependencypinocchiosolana-programanchor-lang
Account validationManualManualDeclarative
Migration from existingRewriteMinimalRewrite

Choose Native when you have an existing solana-program codebase, want std library access, or prefer no framework lock-in beyond Solana’s standard SDK.

Differences from Pinocchio

PinocchioNative
Account type&AccountView&AccountInfo<'info>
Entrypointpinocchio::entrypoint!()solana_program::entrypoint!()
CPIpinocchio::cpi::invoke_signedsolana_program::program::invoke_signed
PDA creationpinocchio_system::CreateAccountsystem_instruction::create_account + invoke_signed
Rentminimum_balance() helperRent::get()?.minimum_balance()
std#![no_std]Full std
Account iterationArray indexingnext_account_info()

Both SDKs use the same CPI authority seed, instruction discriminators, and account layouts. Programs built with either are fully interoperable.

Anchor Framework (v1.0.0)

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.

The ika-dwallet-anchor crate provides an Anchor-native CPI SDK for the dWallet program. It is the Anchor equivalent of ika-dwallet-pinocchio.

Anchor v1.0.0: This SDK uses Anchor’s first stable release (release notes). Key v1 features used:

  • UncheckedAccount instead of raw AccountInfo in #[derive(Accounts)]
  • InitSpace derive for automatic space calculation
  • Single #[error_code] block per program
  • Solana 3.x compatibility

Dependencies

[dependencies]
ika-dwallet-anchor = { git = "https://github.com/dwallet-labs/ika-pre-alpha" }
anchor-lang = "1"

[lib]
crate-type = ["cdylib", "lib"]

Note: Anchor v1 requires Solana CLI 3.x and the Anchor CLI 1.x. Install with:

cargo install --git https://github.com/coral-xyz/anchor avm --force
avm install 1.0.0
avm use 1.0.0

DWalletContext

The DWalletContext struct wraps the accounts needed for CPI calls to the dWallet program.

#![allow(unused)]
fn main() {
use ika_dwallet_anchor::{DWalletContext, CPI_AUTHORITY_SEED};

let ctx = DWalletContext {
    dwallet_program: dwallet_program.to_account_info(),
    cpi_authority: cpi_authority.to_account_info(),
    caller_program: program.to_account_info(),
    cpi_authority_bump: bump,
};
}
FieldTypeDescription
dwallet_programAccountInfoThe dWallet program account
cpi_authorityAccountInfoYour program’s CPI authority PDA
caller_programAccountInfoYour program’s account (must be executable)
cpi_authority_bumpu8Bump seed for the CPI authority PDA

CPI Authority PDA

Same derivation as Pinocchio – a single seed per program:

#![allow(unused)]
fn main() {
use ika_dwallet_anchor::CPI_AUTHORITY_SEED;

let (cpi_authority, bump) = Pubkey::find_program_address(
    &[CPI_AUTHORITY_SEED],
    &your_program_id,
);
}

Methods

approve_message

Creates a MessageApproval PDA requesting a signature.

#![allow(unused)]
fn main() {
ctx.approve_message(
    &message_approval.to_account_info(),
    &dwallet.to_account_info(),
    &payer.to_account_info(),
    &system_program.to_account_info(),
    message_hash,       // [u8; 32]
    user_pubkey,        // [u8; 32]
    signature_scheme,   // u8: 0=Ed25519, 1=Secp256k1, 2=Secp256r1
    bump,               // MessageApproval PDA bump
)?;
}

transfer_dwallet

Transfers dWallet authority to a new pubkey.

#![allow(unused)]
fn main() {
ctx.transfer_dwallet(
    &dwallet.to_account_info(),
    &new_authority,     // &Pubkey
)?;
}

transfer_future_sign

Transfers the completion authority of a PartialUserSignature.

#![allow(unused)]
fn main() {
ctx.transfer_future_sign(
    &partial_user_sig.to_account_info(),
    &new_authority,     // &Pubkey
)?;
}

Example: Voting-Controlled dWallet

The voting-anchor example demonstrates the full pattern. Proposals reference a dWallet whose authority has been transferred to this program’s CPI authority PDA. When enough yes-votes reach quorum, the program CPI-calls approve_message.

Source: chains/solana/examples/voting-anchor/

Account Definitions (Anchor v1 style)

#![allow(unused)]
fn main() {
use anchor_lang::prelude::*;

#[account]
#[derive(InitSpace)]    // v1: auto-calculates space
pub struct Proposal {
    pub proposal_id: [u8; 32],
    pub dwallet: Pubkey,
    pub message_hash: [u8; 32],
    pub user_pubkey: [u8; 32],
    pub signature_scheme: u8,
    pub creator: Pubkey,
    pub yes_votes: u32,
    pub no_votes: u32,
    pub quorum: u32,
    pub status: ProposalStatus,
    pub message_approval_bump: u8,
}

#[account]
#[derive(InitSpace)]
pub struct VoteRecord {
    pub voter: Pubkey,
    pub proposal_id: [u8; 32],
    pub vote: bool,
}
}

Account Validation (Anchor v1 constraints)

#![allow(unused)]
fn main() {
#[derive(Accounts)]
#[instruction(proposal_id: [u8; 32])]
pub struct CreateProposal<'info> {
    #[account(
        init,
        payer = payer,
        space = 8 + Proposal::INIT_SPACE,   // v1: InitSpace derive
        seeds = [b"proposal", proposal_id.as_ref()],
        bump,
    )]
    pub proposal: Account<'info, Proposal>,
    /// CHECK: dWallet account (owned by dWallet program)
    pub dwallet: UncheckedAccount<'info>,    // v1: UncheckedAccount
    pub creator: Signer<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
}

CPI on Quorum (cast_vote)

#![allow(unused)]
fn main() {
pub fn cast_vote(
    ctx: Context<CastVote>,
    proposal_id: [u8; 32],
    vote: bool,
    cpi_authority_bump: u8,
) -> Result<()> {
    let proposal = &mut ctx.accounts.proposal;
    require!(proposal.status == ProposalStatus::Open, VotingError::ProposalClosed);

    if vote {
        proposal.yes_votes = proposal.yes_votes.checked_add(1)
            .ok_or(VotingError::ProposalClosed)?;
    } else {
        proposal.no_votes = proposal.no_votes.checked_add(1)
            .ok_or(VotingError::ProposalClosed)?;
    }

    // Quorum reached → CPI approve_message
    if proposal.yes_votes >= proposal.quorum {
        let dwallet_ctx = DWalletContext {
            dwallet_program: ctx.accounts.dwallet_program.to_account_info(),
            cpi_authority: ctx.accounts.cpi_authority.to_account_info(),
            caller_program: ctx.accounts.program.to_account_info(),
            cpi_authority_bump,
        };

        dwallet_ctx.approve_message(
            &ctx.accounts.message_approval.to_account_info(),
            &ctx.accounts.dwallet.to_account_info(),
            &ctx.accounts.payer.to_account_info(),
            &ctx.accounts.system_program.to_account_info(),
            proposal.message_hash,
            proposal.user_pubkey,
            proposal.signature_scheme,
            proposal.message_approval_bump,
        )?;

        proposal.status = ProposalStatus::Approved;
    }

    Ok(())
}
}

Error Definition (v1: single block only)

#![allow(unused)]
fn main() {
#[error_code]
pub enum VotingError {
    #[msg("Proposal is not open for voting")]
    ProposalClosed,
}
}

Anchor v1 enforces a single #[error_code] block per program. Multiple blocks now produce a compile-time error.

Key Patterns

PDA-based proposals — each proposal is a PDA seeded by [b"proposal", proposal_id].

One vote per voter — vote records are PDAs seeded by [b"vote", proposal_id, voter_pubkey], preventing double-voting via Anchor’s init constraint.

Automatic CPI on quorum — when yes_votes >= quorum, cast_vote constructs a DWalletContext and calls approve_message in the same transaction.

UncheckedAccount for cross-program accounts — dWallet-program-owned accounts use UncheckedAccount with /// CHECK: comments (v1 best practice, replacing raw AccountInfo).

Anchor v1.0.0 Migration Notes

If migrating from Anchor 0.30/0.31:

ChangeBefore (0.30)After (v1.0.0)
Space calculationManual 8 + 32 + 32 + ...8 + MyAccount::INIT_SPACE (InitSpace derive)
Raw AccountInfoAccountInfo<'info> in derivesUncheckedAccount<'info> with /// CHECK:
Error blocksMultiple #[error_code] allowedSingle #[error_code] per program
CPI programCpiContext::new(program.to_account_info(), ...)CpiContext::new(Program::id(), ...) or direct
Solana versionSolana 2.xSolana 3.x

Differences from Pinocchio SDK

PinocchioAnchor v1
Account types&AccountViewAccountInfo / UncheckedAccount
Error handlingProgramResultanchor_lang::Result<()>
CPI signingpinocchio::cpi::invoke_signedanchor_lang::solana_program::program::invoke_signed
EntrypointManual entrypoint!() macro#[program] attribute macro
Account validationManual checks#[derive(Accounts)] constraints
Spacecore::mem::size_of::<T>()8 + T::INIT_SPACE (InitSpace derive)
Best forMaximum CU efficiencyRapid development, safety

Both SDKs use the same CPI authority seed (b"__ika_cpi_authority"), the same instruction discriminators, and the same account layouts. Programs built with either SDK are fully interoperable.

TypeScript Client

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.

The @ika.xyz/pre-alpha-solana-client package provides a TypeScript client for interacting with the dWallet program on Solana. Built on @solana/kit (web3.js v2).

Installation

bun add @ika.xyz/pre-alpha-solana-client @solana/kit

Or with npm:

npm install @ika.xyz/pre-alpha-solana-client @solana/kit

Quick Start

import {
  address,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  generateKeyPairSigner,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
} from "@solana/kit";

const RPC_URL = "https://api.devnet.solana.com";
const WS_URL = "wss://api.devnet.solana.com";
const DWALLET_PROGRAM = address("TODO: program ID after deployment");

const rpc = createSolanaRpc(RPC_URL);
const rpcSubscriptions = createSolanaRpcSubscriptions(WS_URL);
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });

Building Transactions

Approve Message

Build an ApproveMessage instruction to authorize the Ika network to sign a message:

import { getAddressEncoder, getProgramDerivedAddress, getUtf8Encoder } from "@solana/kit";

const utf8 = getUtf8Encoder();
const addressEncoder = getAddressEncoder();

// Derive MessageApproval PDA
const [messageApprovalPda, messageApprovalBump] = await getProgramDerivedAddress({
  seeds: [
    utf8.encode("message_approval"),
    addressEncoder.encode(dwalletAddress),
    messageHash, // Uint8Array(32)
  ],
  programAddress: DWALLET_PROGRAM,
});

// Build instruction data: disc(1) + bump(1) + message_hash(32) + user_pubkey(32) + scheme(1) = 67
const data = new Uint8Array(67);
data[0] = 8; // IX_APPROVE_MESSAGE discriminator
data[1] = messageApprovalBump;
data.set(messageHash, 2);
data.set(userPubkey, 34);
data[66] = 0; // signature_scheme: 0=Ed25519

const approveMessageIx = {
  programAddress: DWALLET_PROGRAM,
  accounts: [
    { address: messageApprovalPda, role: AccountRole.WRITABLE },
    { address: dwalletAddress, role: AccountRole.READONLY },
    { address: authority, role: AccountRole.READONLY_SIGNER },
    { address: payer, role: AccountRole.WRITABLE_SIGNER },
    { address: SYSTEM_PROGRAM, role: AccountRole.READONLY },
  ],
  data,
};

Transfer dWallet Authority

const data = new Uint8Array(33);
data[0] = 24; // IX_TRANSFER_DWALLET discriminator
data.set(newAuthorityBytes, 1);

const transferIx = {
  programAddress: DWALLET_PROGRAM,
  accounts: [
    { address: currentAuthority, role: AccountRole.READONLY_SIGNER },
    { address: dwalletAddress, role: AccountRole.WRITABLE },
  ],
  data,
};

Send Transaction

const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const tx = pipe(
  createTransactionMessage({ version: 0 }),
  (msg) => setTransactionMessageFeePayerSigner(payer, msg),
  (msg) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, msg),
  (msg) => appendTransactionMessageInstruction(approveMessageIx, msg),
);

const signedTx = await signTransactionMessageWithSigners(tx);
await sendAndConfirm(signedTx, { commitment: "confirmed" });

Reading Accounts

Read dWallet

const account = await rpc.getAccountInfo(dwalletAddress, { encoding: "base64" }).send();
const data = Buffer.from(account.value.data[0], "base64");

// Field offsets (after 2-byte disc+version prefix):
const authority = data.subarray(2, 34);       // [u8; 32]
const curve = data[34];                        // u8
const state = data[35];                        // u8: 0=DKGInProgress, 1=Active, 2=Frozen
const publicKeyLen = data[36];                 // u8
const publicKey = data.subarray(37, 37 + publicKeyLen);

Read MessageApproval

const data = Buffer.from(account.value.data[0], "base64");

const dwallet = data.subarray(2, 34);
const messageHash = data.subarray(34, 66);
const approver = data.subarray(66, 98);
const status = data[139];                      // 0=Pending, 1=Signed
const signatureLen = data.readUInt16LE(140);
const signature = data.subarray(142, 142 + signatureLen);

gRPC Client

For submitting dWallet operations (DKG, Sign, Presign) via gRPC:

// The gRPC client uses BCS-serialized request/response types.
// See the gRPC API section for details on SubmitTransaction.

// Connect to the pre-alpha dWallet gRPC service
const GRPC_URL = "pre-alpha-dev-1.ika.ika-network.net:443";

// gRPC types are defined in proto/ika_dwallet.proto
// Use a gRPC client library (e.g., @grpc/grpc-js or connectrpc) to call:
//   DWalletService.SubmitTransaction(UserSignedRequest) -> TransactionResponse

Instruction Discriminators

DiscriminatorInstruction
8ApproveMessage
24TransferDWallet
31CommitDWallet
33CommitFutureSign
34CommitEncryptedUserSecretKeyShare
35CommitPublicUserSecretKeyShare
36CreateDeposit
37TopUp
38SettleGas
42TransferFutureSign
43CommitSignature
44RequestWithdraw
45Withdraw
46Initialize

PDA Seeds

AccountSeeds
DWalletCoordinator["dwallet_coordinator"]
DWallet["dwallet", curve_byte, public_key_bytes]
MessageApproval["message_approval", dwallet_pubkey, message_hash]
GasDeposit["gas_deposit", user_pubkey]
NetworkEncryptionKey["network_encryption_key", noa_public_key]
CPI Authority["__ika_cpi_authority"] (derived per calling program)

Framework Comparison

PinocchioNativeAnchorTypeScript
LanguageRust (no_std)Rust (std)Rust (std)TypeScript
RunsOn-chainOn-chainOn-chainOff-chain
Use caseProgram CPIProgram CPIProgram CPIClient transactions
Account typesAccountViewAccountInfoAccount/UncheckedAccountAddress + raw bytes
Best forMax performanceExisting codebasesRapid developmentdApps, scripts, bots

Examples

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.

Overview

The Ika SDK ships with two complete example programs demonstrating different patterns for program-controlled dWallet signing:

ExamplePatternDescription
VotingGovernanceOpen voting with quorum — anyone can vote, quorum triggers signing
MultisigAccess ControlM-of-N multisig — fixed members, threshold approval triggers signing

Structure

Each example follows the same directory layout:

examples/<name>/
├── pinocchio/          # Pinocchio framework implementation
│   ├── src/lib.rs
│   └── tests/mollusk.rs
├── native/             # Native solana-program implementation
│   ├── src/lib.rs
│   └── tests/mollusk.rs
├── anchor/             # Anchor framework implementation
│   └── src/lib.rs
├── e2e/                # TypeScript end-to-end tests (bun)
│   ├── main.ts
│   └── instructions.ts
└── e2e-rust/           # Rust end-to-end tests (alternative)
    └── src/main.rs

Common Flow

Both examples follow the same high-level flow:

  1. Create dWallet via gRPC DKG request — the mock commits on-chain and transfers authority to the caller
  2. Transfer authority to the example program’s CPI authority PDA (["__ika_cpi_authority"])
  3. Create proposal/transaction — on-chain state describing what to sign
  4. Collect approvals — votes or multisig member approvals
  5. CPI approve_message — when threshold is reached, the program calls the dWallet program to create a MessageApproval PDA
  6. gRPC presign + sign — allocate a presign, then sign via gRPC with ApprovalProof referencing the on-chain approval
  7. Signature returned — 64-byte Ed25519 signature for the approved message

Running Examples

Pre-Alpha Environment

ResourceEndpoint
dWallet gRPChttps://pre-alpha-dev-1.ika.ika-network.net:443
Solana RPChttps://api.devnet.solana.com

Deploy your example program to devnet, then run E2E tests against the pre-alpha environment:

# TypeScript (recommended)
just e2e-voting <DWALLET_ID> <VOTING_ID>
just e2e-multisig <DWALLET_ID> <MULTISIG_ID>

# Rust (alternative)
just e2e-voting-rust <DWALLET_ID> <VOTING_ID>
just e2e-multisig-rust <DWALLET_ID> <MULTISIG_ID>

Unit Tests (Mollusk)

Mollusk tests run in-process with no network dependency:

just test-examples-mollusk

Shared Helpers

TypeScript e2e tests use shared helpers from examples/_shared/:

  • helpers.ts — Colored logging (log, ok, val), sendTx, pda, pollUntil, createAndFundKeypair
  • ika-setup.ts — BCS types matching ika-dwallet-types, gRPC client, setupDWallet(), requestPresign(), requestSign()

These helpers handle the full dWallet lifecycle (DKG, on-chain polling, authority transfer) so example e2e tests focus on their specific program logic.

Voting Example

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.

Overview

A governance program where anyone can create proposals referencing a dWallet. Voters cast yes/no votes, and when the quorum of yes votes is reached, the program automatically CPI-calls approve_message on the dWallet program to authorize signing.

Use case: DAO governance, treasury management, group signing authorization.

Program Design

Accounts

AccountSeedsSizeDescription
Proposal["proposal", proposal_id]195 bytesStores vote counts, quorum, message hash, dWallet reference
VoteRecord["vote", proposal_id, voter]69 bytesPrevents double voting — one per voter per proposal

Instructions

DiscInstructionDescription
0CreateProposalCreate a proposal with message hash, quorum, and dWallet reference
1CastVoteRecord a vote; when quorum reached, CPI-calls approve_message

Proposal Layout (195 bytes)

disc(1) + version(1) + proposal_id(32) + dwallet(32) + message_hash(32) +
user_pubkey(32) + signature_scheme(1) + creator(32) + yes_votes(4) +
no_votes(4) + quorum(4) + status(1) + message_approval_bump(1) +
bump(1) + _reserved(16)

Key Offsets

FieldOffsetSizeType
yes_votes1634u32 LE
no_votes1674u32 LE
quorum1714u32 LE
status17510=Open, 1=Approved

CPI Flow

When yes_votes >= quorum, the program:

  1. Reads message hash, user pubkey, and signature scheme from the Proposal account
  2. Constructs a DWalletContext with the CPI authority PDA
  3. Calls ctx.approve_message(...) which creates a MessageApproval PDA on the dWallet program
  4. Sets proposal status to Approved

The caller then sends a gRPC Sign request with ApprovalProof::Solana { transaction_signature, slot } to obtain the actual signature.

E2E Flow

1. gRPC DKG          → dWallet created on-chain, authority = caller
2. Transfer authority → CPI PDA owns the dWallet
3. Create proposal    → quorum=3, message="Transfer 100 USDC"
4. Cast 3 YES votes   → last vote triggers approve_message CPI
5. Verify approval    → MessageApproval PDA exists, status=Pending
6. gRPC presign       → allocate presign
7. gRPC sign          → 64-byte signature returned

Testing

# Mollusk (instruction-level, no infrastructure needed)
cargo test -p ika-example-voting-pinocchio --test mollusk
cargo test -p ika-example-voting-native --test mollusk

# TypeScript E2E (requires validator + mock)
cd chains/solana/examples/voting/e2e && bun main.ts <DWALLET_ID> <VOTING_ID>

Source Files

  • 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
  • TypeScript E2E: chains/solana/examples/voting/e2e/main.ts

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

Testing 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 write Mollusk instruction-level tests for dWallet programs
  • How to build and verify account data at specific byte offsets
  • Testing error conditions (double vote, closed proposals)

Test Matrix

TestInstructionExpected Result
test_create_proposal_successCreateProposalPDA created with correct fields
test_create_proposal_already_existsCreateProposalFails (account in use)
test_cast_vote_yes_successCastVote (yes)yes_votes incremented, status=Open
test_cast_vote_no_successCastVote (no)no_votes incremented, status=Open
test_cast_vote_double_vote_failsCastVoteFails (VoteRecord exists)
test_cast_vote_closed_proposal_failsCastVoteFails (status=Approved)

Running Tests

# Pinocchio (requires SBF build first)
cargo build-sbf --manifest-path chains/solana/examples/voting/pinocchio/Cargo.toml
cargo test -p ika-example-voting-pinocchio --test mollusk

# Native
cargo build-sbf --manifest-path chains/solana/examples/voting/native/Cargo.toml
cargo test -p ika-example-voting-native --test mollusk

Key Patterns

Building Test Account Data

Tests pre-populate account data with exact byte layouts:

#![allow(unused)]
fn main() {
fn build_proposal_data(
    proposal_id: &[u8; 32], dwallet: &Pubkey,
    message_hash: &[u8; 32], authority: &Pubkey,
    yes_votes: u32, no_votes: u32, quorum: u32,
    status: u8, bump: u8,
) -> Vec<u8> {
    let mut data = vec![0u8; PROPOSAL_LEN]; // 195 bytes
    data[0] = PROPOSAL_DISCRIMINATOR;       // 1
    data[1] = 1;                            // version
    data[PROP_PROPOSAL_ID..PROP_PROPOSAL_ID + 32].copy_from_slice(proposal_id);
    // ... set all fields at correct offsets
    data
}
}

Verifying Results

After processing an instruction, read the resulting account data:

#![allow(unused)]
fn main() {
let result = mollusk.process_instruction(&ix, &accounts);
assert!(result.program_result.is_ok());

let prop_data = &result.resulting_accounts[0].1.data;
assert_eq!(read_u32(prop_data, PROP_YES_VOTES), 1);
assert_eq!(prop_data[PROP_STATUS], STATUS_OPEN);
}

Testing Double Vote Prevention

The VoteRecord PDA prevents double voting. If the PDA already exists, CreateAccount fails:

#![allow(unused)]
fn main() {
// Pre-populate VoteRecord (voter already voted)
let existing_vr = build_vote_record_data(&voter, &proposal_id, 1, vr_bump);

let result = mollusk.process_instruction(&ix, &[
    (proposal_pda, program_account(&program_id, proposal_data)),
    (vote_record_pda, program_account(&program_id, existing_vr)), // exists!
    // ...
]);
assert!(result.program_result.is_err());
}

Source

  • Pinocchio tests: chains/solana/examples/voting/pinocchio/tests/mollusk.rs
  • Native tests: chains/solana/examples/voting/native/tests/mollusk.rs

E2E Demo

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.

Prerequisites

  • Rust toolchain with cargo build-sbf
  • Bun (for TypeScript e2e)
  • Program deployed to Solana devnet

Pre-Alpha Environment

ResourceEndpoint
dWallet gRPChttps://pre-alpha-dev-1.ika.ika-network.net:443
Solana RPChttps://api.devnet.solana.com

Quick Start

Deploy your voting program to devnet, then run:

# TypeScript
just e2e-voting <DWALLET_ID> <VOTING_ID>

# Rust
just e2e-voting-rust <DWALLET_ID> <VOTING_ID>

Available Demos

CommandLanguageFile
just e2e-votingTypeScript (bun)examples/voting/e2e/main.ts
just e2e-voting-rustRustexamples/voting/e2e-rust/src/main.rs

Both demos produce identical results — the TypeScript version uses shared helpers from _shared/.

What the Demo Does

Step 1: gRPC DKG           → Creates dWallet, mock commits on-chain
Step 2: Transfer authority  → Voting program CPI PDA owns the dWallet
Step 3: Create proposal     → quorum=3, message="Transfer 100 USDC to treasury"
Step 4: Cast 3 YES votes    → Last vote triggers approve_message CPI
Step 5: Verify approval     → MessageApproval PDA exists on-chain
Step 6: gRPC presign        → Allocate presign for signing
Step 7: gRPC sign           → 64-byte Ed25519 signature returned

Expected Output

═══ dWallet Voting E2E Demo (TypeScript) ═══

[Setup] Funding payer...
  ✓ Payer: ...
[Setup] Waiting for mock + creating dWallet via gRPC...
  ✓ DWalletCoordinator: ...
  ✓ dWallet on-chain: ...
  ✓ Authority transferred to CPI PDA: ...

[1/5] Creating voting proposal (quorum=3)...
  ✓ Proposal: ...
[2/5] Vote 1/3: Alice casts YES...
  ✓ Alice voted YES
[2/5] Vote 2/3: Bob casts YES...
  ✓ Bob voted YES
[2/5] Vote 3/3: Charlie casts YES...
  ✓ Charlie voted YES
  ✓ Proposal approved (3/3 yes)
[3/5] Verifying MessageApproval on-chain...
  ✓ MessageApproval: ...
[4/5] Allocating presign via gRPC...
  ✓ Presign allocated!
[5/5] Sending Sign request via gRPC...
  ✓ Signature received from gRPC!
  → Signature: <64-byte hex>

═══ E2E Test Passed! ═══

How the Shared Helpers Work

The TypeScript e2e imports from _shared/:

import { setupDWallet, requestPresign, requestSign } from "../../_shared/ika-setup.ts";
import { log, ok, val, sendTx, pda, pollUntil } from "../../_shared/helpers.ts";

setupDWallet() handles the entire dWallet lifecycle:

  1. Waits for program initialization (polls for DWalletCoordinator PDA)
  2. Sends gRPC DKG request (creates dWallet on-chain + transfers authority)
  3. Polls for dWallet PDA to appear
  4. Transfers authority to the example program’s CPI PDA

This means the e2e test code only needs to focus on the voting-specific logic.

Multisig Example

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.

Overview

An M-of-N multisig program controlling a dWallet. Fixed members are set at creation. Any member can propose transactions with message data stored on-chain for other members to inspect. Members approve or reject. When threshold approvals are reached, the program CPI-calls approve_message and optionally transfer_future_sign. When enough rejections accumulate (making approval impossible), the transaction is marked rejected.

Use case: Multi-party custody, organizational signing policies, timelocked operations.

Program Design

Accounts

AccountSeedsSizeDescription
Multisig["multisig", create_key]395 bytesMembers list, threshold, dWallet reference, tx counter
Transaction["transaction", multisig, tx_index_le]432 bytesMessage data, approval/rejection counts, status
ApprovalRecord["approval", transaction, member]68 bytesPrevents double voting — one per member per transaction

Instructions

DiscInstructionDescription
0CreateMultisigCreate multisig with members (up to 10), threshold, dWallet
1CreateTransactionPropose a transaction with message data stored on-chain
2ApproveApprove; when threshold reached, CPI approve_message + optional transfer_future_sign
3RejectReject; when enough rejections, mark transaction as rejected

Multisig Layout (395 bytes)

disc(1) + version(1) + create_key(32) + threshold(u16) + member_count(u16) +
tx_index(u32) + dwallet(32) + bump(1) + members(32 * 10)

Transaction Layout (432 bytes)

disc(1) + version(1) + multisig(32) + tx_index(u32) + proposer(32) +
message_hash(32) + user_pubkey(32) + signature_scheme(1) +
approval_count(u16) + rejection_count(u16) + status(1) +
message_approval_bump(1) + partial_user_sig(32) + bump(1) +
message_data_len(u16) + message_data(256)

Key Offsets

FieldOffsetSizeType
approval_count1352u16 LE
rejection_count1372u16 LE
status13910=Active, 1=Approved, 2=Rejected
message_data_len1742u16 LE
message_data176256raw bytes

Rejection Logic

A transaction is rejected when enough members reject that approval becomes impossible:

rejection_threshold = member_count - threshold + 1

For a 2-of-3 multisig: 3 - 2 + 1 = 2 rejections needed to reject.

CPI Flow

When approval_count >= threshold, the program:

  1. Calls ctx.approve_message(...) — creates MessageApproval PDA
  2. If partial_user_sig is set (non-zero), calls ctx.transfer_future_sign(...) — transfers the partial signature completion authority to the proposer
  3. Sets transaction status to Approved

E2E Flow

1.  gRPC DKG           → dWallet created, authority = caller
2.  Transfer authority  → CPI PDA owns the dWallet
3.  Create multisig     → 2-of-3 with 3 member pubkeys
4.  Propose transaction → message data stored on-chain
5.  Member1 approves    → approval_count = 1
6.  Member2 approves    → approval_count = 2 = threshold → CPI!
7.  Verify approval     → MessageApproval exists
8.  gRPC presign        → allocate presign
9.  gRPC sign           → 64-byte signature
10. Rejection test      → propose 2nd tx, 2 rejections → status=Rejected

React Frontend

A React frontend is included at chains/solana/examples/multisig/react/ with:

  • Create dWallet + Multisig (via gRPC-web client)
  • Propose transactions
  • Approve/reject as a member
  • View transaction status and message data
  • Airdrop button for local testing
cd chains/solana/examples/multisig/react && bun install && bun dev

Testing

# Mollusk (all 3 framework variants)
cargo test -p ika-example-multisig --test mollusk         # pinocchio (11 tests)
cargo test -p ika-example-multisig-native --test mollusk  # native (11 tests)

# TypeScript E2E
cd chains/solana/examples/multisig/e2e && bun main.ts <DWALLET_ID> <MULTISIG_ID>

Source Files

  • Pinocchio: chains/solana/examples/multisig/pinocchio/src/lib.rs
  • Native: chains/solana/examples/multisig/native/src/lib.rs
  • Anchor: chains/solana/examples/multisig/anchor/src/lib.rs
  • TypeScript E2E: chains/solana/examples/multisig/e2e/main.ts
  • React: chains/solana/examples/multisig/react/

Building the Multisig 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 design a multisig with fixed members and threshold approval
  • How to store transaction data on-chain for other signers to inspect
  • How to implement both approval and rejection flows
  • How to use transfer_future_sign for partial signature management

Architecture

Creator ──► CreateMultisig (members, threshold, dWallet)
                │
Member 1 ──► CreateTransaction (message data stored on-chain)
                │
Member 1 ──► Approve ──┐
Member 2 ──► Approve ──┼──► threshold reached? ──► approve_message CPI
Member 3 ──► Reject  ──┘                                    │
                                                   transfer_future_sign CPI
                                                            │
                                                   Transaction = Approved

1. Account Layouts

Multisig PDA (["multisig", create_key]) — 395 bytes

FieldOffsetSizeType
disc01always 1
version11always 1
create_key232unique key
threshold342u16 LE
member_count362u16 LE
tx_index384u32 LE (auto-increment)
dwallet4232pubkey
bump741PDA bump
members7532010 × 32-byte pubkeys

Transaction PDA (["transaction", multisig, tx_index_le]) — 432 bytes

FieldOffsetSizeType
disc01always 2
multisig232pubkey
tx_index344u32 LE
proposer3832pubkey
message_hash7032keccak256
approval_count1352u16 LE
rejection_count1372u16 LE
status13910=Active, 1=Approved, 2=Rejected
message_data_len1742u16 LE
message_data176256raw bytes

ApprovalRecord PDA (["approval", transaction, member]) — 68 bytes

Prevents double voting. One per member per transaction.

2. Instructions

DiscNameDescription
0CreateMultisigSet members (up to 10), threshold, dWallet reference
1CreateTransactionPropose with message data stored on-chain
2ApproveVote yes; triggers CPI at threshold
3RejectVote no; marks rejected when impossible to approve

3. Rejection Threshold

A transaction is rejected when enough members reject that approval becomes impossible:

rejection_threshold = member_count - threshold + 1

Example: 2-of-3 multisig → 3 - 2 + 1 = 2 rejections needed.

4. CPI Flow on Approval

When approval_count >= threshold:

#![allow(unused)]
fn main() {
// 1. Approve the message (creates MessageApproval PDA)
ctx.approve_message(
    message_approval, dwallet, payer, system_program,
    message_hash, user_pubkey, signature_scheme,
    message_approval_bump,
)?;

// 2. Optionally transfer future sign authority
if partial_user_sig != [0u8; 32] {
    ctx.transfer_future_sign(partial_user_sig_account, proposer_key)?;
}

// 3. Mark transaction as approved
tx_data[TX_STATUS] = STATUS_APPROVED;
}

Source Code

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

Testing the Multisig 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.

Test Matrix

TestInstructionExpected Result
test_create_multisig_successCreateMultisig2-of-3 with correct fields
test_create_multisig_zero_threshold_failsCreateMultisigRejects threshold=0
test_create_multisig_threshold_exceeds_members_failsCreateMultisigRejects threshold > members
test_create_transaction_successCreateTransactionMessage data stored, tx_index incremented
test_create_transaction_non_member_failsCreateTransactionNon-member cannot propose
test_approve_successApproveapproval_count incremented, still Active
test_approve_double_vote_failsApproveApprovalRecord already exists
test_approve_non_member_failsApproveNon-member cannot approve
test_reject_successRejectrejection_count incremented, still Active
test_reject_threshold_marks_rejectedReject2nd rejection → status=Rejected
test_vote_on_closed_transaction_failsApproveCannot vote on Approved transaction

All 11 tests pass for both Pinocchio and Native variants.

Running Tests

# Pinocchio
cargo build-sbf --manifest-path chains/solana/examples/multisig/pinocchio/Cargo.toml
cargo test -p ika-example-multisig --test mollusk

# Native
cargo build-sbf --manifest-path chains/solana/examples/multisig/native/Cargo.toml
cargo test -p ika-example-multisig-native --test mollusk

Source

  • Pinocchio: chains/solana/examples/multisig/pinocchio/tests/mollusk.rs
  • Native: chains/solana/examples/multisig/native/tests/mollusk.rs

E2E Demo

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.

Pre-Alpha Environment

ResourceEndpoint
dWallet gRPChttps://pre-alpha-dev-1.ika.ika-network.net:443
Solana RPChttps://api.devnet.solana.com

Quick Start

Deploy your multisig program to devnet, then run:

# TypeScript
just e2e-multisig <DWALLET_ID> <MULTISIG_ID>

# Rust
just e2e-multisig-rust <DWALLET_ID> <MULTISIG_ID>

Available Demos

CommandLanguageFile
just e2e-multisigTypeScript (bun)examples/multisig/e2e/main.ts
just e2e-multisig-rustRustexamples/multisig/e2e-rust/src/main.rs

What the Demo Does

Step 1:  gRPC DKG            → dWallet created, authority = caller
Step 2:  Transfer authority   → CPI PDA owns the dWallet
Step 3:  Create multisig      → 2-of-3 with 3 member pubkeys
Step 4:  Propose transaction  → Message data stored on-chain
Step 5:  Member1 approves     → approval_count = 1
Step 6:  Member2 approves     → approval_count = 2 = threshold → CPI!
Step 7:  Verify approval      → MessageApproval exists on-chain
Step 8:  gRPC presign         → Allocate presign
Step 9:  gRPC sign            → 64-byte signature returned
Step 10: Rejection test       → Propose 2nd tx, 2 rejections → Rejected

React Frontend

A React frontend is included for interactive testing:

cd chains/solana/examples/multisig/react
bun install && bun dev

The frontend includes:

  • Create dWallet + Multisig in one click (via gRPC-web)
  • Propose transactions with on-chain message data
  • Approve/Reject as a connected wallet member
  • Live status with auto-refresh

Instruction Reference

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.

Instructions in the Ika dWallet Solana program. The first byte of instruction data is the discriminator.

Instruction Groups

GroupDisc RangeInstructions
Message8approve_message
Ownership24transfer_ownership
DKG31commit_dwallet
Signing42–43transfer_future_sign, commit_signature

Message

approve_message (disc 8)

Create a MessageApproval PDA requesting a signature from the Ika network. Supports both direct signer and CPI callers.

Accounts (CPI path):

#AccountWSDescription
0message_approvalyesnoMessageApproval PDA (must be empty)
1dwalletnonodWallet account
2caller_programnonoCalling program (executable)
3cpi_authoritynoyesCPI authority PDA (signed via invoke_signed)
4payeryesyesRent payer
5system_programnonoSystem program

Data (67 bytes):

OffsetFieldSize
0discriminator1
1bump1
2message_hash32
34user_pubkey32
66signature_scheme1

The dWallet program verifies:

  • caller_program is executable
  • cpi_authority matches PDA(["__ika_cpi_authority"], caller_program)
  • dwallet.authority == cpi_authority

Ownership

transfer_ownership (disc 24)

Transfer dWallet authority to a new pubkey.

Accounts (signer path):

#AccountWSDescription
0current_authoritynoyesCurrent dWallet authority (signer)
1dwalletyesnodWallet account

Accounts (CPI path):

#AccountWSDescription
0caller_programnonoCalling program (executable)
1cpi_authoritynoyesCPI authority PDA (signer)
2dwalletyesnodWallet account

Data (33 bytes):

OffsetFieldSize
0discriminator1
1new_authority32

DKG

commit_dwallet (disc 31)

NOA-only: create a dWallet account after DKG completes.

Accounts:

#AccountWSDescription
0coordinatornonoDWalletCoordinator PDA
1neknonoNetworkEncryptionKey PDA
2noanoyesNOA signer
3dwalletyesnoDWallet PDA (must be empty)
4authoritynonoInitial dWallet authority
5payeryesyesRent payer
6system_programnonoSystem program

Data:

OffsetFieldSize
0discriminator1
1curve1
2is_imported1
3public_key_len1
4public_key65
69bump1
70public_output_len2
72public_output256
328noa_signature64

Signing

transfer_future_sign (disc 42)

Transfer the completion authority of a PartialUserSignature.

Accounts (CPI path):

#AccountWSDescription
0partial_user_sigyesnoPartialUserSignature account
1caller_programnonoCalling program (executable)
2cpi_authoritynoyesCPI authority PDA (signer)

Data (33 bytes):

OffsetFieldSize
0discriminator1
1new_completion_authority32

commit_signature (disc 43)

NOA-only: write the signature into a MessageApproval account.

Accounts:

#AccountWSDescription
0message_approvalyesnoMessageApproval PDA
1neknonoNetworkEncryptionKey PDA
2noanoyesNOA signer

Data:

OffsetFieldSize
0discriminator1
1signature_len2
3signature128

Voting Example Instructions

These are defined by the example voting program, not the dWallet program:

create_proposal (disc 0)

Create a voting proposal.

Accounts:

#AccountWSDescription
0proposalyesnoProposal PDA (["proposal", proposal_id])
1dwalletnonodWallet account
2creatornoyesProposal creator
3payeryesyesRent payer
4system_programnonoSystem program

Data (103 bytes): proposal_id(32) | message_hash(32) | user_pubkey(32) | signature_scheme(1) | quorum(4) | message_approval_bump(1) | bump(1)

cast_vote (disc 1)

Cast a vote. Triggers approve_message CPI when quorum is reached.

Accounts (base, 5):

#AccountWSDescription
0proposalyesnoProposal PDA
1vote_recordyesnoVoteRecord PDA (["vote", proposal_id, voter])
2voternoyesVoter
3payeryesyesRent payer
4system_programnonoSystem program

Additional accounts when quorum reached (5):

#AccountWSDescription
5message_approvalyesnoMessageApproval PDA
6dwalletnonodWallet account
7caller_programnonoVoting program
8cpi_authoritynonoCPI authority PDA
9dwallet_programnonodWallet program

Data (35 bytes): proposal_id(32) | vote(1) | vote_record_bump(1) | cpi_authority_bump(1)

Account Reference

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.

All account types in the Ika dWallet system. Each account starts with a 2-byte prefix: discriminator(1) | version(1), followed by the account data.

dWallet Program Accounts

Account Discriminators

DiscriminatorAccount Type
1DWalletCoordinator
3NetworkEncryptionKey
14MessageApproval

DWalletCoordinator (disc 1)

Program-wide state. PDA seeds: ["dwallet_coordinator"].

OffsetFieldSizeDescription
0discriminator11
1version11
2(fields)114Coordinator state

Total: 116 bytes


NetworkEncryptionKey (disc 3)

The network encryption public key used for DKG. PDA seeds: ["network_encryption_key", noa_pubkey].

OffsetFieldSizeDescription
0discriminator13
1version11
2(fields)162NEK data

Total: 164 bytes


DWallet

A distributed signing key. PDA seeds: ["dwallet", curve_byte, public_key].

OffsetFieldSizeDescription
0discriminator1Account type
1version11
2authority32Who can approve messages (user or CPI PDA)
34public_key65dWallet public key (padded)
99public_key_len1Actual key length (32 or 33)
100curve1Curve ID (0=Secp256k1, 1=Secp256r1, 2=Curve25519, 3=Ristretto)
101is_imported1Whether the key was imported

MessageApproval (disc 14)

A signing request. PDA seeds: ["message_approval", dwallet, message_hash].

OffsetFieldSizeDescription
0discriminator114
1version11
2dwallet32dWallet account pubkey
34message_hash32Hash of message to sign
66user_pubkey32User public key
98signature_scheme1Ed25519(0), Secp256k1(1), Secp256r1(2)
99caller_program32Program that approved this
131cpi_authority32CPI authority PDA that signed
139status1Pending(0) or Signed(1)
140signature_len2Signature byte count (LE u16)
142signature128Signature bytes (padded)

Total: 287 bytes (2 + 285)

Status values:

  • 0 = PENDING – awaiting signature from the network
  • 1 = SIGNED – signature is available

Ika System Accounts (SDK Types)

These accounts are part of the Ika System program, readable via ika-solana-sdk-types.

SystemState (disc 1)

PDA seeds: ["ika_system_state"]. Total: 365 bytes.

OffsetFieldSizeDescription
0discriminator11
1version11
2epoch8Current epoch (LE u64)
34authority32System authority

Validator (disc 2)

PDA seeds: ["validator", identity_pubkey]. Total: 973 bytes.

OffsetFieldSizeDescription
0discriminator12
1version11
2identity32Validator identity pubkey
98state1PreActive(0), Active(1), Withdrawing(2)
159ika_balance8IKA token balance (LE u64)

StakeAccount (disc 3)

PDA seeds: ["stake_account", stake_id_le_bytes]. Total: 115 bytes.

OffsetFieldSizeDescription
0discriminator13
1version11
2owner32Stake owner pubkey
74principal8Staked amount (LE u64)
98state1Active(0), Withdrawing(1)

ValidatorList (disc 4)

PDA seeds: ["validator_list"].

OffsetFieldSizeDescription
0discriminator14
1version11
2validator_count4Total validators (LE u32)
6active_count4Active validators (LE u32)

Voting Example Accounts

Proposal (disc 1)

PDA seeds: ["proposal", proposal_id]. Total: 195 bytes.

OffsetFieldSizeDescription
0discriminator11
1version11
2proposal_id32Unique identifier
34dwallet32dWallet pubkey
66message_hash32Message hash to sign
98user_pubkey32User public key
130signature_scheme1Signature scheme
131creator32Creator pubkey
163yes_votes4Yes count (LE u32)
167no_votes4No count (LE u32)
171quorum4Required yes votes (LE u32)
175status1Open(0), Approved(1)
176msg_approval_bump1MessageApproval PDA bump
177bump1Proposal PDA bump
178_reserved16Reserved

VoteRecord (disc 2)

PDA seeds: ["vote", proposal_id, voter]. Total: 69 bytes.

OffsetFieldSizeDescription
0discriminator12
1version11
2voter32Voter pubkey
34proposal_id32Proposal identifier
66vote1Yes(1) or No(0)
67bump1VoteRecord PDA bump

Account Type Summary

AccountDiscTypeSizePDA SeedsProgram
DWalletCoordinator1PDA116["dwallet_coordinator"]dWallet
NetworkEncryptionKey3PDA164["network_encryption_key", noa]dWallet
MessageApproval14PDA287["message_approval", dwallet, hash]dWallet
SystemState1PDA365["ika_system_state"]Ika System
Validator2PDA973["validator", identity]Ika System
StakeAccount3PDA115["stake_account", stake_id]Ika System
ValidatorList4PDA18+["validator_list"]Ika System
Proposal1PDA195["proposal", id]Voting example
VoteRecord2PDA69["vote", id, voter]Voting example

Event Reference

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.

Overview

The dWallet program emits events via Anchor-compatible self-CPI. Events are emitted as inner instructions and can be parsed from transaction metadata.

Anchor-Compatible Event Format

Events use the same wire format as Anchor events:

EVENT_IX_TAG_LE(8) | event_discriminator(1) | event_data(N)

The EVENT_IX_TAG_LE is 8 bytes (0xe4a545ea51cb9a1d in little-endian). The event discriminator follows, then the event-specific data.

Key Events

MessageApprovalCreated

Emitted when approve_message creates a new MessageApproval PDA.

FieldSizeDescription
dwallet32dWallet pubkey
message_hash32Hash of the message to sign
caller_program32Program that approved

The Ika network listens for this event to initiate the signing protocol.

SignatureCommitted

Emitted when the NOA calls commit_signature to write a signature.

FieldSizeDescription
message_approval32MessageApproval account pubkey
signature_len2Length of the signature

Off-chain clients can listen for this to know when a signature is ready.

DWalletCreated

Emitted when commit_dwallet creates a new dWallet.

FieldSizeDescription
dwallet32New dWallet pubkey
authority32Initial authority
curve1Curve identifier

AuthorityTransferred

Emitted when transfer_ownership changes a dWallet’s authority.

FieldSizeDescription
dwallet32dWallet pubkey
old_authority32Previous authority
new_authority32New authority

Parsing Events

Events appear as inner instructions in the transaction metadata. To parse them:

  1. Find inner instructions targeting the dWallet program
  2. Match the first 8 bytes against EVENT_IX_TAG_LE
  3. Read the 1-byte event discriminator
  4. Deserialize the remaining bytes according to the event schema

Example: Detecting Signatures

#![allow(unused)]
fn main() {
use solana_transaction_status::UiTransactionEncoding;

let tx = client.get_transaction_with_config(
    &tx_signature,
    RpcTransactionConfig {
        encoding: Some(UiTransactionEncoding::Base64),
        commitment: Some(CommitmentConfig::confirmed()),
        max_supported_transaction_version: Some(0),
    },
)?;

// Parse inner instructions for SignatureCommitted events
if let Some(meta) = tx.transaction.meta {
    for inner_ix in meta.inner_instructions.unwrap_or_default() {
        for ix in inner_ix.instructions {
            // Check EVENT_IX_TAG_LE prefix and parse event data
        }
    }
}
}

Example: Polling for MessageApproval Status

Rather than parsing events, you can poll the MessageApproval account directly:

#![allow(unused)]
fn main() {
loop {
    let data = client.get_account(&message_approval_pda)?.data;
    if data[139] == 1 { // status == Signed
        let sig_len = u16::from_le_bytes(data[140..142].try_into().unwrap()) as usize;
        let signature = data[142..142 + sig_len].to_vec();
        break;
    }
    std::thread::sleep(Duration::from_millis(500));
}
}

Event vs Polling

ApproachProsCons
Event parsingImmediate notification, no pollingRequires transaction metadata, more complex
Account pollingSimple, works everywhereLatency, wasted RPC calls

For production use, event-based detection is recommended. For testing and simple scripts, polling is sufficient.