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

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.