# Arcium

> Build and debug encrypted Solana applications with Arcium — data stays private during computation, no single party sees it. Use when writing Arcis circuits (#[encrypted], #[instruction]), wiring Anchor programs with init/queue_computation/callback flows, choosing Shared vs Mxe encrypted state, encrypting inputs with @arcium-hq/client (RescueCipher, x25519), or debugging ArgBuilder ordering, nonce, callback, or computation finalization failures. Covers dark pools, sealed-bid auctions, encrypted voting, hidden game state, confidential DeFi, secure randomness, and threshold signing. Also use for getting started with your first Arcium app. SKIP: generic Solana programs without encrypted compute, AES or wallet-only encryption client code, MPC theory not tied to Arcium, or Token-2022 confidential transfers.

- Skill: `arcium-hq/arcium` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add arcium-hq/arcium`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arcium-hq/arcium/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: arcium-hq (https://skillmd.com/u/arcium-hq)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/arcium-hq/arcium

---


# Arcium

Encrypted computation on Solana via MPC. Data stays encrypted during computation. Your confidential app — a Solana program + Arcis circuits (the Rust circuit framework) + an on-chain metadata account — is an **MXE** (MPC eXecution Environment). The `arcium` CLI (wraps Anchor) handles init, build, test, and deploy — use MCP for current flags and options.

> **Targets Arcium v0.11.x** (Anchor 1.0.2, Solana 3.1.10 — unchanged from v0.10.x). Upgrading from v0.10.x? See the [v0.10 → v0.11 migration guide](https://docs.arcium.com/developers/migration/migration-v0.10.0-to-v0.11.0) — the one app-facing breaking change is `queue_computation`'s new 7th arg `callback_cu_limit: u32` (pass `0` for default). v0.11 also adds Arcis enums and `Option<T>` (additive; not usable as circuit input/output). Coming from v0.9.x? Do the [v0.9 → v0.10 migration](https://docs.arcium.com/developers/migration/migration-v0.9.0-to-v0.10.0) first (`init_comp_def` → `init_computation_def`, `@coral-xyz/anchor` → `@anchor-lang/core`, `Box<…>` on queue-side accounts).

**MCP Tools**: `search_arcium_docs` for discovery (returns page path), then `query_docs_filesystem_arcium_docs` with `cat <path>.mdx` for full-page reads (e.g., `cat /developers/arcis/mental-model.mdx`).

**No MCP?** Use the docs directly: [`docs.arcium.com/llms.txt`](https://docs.arcium.com/llms.txt) for the page index, or [`llms-full.txt`](https://docs.arcium.com/llms-full.txt) for all docs in one file.

## When to Use

**Use when:**
- You need trustless computation -- cryptographically guaranteed, no single party sees the data
- Multiple parties compute on combined data without revealing inputs
- On-chain state must remain encrypted but computable
- Privacy: sealed-bid auctions, voting, hidden game state, dark pools, confidential DeFi

**Constraints:**
- Fixed loop bounds required (no variable-length iteration)

## Mental Model

Arcium apps have three coupled surfaces. Most bugs are mismatches across their boundaries:

| Surface | Responsibility | Common Boundary Bugs |
|---------|---------------|----------------------|
| **Circuit** (Arcis/Rust) | Pure fixed-shape MPC logic | Variable loops, dynamic collections, `.reveal()` inside conditionals |
| **Program** (Anchor/Rust) | Orchestration: init + queue + callback | Macro name mismatch, callback accounts not writable, wrong ArgBuilder order |
| **Client** (TypeScript) | Key exchange, encryption, submission, decryption | Nonce reuse, missing `.x25519_pubkey()` for Shared, param order ≠ circuit order |

**MPC constraints** (from how secret sharing works):
- Both branches of `if/else` execute unless the condition is a compile-time constant — cost = sum of both branches, not max. **Same rule applies to non-constant `match` arms and `if let`** — all reachable arms execute.
- Loops must have fixed bounds — no `while`, `break`, `continue`
- Comparisons are expensive; arithmetic (add/multiply) is nearly free
- `.reveal()` and `.from_arcis()` cannot be called inside conditionals (exception: compile-time constant conditions; also forbidden inside non-constant `match` arms and guards)
- All data must be fixed-size — no `Vec`, `String`, `HashMap`; use `[T; N]`
- Pattern matching (`match`, `if let`, `matches!`) is supported in v0.10+. Last `match` arms cannot have guards. Let chains require `edition = "2024"` in `encrypted-ixs/Cargo.toml`.
- Enums and `Option<T>` are supported in v0.11+ (full `Option` combinator surface — `.unwrap_or`, `.map`, `.and_then`, etc.). Two constraints: neither can be a circuit input or output, and enum discriminants cannot be set explicitly.

## Intent Router

Identify what you're building, then read the linked reference before coding. For API details, CLI flags, deployment, and versions, use MCP directly.

| Intent | Read | MCP Query |
|--------|------|-----------|
| First Arcium app | [minimal-circuit.md](examples/minimal-circuit.md) | "hello world tutorial" |
| Core concepts / terminology (MXE, Arx, comp def) | [core-concepts](https://docs.arcium.com/developers/core-concepts) | "arcium core concepts" |
| Choose a pattern (stateless, stateful, multi-party) | [patterns.md](examples/patterns.md) | "arcium examples" |
| Circuit syntax (`#[encrypted]`, `#[instruction]`) | [patterns.md](examples/patterns.md) | "arcis encrypted instruction" |
| Shared vs Mxe encryption | See [Encryption Context](#encryption-context) below | "Shared vs Mxe encryption" |
| ArgBuilder ordering / ciphertext errors | [troubleshooting.md -- ArgBuilder Ordering Errors](references/troubleshooting.md#argbuilder-ordering-errors) | "ArgBuilder encrypted plaintext" |
| Callback not firing / computation stuck | [troubleshooting.md -- Computation Never Finalizes](references/troubleshooting.md#computation-never-finalizes) | "arcium_callback queue_computation" |
| Nonce / decryption errors | [troubleshooting.md -- Nonce Errors](references/troubleshooting.md#nonce-errors) | "RescueCipher encrypt nonce" |
| Client-side encryption (RescueCipher, x25519) | [minimal-circuit.md](examples/minimal-circuit.md) -- Test section | "RescueCipher encrypt nonce" |
| Threshold signing / secure randomness | [patterns.md](examples/patterns.md) | "MXESigningKey sign" or "ArcisRNG" |
| Re-encryption / sealing | [patterns.md](examples/patterns.md) | "arcium sealing re-encryption" |
| Deployment (devnet/mainnet) | — | "arcium deploy cluster-offset" |
| CI / GitHub Actions | [setup-arcium](https://github.com/arcium-hq/setup-arcium) | "arcium github actions ci" |
| Version / installation requirements | [installation docs](https://docs.arcium.com/developers/installation) | "arcium installation anchor solana" |
| Upgrading to v0.11.x (`callback_cu_limit`, enums/`Option`) | [migration guide](https://docs.arcium.com/developers/migration/migration-v0.10.0-to-v0.11.0) | "v0.11 migration" |
| Closing MXE / comp def to reclaim rent | — | "deactivate close computation definition" |

## Core Pattern: Three Functions

Every computation needs three functions in your Solana program:

| Function | Purpose | When Called |
|----------|---------|-------------|
| `init_<name>_comp_def` | Init computation definition — on-chain account holding the circuit's compiled bytecode | Once per instruction |
| `<name>` | Build args + queue computation | Each request |
| `<name>_callback` | Handle result from Arx nodes (the cluster's MPC compute nodes) | After MPC completes |

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

// 1. INIT (once per instruction type) — v0.10+ helper is `init_computation_def` (2 args)
pub fn init_flip_comp_def(ctx: Context<InitFlipCompDef>) -> Result<()> {
    init_computation_def(ctx.accounts, None)
}

// 2. QUEUE (each computation)
pub fn flip(ctx: Context<Flip>, offset: u64, ...) -> Result<()> {
    let args = ArgBuilder::new()...build();
    queue_computation(ctx.accounts, offset, args,
        vec![FlipCallback::callback_ix(offset, &ctx.accounts.mxe_account, &[])?],
        1, 0, 0, // num_callback_txs, cu_price_micro, callback_cu_limit (v0.11: 7th arg, 0 = default)
    )?;
    Ok(())
}

// 3. CALLBACK (after MPC completes)
#[arcium_callback(encrypted_ix = "flip")]
pub fn flip_callback(ctx: Context<FlipCallback>,
    output: SignedComputationOutputs<FlipOutput>) -> Result<()> {
    let result = output.verify_output(...)?;
    // Use result...
}
```

**Encryption size**: RescueCipher encrypts any scalar to 32 bytes regardless of type.
Formula: `ciphertext_size = 32 * number_of_scalar_values`. See [troubleshooting.md](references/troubleshooting.md) for the full size table.

## Encryption Context

`Enc<Owner, T>` — `Owner` is who can decrypt. Choose before writing the circuit.

| Scenario | Use |
|----------|-----|
| User submits input / result sealed back to that same user | `Enc<Shared, T>` |
| Internal state users shouldn't access | `Enc<Mxe, T>` |
| State persisted across computations (re-read by any party) | `Enc<Mxe, T>` |
| Result revealed to *everyone* | `.reveal()` |

**Wrong owner fails quietly:** persisted/multi-party state must be `Mxe` — `Shared` grants reveal capability to one client's x25519 key, leaking aggregate state to that recipient. If a different client key/nonce is used later, decryption yields garbage (MPC cannot raise a decrypt error without leaking). `Shared` also seals to *one* recipient; for a public result use `.reveal()`, not `Shared`. (Omitting `.x25519_pubkey()` on a `Shared` input is the same silent-failure class — see Gotchas.)

## Gotchas

> Reference during development to avoid common mistakes.

**NEVER:**
- NEVER reuse a nonce — every `cipher.encrypt()` call needs a fresh `randomBytes(16)`
- NEVER combine multiple ciphertexts into one ArgBuilder call — each encrypted scalar is its own `[u8; 32]` call
- NEVER omit `.x25519_pubkey()` for `Enc<Shared, T>` (silent failure); `Enc<Mxe, T>` skips it

### Critical (silent failures)
- **Box-wrap queue-side Arcium accounts (v0.10+)**: In `#[queue_computation_accounts]`, follow generated templates and wrap heavy accounts: `Box<Account<'info, MXEAccount>>`, `Box<Account<'info, Cluster>>`, `Box<Account<'info, ComputationDefinitionAccount>>`. Unboxed queue accounts can exceed Anchor/Solana stack limits. Callback-side (`#[callback_accounts]`) can stay unboxed.
- **Macro string matching**: All macro strings must exactly match `#[instruction] fn NAME` across `#[arcium_callback]`, `comp_def_offset()`, `#[init_computation_definition_accounts]`, `#[queue_computation_accounts]`, `#[callback_accounts]`
- **ArgBuilder ordering**: Calls must match circuit parameter order left-to-right. For `Enc<Shared, T>`: `.x25519_pubkey()` then `.plaintext_u128(nonce)` then ciphertexts. For `Enc<Mxe, T>`: `.plaintext_u128(nonce)` then ciphertexts. Missing `.x25519_pubkey()` for Shared = silent failure.
- **Division by secret zero**: Guard divisors with the safe divisor pattern -- both branches execute in MPC, so the division always runs. See [patterns.md — Safe Division](examples/patterns.md).
- **Combined ciphertext arrays**: Each encrypted scalar needs a separate `[u8; 32]` ArgBuilder call — do NOT pass `[u8; 64]` for a two-scalar type. See [troubleshooting.md — Ciphertext Size Mismatch](references/troubleshooting.md#ciphertext-size-mismatch).

### Warning (wrong results)
- **Nonce reuse**: Same nonce for multiple encryptions = garbled output. Use unique `randomBytes(16)` per encryption.
- **Callback account writability**: Pass extra accounts via `CallbackAccount { pubkey, is_writable: true }` in `callback_ix(..., &[...])`. Also mark `#[account(mut)]` in callback struct. Accounts cannot be created or resized during callbacks.
- **Output struct naming**: Circuit `fn add_together` generates `AddTogetherOutput`. Single returns use `field_0` (a `SharedEncryptedStruct<1>` or `MXEEncryptedStruct<1>` with `.ciphertexts` and `.nonce`). Tuple returns nest `field_0`, `field_1`, etc.

### Tips
- Prefer arithmetic over comparisons (cheaper in MPC)
- Comparisons/divisions are cheaper with narrower types (`u64` vs `u128`); storage cost is identical

## Debug Triage Order

> Start here when a computation fails or returns wrong results.

When a computation fails, returns wrong results, or never finalizes — check in this order:

1. **Names match exactly** — `#[instruction] fn NAME` must match across `#[arcium_callback(encrypted_ix = "NAME")]`, `comp_def_offset("NAME")`, and all account macros
2. **Comp def initialized** — `init_*_comp_def` must be called once before any computation
3. **ArgBuilder param order** — calls must match circuit fn parameters left-to-right
4. **Shared params include pubkey** — `.x25519_pubkey()` before `.plaintext_u128(nonce)` before ciphertexts (missing = silent failure)
5. **Nonce is unique** — fresh `randomBytes(16)` per encryption, same nonce passed to program
6. **Callback registered and writable** — `callback_ix(...)` passed in `queue_computation` call, accounts set in BOTH `CallbackAccount { pubkey, is_writable: true }` AND `#[account(mut)]` in callback struct
7. **Environment correct** — cluster offset matches network, MXE public key available (retry with backoff), RPC endpoint reliable

For detailed error solutions: [troubleshooting.md](references/troubleshooting.md)

## Verification Checklist

> Pre-deploy gate. Run through before deploying or submitting a PR.

**Circuit:**
- [ ] `arcium build` compiles without errors
- [ ] No `break`/`continue`/`return`/variable-length loops
- [ ] `#[instruction]` fn names are consistent across all macros

**Program:**
- [ ] `init_*_comp_def` called before first computation (once per instruction type)
- [ ] Every circuit fn has init + invoke + callback instructions
- [ ] `#[arcium_callback(encrypted_ix = "...")]` matches circuit fn name exactly
- [ ] Extra callback accounts passed via `CallbackAccount { pubkey, is_writable: true }` AND `#[account(mut)]` in callback struct

**Client:**
- [ ] Unique nonce per encryption (no reuse across calls)
- [ ] ArgBuilder call order matches circuit fn parameter order left-to-right
- [ ] `.x25519_pubkey()` included for every `Enc<Shared, T>` parameter
- [ ] Cluster offset matches deployment environment

**Deploy:**
- [ ] `arcium test` passes locally before deploy
- [ ] RPC endpoint is reliable (not default Solana RPC)
- [ ] CI pins `setup-arcium` and explicit Arcium/Anchor/Solana versions for reproducible toolchain installs

## Resources

- **MCP tools** (primary for API details, CLI flags, deployment, versions): `search_arcium_docs` + `query_docs_filesystem_arcium_docs` — [docs.arcium.com/mcp](https://docs.arcium.com/mcp)
- **Docs**: [docs.arcium.com/developers](https://docs.arcium.com/developers/) — no-MCP fallback: [llms.txt](https://docs.arcium.com/llms.txt) (index), [llms-full.txt](https://docs.arcium.com/llms-full.txt) (full)
- **v0.10 → v0.11 migration**: [migration guide](https://docs.arcium.com/developers/migration/migration-v0.10.0-to-v0.11.0) — `callback_cu_limit` 7th arg on `queue_computation`, Arcis enums and `Option<T>`
- **Account lifecycle / closing**: [account-lifecycle](https://docs.arcium.com/developers/program/account-lifecycle) — `deactivate-computation-definition` → TTL (180 slots) → `close-computation-definition` / `close-mxe`
- **Examples**: [github.com/arcium-hq/examples](https://github.com/arcium-hq/examples)
- **CI / GitHub Actions**: [github.com/arcium-hq/setup-arcium](https://github.com/arcium-hq/setup-arcium) — GitHub Action for CI toolchain installs; set Arcium/Anchor/Solana versions explicitly for v0.11.x instead of relying on defaults
- **TypeScript SDK**: `@arcium-hq/client` + `@arcium-hq/reader` (subscribe to computation events) — [ts.arcium.com/api](https://ts.arcium.com/api)
- **Patterns**: [patterns.md](examples/patterns.md) — curated circuit patterns (stateless, stateful, multi-party, randomness, packing, threshold signing, sealing)
- **Troubleshooting**: [troubleshooting.md](references/troubleshooting.md) — hard-to-debug errors
- **Minimal working app**: [minimal-circuit.md](examples/minimal-circuit.md) — circuit + program + test

