Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.arcium.com/llms.txt

Use this file to discover all available pages before exploring further.

1. Arcium.toml backends (no change required)

Arcium.toml stays the same for this migration. Only the Cerberus backend is supported at the moment; Manticore is unavailable. If your config already uses Cerberus, no action is needed.

2. Update Arcium Rust dependencies

Update your dependencies to v0.5.1:
# Update program dependencies
cd programs/your-program-name
cargo update --package arcium-client --precise 0.5.1
cargo update --package arcium-macros --precise 0.5.1
cargo update --package arcium-anchor --precise 0.5.1

# Update encrypted-ixs dependencies
cd ../../encrypted-ixs
cargo update --package arcis-imports --precise 0.5.1

3. Update Arcium TS dependencies

npm install @arcium-hq/client@0.5.1

4. Update TypeScript environment variable usage

The environment variable and type have changed from a public key to a cluster offset:
// Before v0.5.1
const arciumEnv = getArciumEnv();
console.log(arciumEnv.arciumClusterPubkey);  // PublicKey
// v0.5.1
const arciumEnv = getArciumEnv();
console.log(arciumEnv.arciumClusterOffset);  // number
Environment variable change:
  • Before: ARCIUM_CLUSTER_PUBKEY (base58 pubkey string)
  • After: ARCIUM_CLUSTER_OFFSET (integer)

5. Update TypeScript SDK function names

Several functions have been renamed for consistency: Program Address Function:
// Before v0.5.1
import { getArciumProgAddress } from "@arcium-hq/client";
const address = getArciumProgAddress();
// v0.5.1
import { getArciumProgramId } from "@arcium-hq/client";
const address = getArciumProgramId();
Account Data Functions:
// Before v0.5.1
const mempool = await getMempoolAccData(provider, address);
const execpool = await getExecutingPoolAccData(provider, address);
// v0.5.1
const mempool = await getMempoolAccInfo(provider, address);
const execpool = await getExecutingPoolAccInfo(provider, address);
Parameter Name Updates: Functions now use mxeProgramId (camelCase) instead of mxeProgramID:
// Before v0.5.1
await getMXEPublicKey(provider, mxeProgramID);
await uploadCircuit(provider, name, mxeProgramID, circuit);
// v0.5.1
await getMXEPublicKey(provider, mxeProgramId);
await uploadCircuit(provider, name, mxeProgramId, circuit);
Old FunctionNew Function
getArciumProgAddress()getArciumProgramId()
getMempoolAccData()getMempoolAccInfo()
getExecutingPoolAccData()getExecutingPoolAccInfo()
getArciumProgramReadonly()getArciumProgram()
getArxAccPDA()getArxNodeAccAddress()

6. Update TypeScript PDA derivation functions

Several PDA derivation functions now take clusterOffset instead of programId.
Note: getMXEAccAddress() still uses program.programId - only cluster-related PDAs changed.
// Before v0.5.1
import {
  getMXEAccAddress,
  getMempoolAccAddress,
  getExecutingPoolAccAddress,
  getComputationAccAddress,
} from "@arcium-hq/client";

.accountsPartial({
  computationAccount: getComputationAccAddress(
    program.programId,
    computationOffset
  ),
  clusterAccount: arciumEnv.arciumClusterPubkey,
  mxeAccount: getMXEAccAddress(program.programId),
  mempoolAccount: getMempoolAccAddress(program.programId),
  executingPool: getExecutingPoolAccAddress(program.programId),
})
// v0.5.1
import {
  getMXEAccAddress,
  getMempoolAccAddress,
  getExecutingPoolAccAddress,
  getComputationAccAddress,
  getClusterAccAddress,  // New import
} from "@arcium-hq/client";

.accountsPartial({
  computationAccount: getComputationAccAddress(
    arciumEnv.arciumClusterOffset,  // Changed from program.programId
    computationOffset
  ),
  clusterAccount: getClusterAccAddress(arciumEnv.arciumClusterOffset),  // Now derived
  mxeAccount: getMXEAccAddress(program.programId),  // Unchanged
  mempoolAccount: getMempoolAccAddress(arciumEnv.arciumClusterOffset),  // Changed
  executingPool: getExecutingPoolAccAddress(arciumEnv.arciumClusterOffset),  // Changed
})

7. Update queue_computation call

The queue_computation function has two changes:
  1. New parameter: cu_price_micro: u64 for priority fees
  2. Updated callback_ix: Now requires computation_offset and mxe_account
// Before v0.5.1
queue_computation(
    ctx.accounts,
    computation_offset,
    args,
    None,
    vec![MyCallback::callback_ix(&[])],
    1,
)?;
// v0.5.1
queue_computation(
    ctx.accounts,
    computation_offset,
    args,
    None,
    vec![MyCallback::callback_ix(
        computation_offset,
        &ctx.accounts.mxe_account,
        &[]
    )?],
    1,
    0,  // cu_price_micro: priority fee in microlamports (0 = no priority fee)
)?;
Priority Fee Notes:
  • Set cu_price_micro to 0 for standard processing
  • Use higher values (e.g., 50000) for faster processing during network congestion

8. Update onchain argument API

Replace the vec![Argument::...] pattern with the new ArgBuilder API:
// Before v0.5.1
let args = vec![
    Argument::ArcisPubkey(pub_key),
    Argument::PlaintextU128(nonce),
    Argument::EncryptedU8(ciphertext_0),
    Argument::EncryptedU8(ciphertext_1),
];
// v0.5.1
let args = ArgBuilder::new()
    .x25519_pubkey(pub_key)
    .plaintext_u128(nonce)
    .encrypted_u8(ciphertext_0)
    .encrypted_u8(ciphertext_1)
    .build();

Available ArgBuilder methods

MethodInput TypeDescription
plaintext_bool(value)boolBoolean value
plaintext_u8(value)u8Unsigned 8-bit integer
plaintext_i8(value)i8Signed 8-bit integer
plaintext_u16(value)u16Unsigned 16-bit integer
plaintext_i16(value)i16Signed 16-bit integer
plaintext_u32(value)u32Unsigned 32-bit integer
plaintext_i32(value)i32Signed 32-bit integer
plaintext_u64(value)u64Unsigned 64-bit integer
plaintext_i64(value)i64Signed 64-bit integer
plaintext_u128(value)u128Unsigned 128-bit integer
plaintext_i128(value)i128Signed 128-bit integer
plaintext_float(value)f64Floating point
plaintext_point(value)[u8; 32]Elliptic curve point (new in v0.5.1)
MethodInput TypeDescription
encrypted_bool(value)[u8; 32]Confidential boolean
encrypted_u8(value)[u8; 32]Confidential unsigned 8-bit
encrypted_i8(value)[u8; 32]Confidential signed 8-bit
encrypted_u16(value)[u8; 32]Confidential unsigned 16-bit
encrypted_i16(value)[u8; 32]Confidential signed 16-bit
encrypted_u32(value)[u8; 32]Confidential unsigned 32-bit
encrypted_i32(value)[u8; 32]Confidential signed 32-bit
encrypted_u64(value)[u8; 32]Confidential unsigned 64-bit
encrypted_i64(value)[u8; 32]Confidential signed 64-bit
encrypted_u128(value)[u8; 32]Confidential unsigned 128-bit
encrypted_i128(value)[u8; 32]Confidential signed 128-bit
encrypted_float(value)[u8; 32]Confidential floating point
MethodInput TypeDescription
x25519_pubkey(value)[u8; 32]X25519 public key for confidentiality
arcis_ed25519_signature(value)[u8; 64]Ed25519 signature
MethodInput TypeDescription
account(pubkey, offset, length)Pubkey, u32, u32Reference onchain account data

9. Update onchain PDA macros

PDA derivation macros now require mxe_account and an error code parameter:
// Before v0.5.1
#[account(
    mut,
    address = derive_mempool_pda!()
)]
pub mempool_account: UncheckedAccount<'info>,

#[account(
    mut,
    address = derive_execpool_pda!()
)]
pub executing_pool: UncheckedAccount<'info>,

#[account(
    mut,
    address = derive_comp_pda!(computation_offset)
)]
pub computation_account: UncheckedAccount<'info>,
Don’t forget to update the cluster PDA as well:
// Before v0.5.1
#[account(
    mut,
    address = derive_cluster_pda!()
)]
pub cluster_account: Account<'info, Cluster>,
// v0.5.1
#[account(
    mut,
    address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub cluster_account: Account<'info, Cluster>,
// v0.5.1
#[account(
    mut,
    address = derive_mempool_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub mempool_account: UncheckedAccount<'info>,

#[account(
    mut,
    address = derive_execpool_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub executing_pool: UncheckedAccount<'info>,

#[account(
    mut,
    address = derive_comp_pda!(
        computation_offset,
        mxe_account,
        ErrorCode::ClusterNotSet
    )
)]
pub computation_account: UncheckedAccount<'info>,
Make sure you have the ClusterNotSet error code defined (this should already exist from v0.4.0):
#[error_code]
pub enum ErrorCode {
    #[msg("The computation was aborted")]
    AbortedComputation,
    #[msg("Cluster not set")]
    ClusterNotSet,
}

10. Update callback account structs

v0.5.1 requires three additional accounts in callback structs to support BLS signature verification:
// Before v0.5.1
#[callback_accounts("my_circuit")]
#[derive(Accounts)]
pub struct MyCircuitCallback<'info> {
    pub arcium_program: Program<'info, Arcium>,
    #[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_MY_CIRCUIT))]
    pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
    #[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
    /// CHECK: instructions_sysvar
    pub instructions_sysvar: AccountInfo<'info>,
}
// v0.5.1
#[callback_accounts("my_circuit")]
#[derive(Accounts)]
pub struct MyCircuitCallback<'info> {
    pub arcium_program: Program<'info, Arcium>,
    #[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_MY_CIRCUIT))]
    pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
    #[account(address = derive_mxe_pda!())]
    pub mxe_account: Account<'info, MXEAccount>,
    /// CHECK: computation_account, checked by arcium program via constraints in the callback context.
    pub computation_account: UncheckedAccount<'info>,
    #[account(address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet))]
    pub cluster_account: Account<'info, Cluster>,
    #[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
    /// CHECK: instructions_sysvar
    pub instructions_sysvar: AccountInfo<'info>,
}
New Required Accounts:
  • mxe_account - The MXE program account, derived using derive_mxe_pda!()
  • computation_account - The computation account (unchecked, verified by Arcium program)
  • cluster_account - The cluster account, derived from mxe_account
The order matters - cluster_account must come after mxe_account since its PDA derivation depends on the MXE account reference.
These accounts are required for the verify_output() call in your callback handler (see next section).

11. Update callback handling with BLS verification

v0.5.1 introduces BLS signature verification for computation outputs. Use SignedComputationOutputs and call verify_output() to validate results:
// Before v0.5.1 - Direct output handling
#[arcium_callback(encrypted_ix = "my_circuit")]
pub fn my_circuit_callback(
    ctx: Context<MyCircuitCallback>,
    output: ComputationOutputs<MyCircuitOutput>,
) -> Result<()> {
    let result = output.unwrap();
    // Use result directly...
    Ok(())
}
// v0.5.1 - BLS verified outputs
#[arcium_callback(encrypted_ix = "my_circuit")]
pub fn my_circuit_callback(
    ctx: Context<MyCircuitCallback>,
    output: SignedComputationOutputs<MyCircuitOutput>,
) -> Result<()> {
    // verify_output() validates the BLS signature from the MXE cluster
    let result = match output.verify_output(
        &ctx.accounts.cluster_account,
        &ctx.accounts.computation_account
    ) {
        Ok(MyCircuitOutput { field_0 }) => field_0,
        Err(e) => {
            msg!("Computation verification failed: {}", e);
            return Err(ErrorCode::AbortedComputation.into())
        },
    };

    // Now use the verified result
    emit!(ResultEvent {
        value: result.ciphertexts[0],
        nonce: result.nonce.to_le_bytes(),
    });
    Ok(())
}
Key Changes:
  • Replace ComputationOutputs<T> with SignedComputationOutputs<T>
  • Always call verify_output() to validate BLS signatures
  • Handle the Result - verification can fail if signatures don’t match
  • verify_output() requires cluster_account and computation_account references
Error Codes:
  • BLSSignatureVerificationFailed - The BLS signature doesn’t match
  • InvalidClusterBLSPublicKey - Cluster’s BLS key is not set
  • AbortedComputation - The computation failed in the MXE

12. (Optional) Update callback macros for multi-transaction callbacks

If you need multiple callback functions for the same confidential instruction, use the new skip_name_validation parameter:
// Primary callback (standard naming)
#[arcium_callback(encrypted_ix = "add_order")]
pub fn add_order_callback(
    ctx: Context<AddOrderCallback>,
    output: SignedComputationOutputs<AddOrderOutput>,
) -> Result<()> {
    // Handle primary callback with BLS verification
    let result = output.verify_output(
        &ctx.accounts.cluster_account,
        &ctx.accounts.computation_account
    )?;
    Ok(())
}

// Secondary callback (non-standard naming)
#[arcium_callback(
    encrypted_ix = "add_order",
    auto_serialize = false,
    skip_name_validation = true
)]
pub fn emit_order_event(
    ctx: Context<EmitOrderEvent>,
    output: RawComputationOutputs<ShortVec<u8>>
) -> Result<()> {
    // Handle secondary callback (e.g., emit event)
    Ok(())
}

13. (Optional) Use circuit_hash! macro for offchain circuits

If using offchain circuit storage, replace placeholder hashes with verified hashes:
// Before v0.5.1
hash: [0u8; 32],
// v0.5.1
use arcium_macros::circuit_hash;
hash: circuit_hash!("my_circuit"),
See Deployment: Handling Large Circuits for complete offchain circuit configuration.

14. Verify migration

After completing all migration steps, verify that everything works correctly:

Build test

# From your workspace root
arcium build

Type checking

# Ensure all new types compile correctly
cargo check --all

Test your changes

# Run your existing tests to ensure functionality is preserved
arcium test

15. Complete example

Here’s a complete before/after example of a typical computation function:

Before v0.5.1:

// Arcium.toml
[localnet]
nodes = 2
localnet_timeout_secs = 60

// programs/coinflip/src/lib.rs
pub fn flip(
    ctx: Context<Flip>,
    computation_offset: u64,
    user_choice: [u8; 32],
    pub_key: [u8; 32],
    nonce: u128,
) -> Result<()> {
    let args = vec![
        Argument::ArcisPubkey(pub_key),
        Argument::PlaintextU128(nonce),
        Argument::EncryptedU8(user_choice),
    ];

    ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;

    queue_computation(
        ctx.accounts,
        computation_offset,
        args,
        None,
        vec![FlipCallback::callback_ix(&[])],
        1,
    )?;

    Ok(())
}

#[queue_computation_accounts("flip", payer)]
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct Flip<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(
        mut,
        address = derive_mempool_pda!()
    )]
    pub mempool_account: UncheckedAccount<'info>,
    #[account(
        mut,
        address = derive_execpool_pda!()
    )]
    pub executing_pool: UncheckedAccount<'info>,
    #[account(
        mut,
        address = derive_comp_pda!(computation_offset)
    )]
    pub computation_account: UncheckedAccount<'info>,
    // ... other accounts
}
// tests/coinflip.ts
const arciumEnv = getArciumEnv();

await program.methods
  .flip(computationOffset, encryptedChoice, pubKey, nonce)
  .accountsPartial({
    computationAccount: getComputationAccAddress(program.programId, computationOffset),
    clusterAccount: arciumEnv.arciumClusterPubkey,
    mempoolAccount: getMempoolAccAddress(program.programId),
    executingPool: getExecutingPoolAccAddress(program.programId),
  })
  .rpc();

v0.5.1:

// Arcium.toml
[localnet]
nodes = 2
localnet_timeout_secs = 60
backends = ["Cerberus"]

// programs/coinflip/src/lib.rs
use arcium_anchor::prelude::*;

const COMP_DEF_OFFSET_FLIP: u32 = comp_def_offset("flip");

pub fn flip(
    ctx: Context<Flip>,
    computation_offset: u64,
    user_choice: [u8; 32],
    pub_key: [u8; 32],
    nonce: u128,
) -> Result<()> {
    let args = ArgBuilder::new()
        .x25519_pubkey(pub_key)
        .plaintext_u128(nonce)
        .encrypted_u8(user_choice)
        .build();

    ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;

    queue_computation(
        ctx.accounts,
        computation_offset,
        args,
        None,
        vec![FlipCallback::callback_ix(
            computation_offset,
            &ctx.accounts.mxe_account,
            &[]
        )?],
        1,
        0,  // cu_price_micro: priority fee (0 = no priority)
    )?;

    Ok(())
}

// Callback with BLS verification
#[arcium_callback(encrypted_ix = "flip")]
pub fn flip_callback(
    ctx: Context<FlipCallback>,
    output: SignedComputationOutputs<FlipOutput>,
) -> Result<()> {
    let result = match output.verify_output(
        &ctx.accounts.cluster_account,
        &ctx.accounts.computation_account
    ) {
        Ok(FlipOutput { field_0 }) => field_0,
        Err(e) => {
            msg!("Error: {}", e);
            return Err(ErrorCode::AbortedComputation.into())
        },
    };

    emit!(FlipResultEvent {
        result: result.ciphertexts[0],
        nonce: result.nonce.to_le_bytes(),
    });
    Ok(())
}

#[callback_accounts("flip")]
#[derive(Accounts)]
pub struct FlipCallback<'info> {
    pub arcium_program: Program<'info, Arcium>,
    #[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_FLIP))]
    pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
    #[account(address = derive_mxe_pda!())]
    pub mxe_account: Account<'info, MXEAccount>,
    /// CHECK: computation_account, checked by arcium program
    pub computation_account: UncheckedAccount<'info>,
    #[account(address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet))]
    pub cluster_account: Account<'info, Cluster>,
    #[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
    /// CHECK: instructions_sysvar
    pub instructions_sysvar: AccountInfo<'info>,
}

#[queue_computation_accounts("flip", payer)]
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct Flip<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(
        mut,
        address = derive_mempool_pda!(mxe_account, ErrorCode::ClusterNotSet)
    )]
    pub mempool_account: UncheckedAccount<'info>,
    #[account(
        mut,
        address = derive_execpool_pda!(mxe_account, ErrorCode::ClusterNotSet)
    )]
    pub executing_pool: UncheckedAccount<'info>,
    #[account(
        mut,
        address = derive_comp_pda!(
            computation_offset,
            mxe_account,
            ErrorCode::ClusterNotSet
        )
    )]
    pub computation_account: UncheckedAccount<'info>,
    // ... other accounts
}
// tests/coinflip.ts
import {
  getClusterAccAddress,
  getMXEAccAddress,
  getMempoolAccAddress,
  getExecutingPoolAccAddress,
  getComputationAccAddress,
  getCompDefAccAddress,
  getCompDefAccOffset,
  getArciumEnv,
} from "@arcium-hq/client";

const arciumEnv = getArciumEnv();

await program.methods
  .flip(computationOffset, encryptedChoice, pubKey, nonce)
  .accountsPartial({
    computationAccount: getComputationAccAddress(
      arciumEnv.arciumClusterOffset,
      computationOffset
    ),
    clusterAccount: getClusterAccAddress(arciumEnv.arciumClusterOffset),
    mxeAccount: getMXEAccAddress(program.programId),
    mempoolAccount: getMempoolAccAddress(arciumEnv.arciumClusterOffset),
    executingPool: getExecutingPoolAccAddress(arciumEnv.arciumClusterOffset),
    compDefAccount: getCompDefAccAddress(
      program.programId,
      Buffer.from(getCompDefAccOffset("flip")).readUInt32LE()
    ),
  })
  .rpc({ skipPreflight: true, commitment: "confirmed" });
That’s it! Your program should now be compatible with Arcium tooling v0.5.1.

16. Summary of breaking changes

ChangeBefore v0.5.1v0.5.1
queue_computation6 parameters7 parameters (add cu_price_micro)
callback_ixcallback_ix(&[])callback_ix(computation_offset, &mxe_account, &[])?
Callback outputComputationOutputs<T>SignedComputationOutputs<T> with verify_output()
PDA functionsprogram.programIdarciumEnv.arciumClusterOffset
EnvironmentARCIUM_CLUSTER_PUBKEYARCIUM_CLUSTER_OFFSET
PDA macrosderive_*_pda!()derive_*_pda!(mxe_account, ErrorCode::ClusterNotSet)
TS Program IDgetArciumProgAddress()getArciumProgramId()
TS Account DatagetMempoolAccData()getMempoolAccInfo()
TS ParametermxeProgramIDmxeProgramId (camelCase)
init_comp_definit_comp_def(accs, 0, None, None)init_comp_def(accs, None, None)
ArgBuilder pubkey.arcis_x25519_pubkey(key).x25519_pubkey(key)
Callback struct3 accounts6 accounts (add mxe_account, computation_account, cluster_account)
Offchain circuit hash[0u8; 32] placeholdercircuit_hash!("circuit_name")