Skip to main content
Unlike regular transactions, confidential computations involve additional steps after your Solana transaction completes:
  1. Your transaction completes - Confidential data is submitted and queued in the cluster’s mempool
  2. Computation waits in queue - MPC nodes process computations from the mempool in order
  3. MPC execution - When your computation’s turn comes, MPC nodes execute it offchain
  4. Callback invocation - Results are returned via your callback instruction
This means you can’t await a transaction completion like normal Solana programs. Instead, you need to wait for the entire computation lifecycle to finish. The Arcium client library provides utilities to handle this:

Await computation completion with awaitComputationFinalization

// Generate a random 8-byte computation offset
const computationOffset = new anchor.BN(randomBytes(8), "hex");

// `program` is the anchor program client of the MXE we're invoking
// the instruction `ourIx` on (which then invokes a computation under the hood by CPIing into the Arcium program).
// `queueSig` is the signature of said transaction.
const queueSig = await program.methods
  .ourIx(
    // Computation offset that you provide when invoking the instruction
    computationOffset
    /* other inputs */
  )
  .accounts(/* some accounts */)
  .rpc();

// Since this is an Arcium computation, we need to wait for it to be finalized
// a little bit differently
const finalizeSig = await awaitComputationFinalization(
  // Anchor provider
  provider as anchor.AnchorProvider,
  // Computation offset that you provide when invoking the instruction
  computationOffset,
  // Program ID of the MXE
  program.programId,
  // Solana commitment level, "confirmed" by default
  "confirmed"
);

console.log("Computation was finalized with sig: ", finalizeSig);
On v0.10.x, failed finalization reports a CircuitFailureReason such as OffChainCircuitFetchFailed, OffChainCircuitHashMismatch, CircuitCUMismatch, LocalCircuitFetchFailed, or CircuitSerialization.

Read and assert the callback result

Finalization tells you the callback has run, but not what it produced. Consume the result the way you would in any Solana program: await an emitted event, or read updated account state. Events are the common path because Arcium callbacks typically relay encrypted payloads for the client to decrypt offchain. Event-based decryption requires the callback to emit both the ciphertext and the nonce. See the callback-side emit! pattern in the Arcium program guide. On the client side, register the event listener before queuing the computation so it’s attached by the time the callback fires. Helpers and imports are omitted for brevity: awaitEvent is a test-scaffold wrapper around program.addEventListener, cipher comes from the encryption setup in Encrypting inputs, and expect / expectedSum are shown in a test context.
// Register the listener first to avoid a race where the callback fires
// before you've subscribed.
const sumEventPromise = awaitEvent("sumEvent");

const computationOffset = new anchor.BN(randomBytes(8), "hex");
const queueSig = await program.methods
  .ourIx(computationOffset /* other inputs */)
  .accounts(/* ... */)
  .rpc({ commitment: "confirmed" });

await awaitComputationFinalization(
  provider as anchor.AnchorProvider,
  computationOffset,
  program.programId,
  "confirmed"
);

// Callback has fired; resolve the event, decrypt offchain, and assert.
const sumEvent = await sumEventPromise;
const decrypted = cipher.decrypt([sumEvent.sum], new Uint8Array(sumEvent.nonce))[0];
expect(decrypted).to.equal(expectedSum);
The two valid orderings are:
  • Event path: subscribe → queue → finalize → await event → decrypt → assert
  • Account path: queue → finalize → read account → assert

What’s next?

Protecting inputs

Learn how to protect data before sending to the MXE.

Deployment

Deploy your MXE program to devnet or mainnet.