Skills arcium
git clone https://github.com/sendaifun/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/sendaifun/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/arcium" ~/.claude/skills/sendaifun-skills-arcium && rm -rf "$T"
skills/arcium/SKILL.mdArcium
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).
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, 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 for Shared, param order ≠ circuit order |
MPC constraints (from how secret sharing works):
- Both branches of
execute unless the condition is a compile-time constant — cost = sum of both branches, not maxif/else - Loops must have fixed bounds — no
,while
,breakcontinue - Comparisons are expensive; arithmetic (add/multiply) is nearly free
and.reveal()
cannot be called inside conditionals (exception: compile-time constant conditions).from_arcis()- All data must be fixed-size — no
,Vec
,String
; useHashMap[T; N]
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 | "hello world tutorial" |
| Choose a pattern (stateless, stateful, multi-party) | patterns.md | "arcium examples" |
Circuit syntax (, ) | 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" |
Core Pattern: Three Functions
Every computation needs three functions in your Solana program:
| Function | Purpose | When Called |
|---|---|---|
| Initialize computation definition | Once per instruction |
| Build args + queue computation | Each request |
| 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.
Encryption Context
| Scenario | Use |
|---|---|
| User inputs, results returned to user | |
| Internal state users shouldn't access | |
| State persisted across computations | |
| Final reveal to all parties | |
Gotchas
Reference during development to avoid common mistakes.
NEVER:
- NEVER reuse a nonce — every
call needs a freshcipher.encrypt()randomBytes(16) - NEVER combine multiple ciphertexts into one ArgBuilder call — each encrypted scalar is its own
call[u8; 32] - NEVER omit
for.x25519_pubkey()
(silent failure);Enc<Shared, T>
skips itEnc<Mxe, T>
Critical (silent failures)
- Macro string matching: All macro strings must exactly match
across#[instruction] fn NAME
,#[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>
then.x25519_pubkey()
then ciphertexts. For.plaintext_u128(nonce)
:Enc<Mxe, T>
then ciphertexts. Missing.plaintext_u128(nonce)
for Shared = silent failure..x25519_pubkey() - 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.
- Combined ciphertext arrays: Each encrypted scalar needs a separate
ArgBuilder call — do NOT pass[u8; 32]
for a two-scalar type. See troubleshooting.md — Ciphertext Size Mismatch.[u8; 64]
Warning (wrong results)
- Nonce reuse: Same nonce for multiple encryptions = garbled output. Use unique
per encryption.randomBytes(16) - Callback account writability: Pass extra accounts via
inCallbackAccount { pubkey, is_writable: true }
. Also markcallback_ix(..., &[...])
in callback struct. Accounts cannot be created or resized during callbacks.#[account(mut)] - Output struct naming: Circuit
generatesfn add_together
. Single returns useAddTogetherOutput
(afield_0
orSharedEncryptedStruct<1>
withMXEEncryptedStruct<1>
and.ciphertexts
). Tuple returns nest.nonce
,field_0
, etc.field_1
Tips
- Prefer arithmetic over comparisons (cheaper in MPC)
- Comparisons/divisions are cheaper with narrower types (
vsu64
); storage cost is identicalu128
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:
- Names match exactly —
must match across#[instruction] fn NAME
,#[arcium_callback(encrypted_ix = "NAME")]
, and all account macroscomp_def_offset("NAME") - Comp def initialized —
must be called once before any computationinit_*_comp_def - ArgBuilder param order — calls must match circuit fn parameters left-to-right
- Shared params include pubkey —
before.x25519_pubkey()
before ciphertexts (missing = silent failure).plaintext_u128(nonce) - Nonce is unique — fresh
per encryption, same nonce passed to programrandomBytes(16) - Callback registered and writable —
passed incallback_ix(...)
call, accounts set in BOTHqueue_computation
ANDCallbackAccount { pubkey, is_writable: true }
in callback struct#[account(mut)] - Environment correct — cluster offset matches network, MXE public key available (retry with backoff), RPC endpoint reliable
For detailed error solutions: troubleshooting.md
Verification Checklist
Pre-deploy gate. Run through before deploying or submitting a PR.
Circuit:
-
compiles without errorsarcium build - No
/break
/continue
/variable-length loopsreturn -
fn names are consistent across all macros#[instruction]
Program:
-
called before first computation (once per instruction type)init_*_comp_def - Every circuit fn has init + invoke + callback instructions
-
matches circuit fn name exactly#[arcium_callback(encrypted_ix = "...")] - Extra callback accounts passed via
ANDCallbackAccount { pubkey, is_writable: true }
in callback struct#[account(mut)]
Client:
- Unique nonce per encryption (no reuse across calls)
- ArgBuilder call order matches circuit fn parameter order left-to-right
-
included for every.x25519_pubkey()
parameterEnc<Shared, T> - Cluster offset matches deployment environment
Deploy:
-
passes locally before deployarcium test - RPC endpoint is reliable (not default Solana RPC)
Resources
- MCP tools (primary for API details, CLI flags, deployment, versions):
+search_arcium_docs
— docs.arcium.com/mcpquery_docs_filesystem_arcium_docs - Docs: docs.arcium.com/developers
- Examples: github.com/arcium-hq/examples
- TypeScript SDK: ts.arcium.com/api
- Patterns: patterns.md — 15 curated circuit patterns
- Troubleshooting: troubleshooting.md — hard-to-debug errors
- Minimal working app: minimal-circuit.md — circuit + program + test