sendaifun/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.
npx skills add https://github.com/sendaifun/skills --skill arcium
Encrypted computation on Solana via MPC. Data stays encrypted during computation. The arcium CLI (wraps Anchor) handles init, build, test, and deploy — use MCP for current flags and options.
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).
Use when:
Constraints:
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):
if/else execute unless the condition is a compile-time constant — cost = sum of both branches, not maxwhile, break, continue.reveal() and .from_arcis() cannot be called inside conditionals (exception: compile-time constant conditions)Vec, String, HashMap; use [T; N]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 | "hello world tutorial" |
| Choose a pattern (stateless, stateful, multi-party) | patterns.md | "arcium examples" |
| Circuit syntax (#encrypted], #[instruction]) | [patterns.md | "arcis encrypted instruction" |
| Shared vs Mxe encryption | See Encryption Context below | "Shared vs Mxe encryption" |
| ArgBuilder ordering / ciphertext errors | troubleshooting.md -- ArgBuilder Ordering Errors | "ArgBuilder encrypted plaintext" |
| Callback not firing / computation stuck | troubleshooting.md -- Computation Never Finalizes | "arcium_callback queue_computation" |
| Nonce / decryption errors | troubleshooting.md -- Nonce Errors | "RescueCipher encrypt nonce" |
| Client-side encryption (RescueCipher, x25519) | minimal-circuit.md -- Test section | "RescueCipher encrypt nonce" |
| Threshold signing / secure randomness | — | "MXESigningKey sign" or "ArcisRNG" |
| Deployment (devnet/mainnet) | — | "arcium deploy cluster-offset" |
| Version / installation requirements | — | "arcium installation anchor solana" |
Every computation needs three functions in your Solana program:
| Function | Purpose | When Called |
|----------|---------|-------------|
| init_<name>_comp_def | Initialize computation definition | Once per instruction |
| <name> | Build args + queue computation | Each request |
| <name>_callback | Handle result from Arx nodes | After MPC completes |
const COMP_DEF_OFFSET_FLIP: u32 = comp_def_offset("flip");
// 1. INIT (once per instruction type)
pub fn init_flip_comp_def(ctx: Context<InitFlipCompDef>) -> Result<()> {
init_comp_def(ctx.accounts, None, 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,
)?;
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 for the full size table.
| Scenario | Use |
|----------|-----|
| User inputs, results returned to user | Enc<Shared, T> |
| Internal state users shouldn't access | Enc<Mxe, T> |
| State persisted across computations | Enc<Mxe, T> |
| Final reveal to all parties | .reveal() |
> Reference during development to avoid common mistakes.
NEVER:
cipher.encrypt() call needs a fresh randomBytes(16)[u8; 32] call.x25519_pubkey() for Enc<Shared, T> (silent failure); Enc<Mxe, T> skips it#[instruction] fn NAME across #[arcium_callback], comp_def_offset(), #[init_computation_definition_accounts], #[queue_computation_accounts], #[callback_accounts]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.u8; 32] ArgBuilder call — do NOT pass [u8; 64] for a two-scalar type. See [troubleshooting.md — Ciphertext Size Mismatch.randomBytes(16) per encryption.CallbackAccount { pubkey, is_writable: true } in callback_ix(..., &[...]). Also mark #[account(mut)] in callback struct. Accounts cannot be created or resized during callbacks.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.u64 vs u128); storage cost is identical> Start here when a computation fails or returns wrong results.
When a computation fails, returns wrong results, or never finalizes — check in this order:
#[instruction] fn NAME must match across #[arcium_callback(encrypted_ix = "NAME")], comp_def_offset("NAME"), and all account macrosinit_*_comp_def must be called once before any computation.x25519_pubkey() before .plaintext_u128(nonce) before ciphertexts (missing = silent failure)randomBytes(16) per encryption, same nonce passed to programcallback_ix(...) passed in queue_computation call, accounts set in BOTH CallbackAccount { pubkey, is_writable: true } AND #[account(mut)] in callback structFor detailed error solutions: troubleshooting.md
> Pre-deploy gate. Run through before deploying or submitting a PR.
Circuit:
arcium build compiles without errorsbreak/continue/return/variable-length loops#[instruction] fn names are consistent across all macrosProgram:
init_*_comp_def called before first computation (once per instruction type)#[arcium_callback(encrypted_ix = "...")] matches circuit fn name exactlyCallbackAccount { pubkey, is_writable: true } AND #[account(mut)] in callback structClient:
.x25519_pubkey() included for every Enc<Shared, T> parameterDeploy:
arcium test passes locally before deploysearch_arcium_docs + query_docs_filesystem_arcium_docs — docs.arcium.com/mcpTake sendaifun/arcium from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.