Migrate from v0.1.x to v0.2.x

1. Update Arcium Rust dependencies

# Update program dependencies
cd programs/*
cargo update --package arcium-client --precise 0.2.0
cargo update --package arcium-macros --precise 0.2.0
cargo update --package arcium-anchor --precise 0.2.0

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

2. Update Arcium TS dependencies

npm install @arcium-hq/[email protected]

3. (Optional) Update your Arcium.toml

We no longer need the clusters field under localnet in Arcium.toml. New file Arcium.toml should look like this:

[localnet]
# number of nodes in the single cluster of the localnet
nodes = 2
# number of seconds to wait for the localnet to come online
localnet_timeout_secs = 60

This change is optional, and leaving clusters field as is will still work.

4. Simplify your Arcium crate imports

No more 10 line import statements from arcium_anchor, arcium_client, arcium_macros, etc. All of them can be compressed such that the following:

use arcium_anchor::{
    comp_def_offset, derive_cluster_pda, derive_comp_def_pda, derive_comp_pda, derive_execpool_pda,
    derive_mempool_pda, derive_mxe_pda, init_comp_def, queue_computation, ComputationOutputs,
    ARCIUM_CLOCK_ACCOUNT_ADDRESS, ARCIUM_STAKING_POOL_ACCOUNT_ADDRESS, CLUSTER_PDA_SEED,
    COMP_DEF_PDA_SEED, COMP_PDA_SEED, EXECPOOL_PDA_SEED, MEMPOOL_PDA_SEED, MXE_PDA_SEED,
};
use arcium_client::idl::arcium::{
    accounts::{
        ClockAccount, Cluster, ComputationDefinitionAccount, PersistentMXEAccount,
        StakingPoolAccount,
    },
    program::Arcium,
    types::Argument,
    ID_CONST as ARCIUM_PROG_ID,
};
use arcium_macros::{
    arcium_callback, arcium_program, callback_accounts, init_computation_definition_accounts,
    queue_computation_accounts,
};

now becomes just

use arcium_anchor::prelude::*;

This includes all the basic imports required to setup and run the basic Arcium example program. For additional types such as CircuitSource, CallbackAccount, they will still need to be imported separately from arcium_client::idl::arcium::types module.

5. Update your Arcium CPI calls

In your init_comp_def calls, you need to add an additional parameter at the third position. So, it used to look like:

init_comp_def(ctx.accounts, true, None, None)?;

Now it should look like:

init_comp_def(ctx.accounts, true, 0, None, None)?;

You don't need to care about the 0 parameter, it's just a placeholder for the new parameter.

6. Update your callback functions

No more manually handling all output bytes from an Arcium computation. The new #[arcium_callback] macro will handle all the deserialization of bytes for you based on your circuit's generated interface file.

#[arcium_callback(encrypted_ix = "add_together")]
pub fn add_together_callback(
    ctx: Context<AddTogetherCallback>,
    output: ComputationOutputs,
) -> Result<()> {
    let bytes = if let ComputationOutputs::Bytes(bytes) = output {
        bytes
    } else {
        return Err(ErrorCode::AbortedComputation.into());
    };

    emit!(SumEvent {
        sum: bytes[48..80].try_into().unwrap(),
        nonce: bytes[32..48].try_into().unwrap(),
    });
    Ok(())
}

becomes

#[arcium_callback(encrypted_ix = "add_together")]
pub fn add_together_callback(
    ctx: Context<AddTogetherCallback>,
    output: ComputationOutputs<AddTogetherOutput>,
) -> Result<()> {
    let o = match output {
        ComputationOutputs::Success(AddTogetherOutput { field_0 }) => field_0,
        _ => return Err(ErrorCode::AbortedComputation.into()),
    };

    emit!(SumEvent {
        sum: o.ciphertexts[0],
        nonce: o.nonce.to_le_bytes(),
    });
    Ok(())
}

For a comprehensive guide on how the callback type generation system works, see Callback Type Generation.

7. Update your Context structs

First, all references to PersistentMXEAccount becomes just MXEAccount. This has to be done in both queue_computation_accounts and init_computation_definition_accounts context structs.

Second, we need to update the StakingPoolAccount to FeePool in your queue_computation_accounts Context structs.

#[account(
    mut,
    address = ARCIUM_STAKING_POOL_ACCOUNT_ADDRESS,
)]
pub pool_account: Account<'info, StakingPoolAccount>,

now becomes

#[account(
    mut,
    address = ARCIUM_FEE_POOL_ACCOUNT_ADDRESS,
)]
pub pool_account: Account<'info, FeePool>,

8. Add a new error code

In your program ErrorCode enum, add a new error code:

#[error_code]
pub enum ErrorCode {
    ...
    #[msg("The cluster is not set")]
    ClusterNotSet,
}

9. Replace static mxePublicKey

No more constant MXE public key definition in your tests or client. Instead you can import getMXEPublicKey function from @arcium-hq/client and use it to get the MXE public key.

const mxePublicKey = await getMXEPublicKey(
  provider as anchor.AnchorProvider,
  program.programId
);

This might cause some issues in your tests as the function might be called before the MXE keys are fully set. In which case, we recommend using the following helper function as a wrapper around getMXEPublicKey to fetch MXE public key with retries:

async function getMXEPublicKeyWithRetry(
  provider: anchor.AnchorProvider,
  programId: PublicKey,
  maxRetries: number = 10,
  retryDelayMs: number = 500
): Promise<Uint8Array> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const mxePublicKey = await getMXEPublicKey(provider, programId);
      if (mxePublicKey) {
        return mxePublicKey;
      }
    } catch (error) {
      console.log(`Attempt ${attempt} failed to fetch MXE public key:`, error);
    }

    if (attempt < maxRetries) {
      console.log(
        `Retrying in ${retryDelayMs}ms... (attempt ${attempt}/${maxRetries})`
      );
      await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
    }
  }

  throw new Error(
    `Failed to fetch MXE public key after ${maxRetries} attempts`
  );
}

And voila, you should have a working program that is compatible with Arcium tooling v0.2.0!

Last updated