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:
UncheckedAccountinstead of rawAccountInfoin#[derive(Accounts)]InitSpacederive 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,
};
}
| Field | Type | Description |
|---|---|---|
dwallet_program | AccountInfo | The dWallet program account |
cpi_authority | AccountInfo | Your program’s CPI authority PDA |
caller_program | AccountInfo | Your program’s account (must be executable) |
cpi_authority_bump | u8 | Bump 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:
| Change | Before (0.30) | After (v1.0.0) |
|---|---|---|
| Space calculation | Manual 8 + 32 + 32 + ... | 8 + MyAccount::INIT_SPACE (InitSpace derive) |
| Raw AccountInfo | AccountInfo<'info> in derives | UncheckedAccount<'info> with /// CHECK: |
| Error blocks | Multiple #[error_code] allowed | Single #[error_code] per program |
| CPI program | CpiContext::new(program.to_account_info(), ...) | CpiContext::new(Program::id(), ...) or direct |
| Solana version | Solana 2.x | Solana 3.x |
Differences from Pinocchio SDK
| Pinocchio | Anchor v1 | |
|---|---|---|
| Account types | &AccountView | AccountInfo / UncheckedAccount |
| Error handling | ProgramResult | anchor_lang::Result<()> |
| CPI signing | pinocchio::cpi::invoke_signed | anchor_lang::solana_program::program::invoke_signed |
| Entrypoint | Manual entrypoint!() macro | #[program] attribute macro |
| Account validation | Manual checks | #[derive(Accounts)] constraints |
| Space | core::mem::size_of::<T>() | 8 + T::INIT_SPACE (InitSpace derive) |
| Best for | Maximum CU efficiency | Rapid 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.