SKILL.md
---
name: arcium-program-development
description: Build, refactor, and debug Arcium MXE program code across Arcis circuits (`encrypted-ixs`) and Anchor programs (`programs/*`). Use when implementing confidential instructions, wiring `init_comp_def`/`queue_computation`/callbacks, deriving Arcium PDAs and callback accounts, handling encrypted I/O and offsets, parsing generated callback output structs, building permission-gated flows, integrating offchain circuit sources, testing with `@arcium-hq/client`, deploying MXEs, or migrating Arcium versions.
---
# Arcium Program Development
Use this skill to implement and debug Arcium computations end-to-end with strict contracts across Arcis circuits, Anchor program wiring, callback verification, and client/test encryption flow.
## Decision Tree (Task Classification)
Classify the request before editing code:
1. **Stateless computation** (e.g., Coinflip)
- No persistent encrypted state account update.
- Usually one encrypted input and one revealed/encrypted output.
- Read first: `references/implementation-playbook.md`, `references/callback-output-shapes.md`.
2. **Stateful encrypted account flow** (e.g., Voting, Sealed Bid, Blackjack)
- Requires `.account(pubkey, offset, len)` with exact byte layout.
- Usually callback writes ciphertext + nonce to account state.
- Read first: `references/account-layout-offsets.md`, `references/implementation-playbook.md`.
3. **Permission-gated flow** (e.g., Encrypted DNA matching)
- Constraints and status transitions determine whether queueing is allowed.
- Read first: `references/permission-and-state-machines.md`, `references/examples-patterns.md`.
4. **Offchain circuit source flow**
- `init_comp_def` uses `CircuitSource::OffChain` and `circuit_hash!`.
- Read first: `references/docs-and-migrations.md`, `references/troubleshooting-matrix.md`.
5. **Migration or compatibility update**
- Update Rust/TypeScript dependencies and API call signatures first.
- Read first: `references/docs-and-migrations.md`, `references/anti-patterns.md`.
6. **Debugging request**
- Triaged by stage: encryption -> queue -> callback verify -> finalization.
- Read first: `references/troubleshooting-matrix.md`, `references/test-client-patterns.md`.
## Mandatory Build Contract
Keep these invariants aligned in every implementation:
1. Circuit function name (`#[instruction]`) must match `comp_def_offset("...")` identifier.
2. Program callback macro must match instruction name:
- `#[arcium_callback(encrypted_ix = "<ix_name>")]`
3. Callback output type must match generated type:
- `SignedComputationOutputs<<IxName>Output>`
4. Queue and callback contexts must reference the same comp-def account and cluster derivation.
5. Every callback must call `verify_output(&cluster_account, &computation_account)` before consuming output.
6. `init_*_comp_def` must exist for every queued encrypted instruction.
## ArgBuilder Contract (Strict Ordering)
### `Enc<Shared, T>` input contract
Order is strict and must be preserved:
1. `x25519_pubkey(<client_pubkey>)`
2. `plaintext_u128(<nonce>)`
3. encrypted fields in exact circuit argument order (`encrypted_u8/u16/u32/u64/u128/bool/...`)
### `Enc<Mxe, T>` input contract
Order is strict and must be preserved:
1. `plaintext_u128(<mxe_nonce>)`
2. encrypted fields in exact circuit argument order
### Account-backed encrypted state contract
Use:
- `.account(<state_account>, <byte_offset>, <byte_len>)`
Rules:
1. Offset must start after Anchor discriminator (`8`) plus preceding fixed fields.
2. Length must match ciphertext field count times `32` bytes (or packed struct contract).
3. Add inline comments documenting offset derivation.
See detailed formulas and examples in `references/account-layout-offsets.md`.
## Callback Output Parsing Contract
Two common shapes are generated:
1. **Simple shape**
```rust
Ok(MyIxOutput { field_0 }) => field_0
```
Used for single output structs/values.
2. **Nested shape**
```rust
Ok(MyIxOutput {
field_0: MyIxOutputStruct0 {
field_0: a,
field_1: b,
field_2: c,
},
}) => (a, b, c)
```
Used when return type is a tuple or multi-field struct.
Mandatory checks:
1. Verify output before parsing.
2. Check ciphertext cardinality when business logic expects exact count.
3. Persist/emit only after successful verification and shape checks.
See `references/callback-output-shapes.md`.
## Account Offset Contract
Use this formula for encrypted payload start:
```text
offset = 8 (Anchor discriminator) + sum(size of preceding account fields)
```
Examples:
- Voting counters: `8 + 1` (discriminator + bump)
- Sealed auction encrypted state: `8 + 1 + 32 + 1 + 1 + 8 + 8 + 1 + 16`
- DNA markers block: `8 + 32 + 16 + 32`
When adding or reordering account fields, recompute offsets immediately.
## Failure Triage Tree
1. **Encryption stage failure**
- Symptoms: decrypt mismatch, invalid nonce usage, unusable ciphertext.
- Check x25519 keypair generation, nonce serialization (`deserializeLE`), shared secret pairing.
- Reference: `references/test-client-patterns.md`, `references/troubleshooting-matrix.md`.
2. **Queue stage failure**
- Symptoms: account not found, constraint errors, custom program errors.
- Check cluster offset, PDA derivation, comp-def account offset, account ordering, constraint seeds.
- Reference: `references/account-layout-offsets.md`, `references/permission-and-state-machines.md`.
3. **Callback verify failure**
- Symptoms: `AbortedComputation`, output parse mismatch.
- Check comp-def alignment, callback macro ix name, generated output shape assumptions.
- Reference: `references/callback-output-shapes.md`.
4. **Finalization stage failure**
- Symptoms: queue tx confirmed but no resolved result.
- Check `awaitComputationFinalization(...)` usage, computation offset mismatch, event listener sequencing.
- Reference: `references/test-client-patterns.md`, `references/troubleshooting-matrix.md`.
## Templates (Scaffolding)
Use templates in `assets/templates` to start implementations quickly:
- `assets/templates/new-computation/arcis-instruction.rs.tpl`
- `assets/templates/new-computation/program-flow.rs.tpl`
- `assets/templates/new-computation/callback.rs.tpl`
- `assets/templates/new-computation/init-comp-def.rs.tpl`
- `assets/templates/new-computation/e2e-test.ts.tpl`
- `assets/templates/offchain-circuit/init-comp-def-offchain.rs.tpl`
- `assets/templates/permissioned-flow/accounts-and-constraints.rs.tpl`
Replace placeholders such as `<IX_NAME>`, `<PROGRAM_ID>`, `<STATE_OFFSET>`, `<STATE_LEN>`.
## Task Routing
- End-to-end implementation workflow:
Read `references/implementation-playbook.md`.
- Pattern selection by example:
Read `references/examples-patterns.md`.
- Account layout and offset math:
Read `references/account-layout-offsets.md`.
- Callback output decoding:
Read `references/callback-output-shapes.md`.
- Permissions and state transitions:
Read `references/permission-and-state-machines.md`.
- Test/client orchestration:
Read `references/test-client-patterns.md`.
- Migrations and deployment:
Read `references/docs-and-migrations.md`.
- Failure triage:
Read `references/troubleshooting-matrix.md`.
- What to avoid:
Read `references/anti-patterns.md`.
## Definition of Done
Code changes are complete only when all checks pass:
1. Structural/build checks:
```bash
arcium build
cargo check --all
arcium test
```
2. Skill checks:
```bash
python3 /Users/grisahudozestvennyj/.codex/skills/.system/skill-creator/scripts/quick_validate.py /Users/grisahudozestvennyj/Documents/projects/arcium/dna/skills/arcium-program-development
rg -n "[\p{Cyrillic}]" /Users/grisahudozestvennyj/Documents/projects/arcium/dna/skills/arcium-program-development || true
```
3. Functional acceptance checks:
- Circuit name, comp-def offset, callback macro, and callback output type are aligned.
- ArgBuilder order matches circuit input ownership contract.
- Offset constants match actual account layout and are documented.
- Callback verifies output before parsing/persisting.
- Tests wait for computation finalization and validate expected results.
## Delivery Standard
- Produce concrete code edits, not abstract guidance.
- Preserve existing seed derivation and PDA conventions unless migration requires changes.
- State assumptions explicitly when required inputs are missing.
- End with executed validation commands and any remaining risk notes.
IMPROVEMENTS.md
# Improvement Proposals
Use this template when suggesting improvements.
## Proposal template
### 1. Problem
Describe what is missing, unclear, or incorrect in the current skill.
### 2. Real scenario
Provide a concrete user request (or coding task) where this fails.
### 3. Proposed change
Explain exactly what should be updated:
- `SKILL.md` section(s)
- `references/*.md` file(s)
- new patterns/examples to add
### 4. Source of truth
Add links to official docs/examples that support the change.
### 5. Expected impact
Describe what should improve after this change.
## Priority labels
- `P1` critical: incorrect guidance can break implementations
- `P2` important: frequent workflow gaps
- `P3` nice-to-have: clarity and quality improvements
CONTRIBUTING.md
# Contributing
Thanks for helping improve this skill.
## How to suggest an improvement
1. Open an Issue in this repository with a short, clear title.
2. Include:
- the current problem
- the expected behavior
- a real request/task where the skill currently fails or underperforms
3. If possible, include:
- the `SKILL.md` or `references/*.md` section you want to change
- links to official docs or source material
## How to submit a change
1. Fork the repository.
2. Create a branch for your change.
3. Update `SKILL.md` and/or `references/*`.
4. Validate the skill before opening a PR.
5. Open a Pull Request explaining what changed and why.
## Quality bar
- Keep guidance practical and testable.
- Version/API claims should be backed by official docs.
- Avoid filler text: include only content that improves real coding outcomes.
agents/openai.yaml
interface:
display_name: "Arcium Program Development"
short_description: "Exhaustive Arcium program implementation, migration, and debugging playbook with reusable templates"
default_prompt: "Use $arcium-program-development to implement or debug this Arcium computation end-to-end, following the decision tree, references, and templates in assets/templates."
assets/templates/new-computation/e2e-test.ts.tpl
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import { randomBytes } from "crypto";
import {
awaitComputationFinalization,
deserializeLE,
getArciumAccountBaseSeed,
getArciumEnv,
getArciumProgram,
getArciumProgramId,
getClusterAccAddress,
getCompDefAccOffset,
getComputationAccAddress,
getExecutingPoolAccAddress,
getLookupTableAddress,
getMempoolAccAddress,
getMXEAccAddress,
getMXEPublicKey,
RescueCipher,
x25519,
} from "@arcium-hq/client";
import { <PROGRAM_IDL_TYPE> } from "../target/types/<PROGRAM_FILE_STEM>";
describe("<PROGRAM_SUITE_NAME>", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const provider = anchor.getProvider() as anchor.AnchorProvider;
const program = anchor.workspace.<PROGRAM_WORKSPACE_NAME> as Program<<PROGRAM_IDL_TYPE>>;
it("queues <IX_NAME> and validates callback result", async () => {
const payer = (provider.wallet as anchor.Wallet).payer;
const arciumEnv = getArciumEnv();
const clusterAccount = getClusterAccAddress(arciumEnv.arciumClusterOffset);
await initCompDefIfNeeded(program, payer);
const mxePublicKey = await getMXEPublicKeyWithRetry(provider, program.programId);
// Build encrypted payload
const senderSecret = x25519.utils.randomSecretKey();
const senderPublic = x25519.getPublicKey(senderSecret);
const sharedSecret = x25519.getSharedSecret(senderSecret, mxePublicKey);
const cipher = new RescueCipher(sharedSecret);
const nonce = randomBytes(16);
const plaintext = [BigInt(<PLAINTEXT_EXAMPLE>)];
const encrypted = cipher.encrypt(plaintext, nonce);
const computationOffset = new anchor.BN(randomBytes(8), "le");
const queueSig = await program.methods
.<QUEUE_METHOD_NAME>(
computationOffset,
Array.from(senderPublic),
new anchor.BN(deserializeLE(nonce).toString()),
Array.from(encrypted[0]),
new anchor.BN(<STATE_NONCE_EXAMPLE>)
)
.accountsPartial({
payer: payer.publicKey,
mxeAccount: getMXEAccAddress(program.programId),
mempoolAccount: getMempoolAccAddress(arciumEnv.arciumClusterOffset),
executingPool: getExecutingPoolAccAddress(arciumEnv.arciumClusterOffset),
computationAccount: getComputationAccAddress(
arciumEnv.arciumClusterOffset,
computationOffset
),
clusterAccount,
})
.signers([payer])
.rpc({ commitment: "confirmed", preflightCommitment: "confirmed" });
const finalizeSig = await awaitComputationFinalization(
provider,
computationOffset,
program.programId,
"confirmed"
);
console.log("queue:", queueSig);
console.log("finalize:", finalizeSig);
// TODO: fetch updated account/event and assert decrypted or expected output semantics.
});
});
async function initCompDefIfNeeded(
program: Program<<PROGRAM_IDL_TYPE>>,
payer: anchor.web3.Keypair
): Promise<void> {
const baseSeed = getArciumAccountBaseSeed("ComputationDefinitionAccount");
const offset = getCompDefAccOffset("<IX_NAME>");
const compDefPda = PublicKey.findProgramAddressSync(
[baseSeed, program.programId.toBuffer(), offset],
getArciumProgramId()
)[0];
const existing = await program.provider.connection.getAccountInfo(compDefPda);
if (existing) {
return;
}
const arciumProgram = getArciumProgram(program.provider as anchor.AnchorProvider);
const mxeAccount = getMXEAccAddress(program.programId);
const mxe = await arciumProgram.account.mxeAccount.fetch(mxeAccount);
const lutAddress = getLookupTableAddress(program.programId, mxe.lutOffsetSlot);
await program.methods
.<INIT_COMP_DEF_METHOD_NAME>()
.accounts({
payer: payer.publicKey,
compDefAccount: compDefPda,
mxeAccount,
addressLookupTable: lutAddress,
})
.signers([payer])
.rpc({ commitment: "confirmed", preflightCommitment: "confirmed" });
}
async function getMXEPublicKeyWithRetry(
provider: anchor.AnchorProvider,
programId: PublicKey,
retries = 10,
delayMs = 1500
): Promise<Uint8Array> {
let lastErr: unknown;
for (let attempt = 1; attempt <= retries; attempt += 1) {
try {
return await getMXEPublicKey(provider, programId);
} catch (err) {
lastErr = err;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
throw new Error(`unable to fetch MXE public key after retries: ${String(lastErr)}`);
}
assets/templates/new-computation/callback.rs.tpl
#[callback_accounts("<IX_NAME>")]
#[derive(Accounts)]
pub struct <CALLBACK_CONTEXT_NAME><'info> {
pub arcium_program: Program<'info, Arcium>,
#[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_<IX_NAME_UPPER>))]
pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
#[account(address = derive_mxe_pda!())]
pub mxe_account: Account<'info, MXEAccount>,
/// CHECK: validated by arcium callback constraints
pub computation_account: UncheckedAccount<'info>,
#[account(address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet))]
pub cluster_account: Account<'info, Cluster>,
#[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
/// CHECK: validated by constraint
pub instructions_sysvar: AccountInfo<'info>,
#[account(mut)]
pub <STATE_ACCOUNT>: Account<'info, <STATE_ACCOUNT_TYPE>>,
}
#[arcium_callback(encrypted_ix = "<IX_NAME>")]
pub fn <CALLBACK_FN_NAME>(
ctx: Context<<CALLBACK_CONTEXT_NAME>>,
output: SignedComputationOutputs<<OUTPUT_TYPE_NAME>>,
) -> Result<()> {
let decoded = match output.verify_output(
&ctx.accounts.cluster_account,
&ctx.accounts.computation_account,
) {
Ok(v) => v,
Err(_) => return Err(ErrorCode::AbortedComputation.into()),
};
// Simple shape example:
// let value = match decoded { <OUTPUT_TYPE_NAME> { field_0 } => field_0 };
// Nested shape example:
// let (a, b) = match decoded {
// <OUTPUT_TYPE_NAME> {
// field_0: <OUTPUT_STRUCT0_NAME> { field_0: a, field_1: b },
// } => (a, b),
// };
let encrypted_result = match decoded {
<OUTPUT_TYPE_NAME> { field_0 } => field_0,
};
// Mandatory when indexing ciphertext arrays.
require!(
encrypted_result.ciphertexts.len() >= <MIN_CT_COUNT>,
ErrorCode::<INVALID_CT_COUNT_ERROR>
);
let state = &mut ctx.accounts.<STATE_ACCOUNT>;
state.<STATE_NONCE_FIELD> = encrypted_result.nonce;
state.<STATE_CT_FIELD_0> = encrypted_result.ciphertexts[0];
// Optional event emission.
// emit!(<EVENT_NAME> { ... });
Ok(())
}
assets/templates/new-computation/program-flow.rs.tpl
use anchor_lang::prelude::*;
use arcium_anchor::prelude::*;
use arcium_client::idl::arcium::types::CallbackAccount;
const COMP_DEF_OFFSET_<IX_NAME_UPPER>: u32 = comp_def_offset("<IX_NAME>");
declare_id!("<PROGRAM_ID>");
#[arcium_program]
pub mod <PROGRAM_MODULE_NAME> {
use super::*;
pub fn <QUEUE_IX_NAME>(
ctx: Context<<QUEUE_CONTEXT_NAME>>,
computation_offset: u64,
<CLIENT_PUBKEY_ARG>: [u8; 32],
<CLIENT_NONCE_ARG>: u128,
<INPUT_CT_0_ARG>: [u8; 32],
<STATE_NONCE_ARG>: u128,
) -> Result<()> {
// Optional pre-queue guards (permission/state machine checks)
// require!(..., ErrorCode::<ERR_NAME>);
let args = ArgBuilder::new()
// Enc<Shared, T> contract: x25519_pubkey -> nonce -> encrypted fields in circuit order.
.x25519_pubkey(<CLIENT_PUBKEY_ARG>)
.plaintext_u128(<CLIENT_NONCE_ARG>)
.encrypted_u64(<INPUT_CT_0_ARG>)
// Existing account-backed encrypted state (if applicable)
.plaintext_u128(<STATE_NONCE_ARG>)
.account(
ctx.accounts.<STATE_ACCOUNT>.key(),
<STATE_OFFSET>, // 8 + ... (document every preceding field byte)
<STATE_LEN>, // ciphertext_count * 32
)
.build();
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
queue_computation(
ctx.accounts,
computation_offset,
args,
vec![<CALLBACK_CONTEXT_NAME>::callback_ix(
computation_offset,
&ctx.accounts.mxe_account,
&[CallbackAccount {
pubkey: ctx.accounts.<STATE_ACCOUNT>.key(),
is_writable: true,
}],
)?],
1,
0,
)?;
Ok(())
}
}
#[queue_computation_accounts("<IX_NAME>", <PAYER_ACCOUNT_NAME>)]
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct <QUEUE_CONTEXT_NAME><'info> {
#[account(mut)]
pub <PAYER_ACCOUNT_NAME>: Signer<'info>,
#[account(mut)]
pub <STATE_ACCOUNT>: Account<'info, <STATE_ACCOUNT_TYPE>>,
#[account(
init_if_needed,
space = 9,
payer = <PAYER_ACCOUNT_NAME>,
seeds = [&SIGN_PDA_SEED],
bump,
address = derive_sign_pda!(),
)]
pub sign_pda_account: Account<'info, ArciumSignerAccount>,
#[account(address = derive_mxe_pda!())]
pub mxe_account: Account<'info, MXEAccount>,
#[account(mut, address = derive_mempool_pda!(mxe_account, ErrorCode::ClusterNotSet))]
/// CHECK: validated by arcium program
pub mempool_account: UncheckedAccount<'info>,
#[account(mut, address = derive_execpool_pda!(mxe_account, ErrorCode::ClusterNotSet))]
/// CHECK: validated by arcium program
pub executing_pool: UncheckedAccount<'info>,
#[account(mut, address = derive_comp_pda!(computation_offset, mxe_account, ErrorCode::ClusterNotSet))]
/// CHECK: validated by arcium program
pub computation_account: UncheckedAccount<'info>,
#[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_<IX_NAME_UPPER>))]
pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
#[account(mut, address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet))]
pub cluster_account: Account<'info, Cluster>,
#[account(mut, address = ARCIUM_FEE_POOL_ACCOUNT_ADDRESS)]
pub pool_account: Account<'info, FeePool>,
#[account(mut, address = ARCIUM_CLOCK_ACCOUNT_ADDRESS)]
pub clock_account: Account<'info, ClockAccount>,
pub system_program: Program<'info, System>,
pub arcium_program: Program<'info, Arcium>,
}
assets/templates/new-computation/arcis-instruction.rs.tpl
use arcis::*;
#[encrypted]
mod circuits {
use arcis::*;
pub struct <INPUT_STRUCT_NAME> {
pub <INPUT_FIELD_A>: u64,
pub <INPUT_FIELD_B>: bool,
}
pub struct <OUTPUT_STRUCT_NAME> {
pub <OUTPUT_FIELD_A>: u64,
pub <OUTPUT_FIELD_B>: bool,
}
#[instruction]
pub fn <IX_NAME>(
// For Enc<Shared, T>, program/client must pass x25519 pubkey then nonce, then ciphertext fields.
input_ctxt: Enc<Shared, <INPUT_STRUCT_NAME>>,
// Include Mxe/Shared owners explicitly when output ownership differs.
receiver: Shared,
) -> Enc<Shared, <OUTPUT_STRUCT_NAME>> {
let input = input_ctxt.to_arcis();
let output = <OUTPUT_STRUCT_NAME> {
<OUTPUT_FIELD_A>: input.<INPUT_FIELD_A>,
<OUTPUT_FIELD_B>: input.<INPUT_FIELD_B>,
};
receiver.from_arcis(output)
}
}
assets/templates/new-computation/init-comp-def.rs.tpl
pub fn init_<IX_NAME>_comp_def(ctx: Context<Init<IX_PASCAL>CompDef>) -> Result<()> {
init_comp_def(ctx.accounts, None, None)?;
Ok(())
}
#[init_computation_definition_accounts("<IX_NAME>", payer)]
#[derive(Accounts)]
pub struct Init<IX_PASCAL>CompDef<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut, address = derive_mxe_pda!())]
pub mxe_account: Box<Account<'info, MXEAccount>>,
#[account(mut)]
/// CHECK: validated by arcium program
pub comp_def_account: UncheckedAccount<'info>,
#[account(mut, address = derive_mxe_lut_pda!(mxe_account.lut_offset_slot))]
/// CHECK: validated by arcium program
pub address_lookup_table: UncheckedAccount<'info>,
#[account(address = LUT_PROGRAM_ID)]
/// CHECK: Address Lookup Table program
pub lut_program: UncheckedAccount<'info>,
pub arcium_program: Program<'info, Arcium>,
pub system_program: Program<'info, System>,
}
README.md
# Arcium Program Skill
A reusable skill for working with Arcium MXE program code (Arcis + Anchor), prepared for the `skills.sh` ecosystem.
## Install
```bash
npx skills add sicmundu/arcium-program-skill
```
## What this skill helps with
- Designing and implementing encrypted instructions in `encrypted-ixs`
- Wiring `init_comp_def`, `queue_computation`, and callbacks in program code
- Correct `ArgBuilder` usage for `Enc<Shared, T>` and `Enc<Mxe, T>`
- Callback output verification and ciphertext/nonce handling
- PDA/account-offset handling for encrypted account state
- Arcium migration and deployment checks
## Feedback and improvements
- Read [CONTRIBUTING.md](./CONTRIBUTING.md)
- Use [IMPROVEMENTS.md](./IMPROVEMENTS.md) to propose changes
Contributions are welcome.
assets/templates/offchain-circuit/init-comp-def-offchain.rs.tpl
use arcium_client::idl::arcium::types::{CircuitSource, OffChainCircuitSource};
use arcium_macros::circuit_hash;
pub fn init_<IX_NAME>_comp_def(ctx: Context<Init<IX_PASCAL>CompDef>) -> Result<()> {
init_comp_def(
ctx.accounts,
Some(CircuitSource::OffChain(OffChainCircuitSource {
// Keep this URL versioned/immutable for reproducible deployments.
source: "<OFFCHAIN_CIRCUIT_URL>".to_string(),
// Must match encrypted instruction name exactly.
hash: circuit_hash!("<IX_NAME>"),
})),
None,
)?;
Ok(())
}
references/account-layout-offsets.md
# Account Layout and Offset Guide
## Table of Contents
- [Offset Formula](#offset-formula)
- [Anchor Size Rules](#anchor-size-rules)
- [Worked Example: Voting](#worked-example-voting)
- [Worked Example: Sealed Bid Auction](#worked-example-sealed-bid-auction)
- [Worked Example: Encrypted DNA Matching](#worked-example-encrypted-dna-matching)
- [Offset Debugging Workflow](#offset-debugging-workflow)
- [Inline Comment Contract](#inline-comment-contract)
## Offset Formula
When queueing encrypted account-backed state via:
```rust
.account(account_pubkey, offset, length)
```
use:
```text
offset = 8 (Anchor discriminator) + size_of(all preceding account fields)
length = ciphertext_field_count * 32 (unless custom packed span)
```
Never hardcode offset without documenting the exact field math.
## Anchor Size Rules
Common fixed-size field costs:
- `u8` / `bool`: 1
- `u16`: 2
- `u32`: 4
- `u64` / `i64`: 8
- `u128`: 16
- `Pubkey`: 32
- fixed array `[u8; 32]`: 32
- nested fixed arrays: multiply dimensions
- Anchor discriminator: 8 bytes at account start
Enum caveat:
- For `#[derive(AnchorSerialize, AnchorDeserialize)]` enums used in accounts, practical examples here use 1-byte discriminant. If enum representation changes, recompute layout and revalidate offsets.
## Worked Example: Voting
Source account fields (`Poll`-like layout used by queueing logic):
- `bump: u8` (1)
- encrypted vote state starts immediately after `bump`
Offset used in program:
```rust
// Offset calculation: 8 bytes (discriminator) + 1 byte (bump)
8 + 1
```
Length used:
```rust
32 * 2 // yes/no ciphertext counters
```
Result:
- `offset = 9`
- `length = 64`
## Worked Example: Sealed Bid Auction
Account fields before encrypted state (`Auction`):
- discriminator: 8
- `bump: u8` -> +1
- `authority: Pubkey` -> +32
- `auction_type: enum` -> +1
- `status: enum` -> +1
- `min_bid: u64` -> +8
- `end_time: i64` -> +8
- `bid_count: u8` -> +1
- `state_nonce: u128` -> +16
Total offset:
```text
8 + 1 + 32 + 1 + 1 + 8 + 8 + 1 + 16 = 76
```
Encrypted state shape:
- `encrypted_state: [[u8; 32]; 5]`
Length:
```text
5 * 32 = 160
```
Program constants:
```rust
const ENCRYPTED_STATE_OFFSET: u32 = 76;
const ENCRYPTED_STATE_SIZE: u32 = 32 * 5;
```
## Worked Example: Encrypted DNA Matching
`GenomeVault` queue reads marker ciphertext region after metadata:
- discriminator: 8
- `owner: Pubkey` -> +32
- `nonce: u128` -> +16
- `encryption_pubkey: [u8; 32]` -> +32
Offset:
```text
8 + 32 + 16 + 32 = 88
```
Marker payload size:
- `GENOME_MARKER_COUNT = 32`
- each marker ciphertext is `[u8; 32]`
Length:
```text
32 * 32 = 1024
```
Program constants:
```rust
const GENOME_CIPHERTEXT_OFFSET: u32 = 8 + 32 + 16 + 32;
const GENOME_CIPHERTEXT_BYTES: u32 = (GENOME_MARKER_COUNT * 32) as u32;
```
## Offset Debugging Workflow
1. Expand the full account struct in order.
2. Write each field size explicitly.
3. Sum discriminator + preceding fields.
4. Compare to `.account(..., offset, len)` in program.
5. Confirm callback output writes back exactly the same region semantics.
6. Validate in tests by roundtrip queue/callback and decrypt expected values.
If mismatch appears:
- inspect recent account field reorder/additions,
- inspect enum representation assumptions,
- inspect packed structs migrated across versions.
## Inline Comment Contract
Every `.account(...)` call for encrypted state must include comments like:
```rust
.account(
ctx.accounts.state.key(),
8 + 1 + 32, // 8 discr + 1 bump + 32 owner
32 * 4, // 4 ciphertext words
)
```
This is mandatory for maintainability and migration safety.
references/anti-patterns.md
# Anti-Patterns
## Table of Contents
- [Naming and Comp-Def Alignment](#naming-and-comp-def-alignment)
- [Callback Verification and Parsing](#callback-verification-and-parsing)
- [Offsets and Account Layout](#offsets-and-account-layout)
- [Arcis Secret-Flow Model Violations](#arcis-secret-flow-model-violations)
- [Nonce and Encryption Discipline](#nonce-and-encryption-discipline)
- [Deployment and Migration Safety](#deployment-and-migration-safety)
## Naming and Comp-Def Alignment
Forbidden:
1. Mismatched instruction naming across:
- Arcis `#[instruction] <name>`
- `comp_def_offset("<name>")`
- `#[arcium_callback(encrypted_ix = "<name>")]`
2. Copy/paste comp-def constants reused for a different instruction.
3. Queueing with a comp-def offset derived from old instruction names.
Consequence:
- callback verification failures, wrong comp-def PDA usage, or runtime aborts.
## Callback Verification and Parsing
Forbidden:
1. Using callback output without `verify_output(...)`.
2. Assuming output shape is always flat `field_0` when generator produced nested `OutputStruct0`.
3. Indexing ciphertext arrays without cardinality checks.
4. Persisting callback output before verification.
Consequence:
- security and correctness failures; runtime panics.
## Offsets and Account Layout
Forbidden:
1. Undocumented offset constants for `.account(...)` reads.
2. Reordering account fields without recomputing offsets.
3. Reusing offset constants from unrelated account types.
Consequence:
- ciphertext reads from wrong byte ranges and corrupted state updates.
## Arcis Secret-Flow Model Violations
Forbidden:
1. Secret-dependent control flow assumptions that Arcis model does not support.
2. Designing circuits around unsupported dynamic containers (`Vec`, `String`, `HashMap`) or unbounded loops.
3. Revealing intermediate secrets unnecessarily when only aggregate/public result is required.
Consequence:
- compilation failures, invalid privacy model, or excessive leakage.
## Nonce and Encryption Discipline
Forbidden:
1. Missing nonce discipline in client encrypt/decrypt paths.
2. Reusing nonce + key pair combinations across independent payloads.
3. Mixing endian conversions inconsistently (`deserializeLE` mismatch).
4. Using wrong key pairing (not MXE counterparty) for shared secret derivation.
Consequence:
- decryption failure or cryptographic weakening.
## Deployment and Migration Safety
Forbidden:
1. Running client/tests on different cluster offsets than deployed program assumptions.
2. Skipping LUT accounts in modern comp-def init contexts.
3. Ignoring migration guides during dependency bumps.
4. Treating offchain circuit URLs as mutable unversioned artifacts.
Consequence:
- environment-specific failures, missing account errors, non-reproducible deployments.
assets/templates/permissioned-flow/accounts-and-constraints.rs.tpl
#[derive(Accounts)]
#[instruction(<MATCHER_ARG>: Pubkey)]
pub struct SetPermission<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init_if_needed,
payer = payer,
space = 8 + <PERMISSION_ACCOUNT_TYPE>::INIT_SPACE,
seeds = [b"<PERMISSION_SEED>", payer.key().as_ref(), <MATCHER_ARG>.as_ref()],
bump,
)]
pub permission: Account<'info, <PERMISSION_ACCOUNT_TYPE>>,
pub system_program: Program<'info, System>,
}
#[queue_computation_accounts("<IX_NAME>", payer)]
#[derive(Accounts)]
#[instruction(computation_offset: u64, <TARGET_OWNER_ARG>: Pubkey)]
pub struct <QUEUE_CONTEXT_NAME><'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
constraint = <REQUESTER_STATE_ACCOUNT>.<OWNER_FIELD> == payer.key()
@ ErrorCode::<INVALID_OWNER_ERROR>,
)]
pub <REQUESTER_STATE_ACCOUNT>: Account<'info, <REQUESTER_STATE_ACCOUNT_TYPE>>,
#[account(
constraint = <TARGET_STATE_ACCOUNT>.<OWNER_FIELD> == <TARGET_OWNER_ARG>
@ ErrorCode::<INVALID_OWNER_ERROR>,
)]
pub <TARGET_STATE_ACCOUNT>: Account<'info, <TARGET_STATE_ACCOUNT_TYPE>>,
#[account(
seeds = [b"<PERMISSION_SEED>", <TARGET_OWNER_ARG>.as_ref(), payer.key().as_ref()],
bump = <PERMISSION_ACCOUNT>.bump,
constraint = <PERMISSION_ACCOUNT>.allowed @ ErrorCode::<PERMISSION_DENIED_ERROR>,
)]
pub <PERMISSION_ACCOUNT>: Account<'info, <PERMISSION_ACCOUNT_TYPE>>,
#[account(
init,
payer = payer,
space = 8 + <JOB_ACCOUNT_TYPE>::INIT_SPACE,
seeds = [
b"<JOB_SEED>",
payer.key().as_ref(),
computation_offset.to_le_bytes().as_ref(),
],
bump,
)]
pub <JOB_ACCOUNT>: Account<'info, <JOB_ACCOUNT_TYPE>>,
// Include standard Arcium queue accounts below this line.
#[account(
init_if_needed,
space = 9,
payer = payer,
seeds = [&SIGN_PDA_SEED],
bump,
address = derive_sign_pda!(),
)]
pub sign_pda_account: Account<'info, ArciumSignerAccount>,
#[account(address = derive_mxe_pda!())]
pub mxe_account: Account<'info, MXEAccount>,
#[account(mut, address = derive_mempool_pda!(mxe_account, ErrorCode::ClusterNotSet))]
/// CHECK: validated by arcium program
pub mempool_account: UncheckedAccount<'info>,
#[account(mut, address = derive_execpool_pda!(mxe_account, ErrorCode::ClusterNotSet))]
/// CHECK: validated by arcium program
pub executing_pool: UncheckedAccount<'info>,
#[account(mut, address = derive_comp_pda!(computation_offset, mxe_account, ErrorCode::ClusterNotSet))]
/// CHECK: validated by arcium program
pub computation_account: UncheckedAccount<'info>,
#[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_<IX_NAME_UPPER>))]
pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
#[account(mut, address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet))]
pub cluster_account: Account<'info, Cluster>,
#[account(mut, address = ARCIUM_FEE_POOL_ACCOUNT_ADDRESS)]
pub pool_account: Account<'info, FeePool>,
#[account(mut, address = ARCIUM_CLOCK_ACCOUNT_ADDRESS)]
pub clock_account: Account<'info, ClockAccount>,
pub system_program: Program<'info, System>,
pub arcium_program: Program<'info, Arcium>,
}
references/callback-output-shapes.md
# Callback Output Shapes
## Table of Contents
- [Core Verification Contract](#core-verification-contract)
- [Shape 1: Simple `field_0`](#shape-1-simple-field_0)
- [Shape 2: Nested `OutputStruct0`](#shape-2-nested-outputstruct0)
- [Shape 3: Multi-Encrypted Payload Contracts](#shape-3-multi-encrypted-payload-contracts)
- [Ciphertext Cardinality Checks](#ciphertext-cardinality-checks)
- [Common Parsing Failures](#common-parsing-failures)
## Core Verification Contract
Always verify before parsing:
```rust
let decoded = match output.verify_output(
&ctx.accounts.cluster_account,
&ctx.accounts.computation_account,
) {
Ok(v) => v,
Err(_) => return Err(ErrorCode::AbortedComputation.into()),
};
```
Never use `output` fields before verification.
## Shape 1: Simple `field_0`
Typical for single return value or single encrypted object.
Pattern:
```rust
let value = match output.verify_output(...) {
Ok(MyIxOutput { field_0 }) => field_0,
Err(_) => return Err(ErrorCode::AbortedComputation.into()),
};
```
Examples:
- `coinflip`: boolean-like reveal output
- `voting`: encrypted state object in `field_0`
- `share_medical_records`: encrypted payload transfer
- `encrypted_dna_matching`: encrypted result object in `field_0`
## Shape 2: Nested `OutputStruct0`
Generated when Arcis instruction returns tuple-like or multi-field structured values.
Pattern:
```rust
let (a, b, c) = match output.verify_output(...) {
Ok(MyIxOutput {
field_0: MyIxOutputStruct0 {
field_0: a,
field_1: b,
field_2: c,
},
}) => (a, b, c),
Err(_) => return Err(ErrorCode::AbortedComputation.into()),
};
```
Examples:
- `sealed_bid_auction` winner callbacks
- `blackjack` callbacks returning complex multi-part outputs
- `ed25519` signing callback with structured signature parts
## Shape 3: Multi-Encrypted Payload Contracts
For encrypted return objects, validate expected semantics:
- `nonce` is present and persisted.
- `ciphertexts` count is expected.
- optional `encryption_key` consistency checks if output target key matters.
Pattern:
```rust
let o = ...; // verified output
if o.ciphertexts.len() < 2 {
return Err(ErrorCode::InvalidCiphertextCount.into());
}
state.output_nonce = o.nonce;
state.value_a = o.ciphertexts[0];
state.value_b = o.ciphertexts[1];
```
Example:
- `encrypted_dna_matching` checks at least 2 ciphertexts before writing result fields.
## Ciphertext Cardinality Checks
Use explicit guards whenever callback logic indexes ciphertext arrays.
Rules:
1. If exact shape is required, enforce `== N`.
2. If minimum shape is acceptable, enforce `>= N`.
3. Prefer explicit custom errors (`InvalidCiphertextCount`) over panics.
Recommended helper pattern:
```rust
fn expect_ciphertexts_len_at_least<T>(slice: &[T], min: usize) -> Result<()> {
require!(slice.len() >= min, ErrorCode::InvalidCiphertextCount);
Ok(())
}
```
## Common Parsing Failures
1. Wrong generated output type
- Symptom: compile mismatch on callback output generic.
- Fix: ensure instruction name did not change; rebuild generated code.
2. Wrong nested structure assumption
- Symptom: pattern match compile errors on `field_0/field_1`.
- Fix: inspect generated callback types and update parser shape.
3. Indexing ciphertexts without guard
- Symptom: runtime panic or custom callback error path.
- Fix: add cardinality checks before indexing.
4. Consuming unverified output
- Symptom: logic accepts forged/unexpected data.
- Fix: enforce `verify_output` first in all callbacks.
references/examples-patterns.md
# Arcium Example Patterns
## Table of Contents
- [Feature Matrix](#feature-matrix)
- [Reusable Patterns by Example](#reusable-patterns-by-example)
- [Anti-Patterns by Example Class](#anti-patterns-by-example-class)
## Feature Matrix
Legend:
- `State model`: stateless, stateful, permissioned
- `Randomness`: `ArcisRNG` or deterministic
- `Re-encryption`: whether ownership transfer (`Shared -> Shared`) is present
- `Packed data`: `Pack<T>` or manual packing (base-64 style, etc.)
- `Offchain source`: whether `CircuitSource::OffChain` is used in comp-def init
- `Output shape`: simple (`field_0`) vs nested (`OutputStruct0` tuple-like)
| Example | Local path | State model | Randomness usage | Re-encryption usage | Packed data usage | Permission constraints | Offchain circuit source | Multi-comp-def count | Callback output shape complexity |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Coinflip | `examples/coinflip` | Stateless | Yes (`ArcisRNG::bool`) | No | No | Minimal | No | 1 | Simple (`FlipOutput { field_0 }`) |
| Voting | `examples/voting` | Stateful | No | No | No | Authority-gated reveal (`has_one` + require) | No | 3 (`init_vote_stats`, `vote`, `reveal_result`) | Simple |
| Share Medical Records | `examples/share_medical_records` | Stateless request + state storage | No | Yes (`receiver.from_arcis(input)`) | No | Minimal | No | 1 | Simple |
| Sealed Bid Auction | `examples/sealed_bid_auction` | Stateful | No | No | No | Strong (`has_one`, status gates) | No | 4 | Mixed simple + nested (`DetermineWinner*OutputStruct0`) |
| RPS Against Player | `examples/rock_paper_scissors/against-player` | Stateful game session | No | No | No | Player/state progression checks | No | 3 (`init_game`, `player_move`, `compare_moves`) | Simple |
| RPS Against House | `examples/rock_paper_scissors/against-house` | Stateless/game-light | Yes (`ArcisRNG::bool`) | No | No | Minimal | No | 1 | Simple |
| Blackjack | `examples/blackjack` | Stateful multi-step | Yes (`ArcisRNG::shuffle`) | Yes (player/dealer views) | Manual packing (`Deck`, `Hand` packed into `u128`) | Strong game-state constraints | No | 6 | Mixed simple + nested across callbacks |
| Ed25519 | `examples/ed25519` | Stateless per call | No | No | Yes (`Pack<VerifyingKey>`) | Minimal | No | 2 (`sign_message`, `verify_signature`) | Mixed simple + nested |
| Encrypted DNA Matching | `examples/encrypted_dna_matching` | Permissioned + stateful jobs | No | No (outputs encrypted to receiver) | No | Strong (`constraint = ... @ ErrorCode`) | Yes (`CircuitSource::OffChain`) | 1 | Simple with explicit ciphertext cardinality checks |
## Reusable Patterns by Example
### Coinflip
What to reuse:
- Minimal single-computation skeleton (`comp_def_offset`, init-comp-def, queue, callback).
- MPC randomness pattern for fair outcomes.
- Simple callback parsing and event emission.
### Voting
What to reuse:
- Encrypted state accumulation in a single account with `.account(...)` offsets.
- Authority-only reveal path.
- Multi-comp-def organization for lifecycle phases.
### Share Medical Records
What to reuse:
- Clean re-encryption ownership transfer (`Shared -> Shared`) via receiver key.
- Compact encrypted struct handoff without revealing plaintext.
### Sealed Bid Auction
What to reuse:
- Status-gated state machine (`Open -> Closed -> Resolved`).
- Nested callback output parsing for tuple-like winner results.
- Explicit offset constants with comments for encrypted state region.
### RPS Against Player
What to reuse:
- Multi-stage game flow with sequential computations.
- Final reveal-only result model while preserving hidden moves.
### RPS Against House
What to reuse:
- Lightweight randomness-driven adversary model.
- Single-output callback flow for quick prototyping.
### Blackjack
What to reuse:
- Multi-computation orchestration at scale.
- Manual packing strategy for large encrypted state (`Deck`, `Hand`) to reduce footprint.
- Complex callback parsing with multiple encrypted outputs and state transitions.
### Ed25519
What to reuse:
- Cryptographic primitive integration (`Pack<...>` for key material).
- Split sign/verify computations with separate comp-defs.
### Encrypted DNA Matching
What to reuse:
- Permission PDA model with explicit constraints.
- Offchain circuit source + `circuit_hash!` init flow.
- Idempotent comp-def initialization helper in tests.
- Ciphertext count validation before storing callback outputs.
## Anti-Patterns by Example Class
### Stateless computations
Avoid:
- Overdesigning with unnecessary state accounts.
- Returning raw secret intermediate values when a boolean/result code is enough.
### Stateful encrypted computations
Avoid:
- Hard-coded offsets without layout comments or formulas.
- Reordering account fields without recomputing `.account(...)` offsets.
- Writing callback outputs before `verify_output`.
### Permissioned computations
Avoid:
- Deferring authorization checks until after queueing.
- Using `UncheckedAccount` where strict Anchor constraints can be expressed.
### Packed data computations
Avoid:
- Mixing encoding schemes across circuit/program/client.
- Silent precision/bit-width changes in packed structs.
### Offchain circuit computations
Avoid:
- Hash/source mismatch (`circuit_hash!(...)` not matching actual instruction artifact).
- Unversioned mutable URLs for production comp-def initialization.
## Source Reference
- Local source of truth: `examples/*` in this repository.
- Upstream examples mirror: [arcium-hq/examples](https://github.com/arcium-hq/examples)
references/implementation-playbook.md
# Arcium Implementation Playbook
## Table of Contents
- [Execution Model](#execution-model)
- [Task Classifier](#task-classifier)
- [End-to-End New Computation Flow](#end-to-end-new-computation-flow)
- [Multi-Instruction Program Flow](#multi-instruction-program-flow)
- [Idempotent Computation-Definition Initialization](#idempotent-computation-definition-initialization)
- [ArgBuilder Contracts](#argbuilder-contracts)
- [Callback Contracts](#callback-contracts)
- [Offset and Layout Contracts](#offset-and-layout-contracts)
- [Offchain Circuit Flow](#offchain-circuit-flow)
- [Validation Contract](#validation-contract)
## Execution Model
Arcium program work is split across three layers:
1. `encrypted-ixs/src/lib.rs`
- Defines `#[instruction]` circuits and ownership model (`Shared` vs `Mxe`).
2. `programs/<program>/src/lib.rs`
- Defines `comp_def_offset(...)`, `init_comp_def`, queue instruction contexts, callback contexts, and callback handlers.
3. Client/tests (`tests/*.ts`, optional frontend)
- Encrypts input payloads.
- Derives Arcium PDAs with the same cluster offset as deployment.
- Waits for finalization.
- Verifies/decrypts outputs.
## Task Classifier
Choose a lane before coding:
1. Stateless
- One queue instruction and one callback.
- Typical for `coinflip` and `rock_paper_scissors/against-house`.
2. Stateful encrypted account updates
- Queue reads account ciphertexts with `.account(...)`.
- Callback stores new ciphertexts + nonce.
- Typical for `voting`, `sealed_bid_auction`, `blackjack`.
3. Permissioned flow
- Queue instruction includes ownership/permission constraints.
- Typical for `encrypted_dna_matching`.
4. Offchain circuit source
- `init_comp_def` uses `CircuitSource::OffChain` with `circuit_hash!`.
- Typical for `encrypted_dna_matching`.
5. Migration/debugging
- Resolve API/signature mismatches, LUT wiring, cluster offset mismatches.
## End-to-End New Computation Flow
### 1) Add Arcis instruction
In `encrypted-ixs/src/lib.rs`:
```rust
#[instruction]
pub fn <IX_NAME>(<inputs>) -> <outputs> {
// use to_arcis() for encrypted input decoding
// use owner.from_arcis(...) or reveal() for outputs
}
```
Rules:
- Keep the instruction name stable; this drives generated callback types and `comp_def_offset`.
- Do not reorder encrypted fields without updating all client/program encryption order.
### 2) Add offset constants in program
```rust
const COMP_DEF_OFFSET_<IX_NAME_UPPER>: u32 = comp_def_offset("<IX_NAME>");
```
Rules:
- String must exactly match Arcis `#[instruction]` name.
- One constant per encrypted instruction.
### 3) Add `init_*_comp_def`
```rust
pub fn init_<IX_NAME>_comp_def(ctx: Context<Init<Ix>CompDef>) -> Result<()> {
init_comp_def(ctx.accounts, None, None)?;
Ok(())
}
```
Rules:
- Required even for single-instruction programs.
- Call once per environment (idempotent helper in tests/client).
### 4) Add queue instruction
Queue instruction must:
- enforce business constraints before queueing,
- build arguments in exact ownership order,
- set signer PDA bump before `queue_computation`,
- pass callback instruction with explicit writable callback accounts.
```rust
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
let args = ArgBuilder::new()
// ordered inputs
.build();
queue_computation(
ctx.accounts,
computation_offset,
args,
vec![<Ix>Callback::callback_ix(
computation_offset,
&ctx.accounts.mxe_account,
&[
CallbackAccount {
pubkey: ctx.accounts.<STATE_OR_JOB>.key(),
is_writable: true,
}
],
)?],
1,
0,
)?;
```
### 5) Add callback instruction
```rust
#[arcium_callback(encrypted_ix = "<IX_NAME>")]
pub fn <ix_name>_callback(
ctx: Context<<Ix>Callback>,
output: SignedComputationOutputs<<IxName>Output>,
) -> Result<()> {
let decoded = match output.verify_output(
&ctx.accounts.cluster_account,
&ctx.accounts.computation_account,
) {
Ok(v) => v,
Err(_) => return Err(ErrorCode::AbortedComputation.into()),
};
// parse, validate cardinality, persist, emit
Ok(())
}
```
Rules:
- Never read output before `verify_output`.
- For encrypted structs, persist both `ciphertexts` and `nonce`.
### 6) Add account contexts
Add these patterns:
- `#[queue_computation_accounts("<ix_name>", <payer_or_authority>)]`
- `#[callback_accounts("<ix_name>")]`
- `#[init_computation_definition_accounts("<ix_name>", payer)]`
Modern `init_computation_definition_accounts` includes LUT accounts:
- `address_lookup_table` with `derive_mxe_lut_pda!(mxe_account.lut_offset_slot)`
- `lut_program` with `LUT_PROGRAM_ID`
### 7) Add tests/client flow
Required steps:
1. Resolve Arcium environment and cluster offset.
2. Initialize comp-def idempotently.
3. Fetch MXE public key (with retry if needed).
4. Encrypt inputs with `x25519` + `RescueCipher`.
5. Queue instruction.
6. `awaitComputationFinalization(...)`.
7. Validate callback event/state and decrypt outputs if applicable.
## Multi-Instruction Program Flow
For programs with many encrypted instructions (`blackjack`, `sealed_bid_auction`, `voting`, `ed25519`):
1. Keep a one-to-one map:
- encrypted ix name,
- `COMP_DEF_OFFSET_*`,
- queue instruction accounts,
- callback accounts,
- callback parser,
- init-comp-def instruction.
2. Treat each instruction as independently deployable.
- A new instruction must not silently rely on another instruction's comp-def.
3. Use explicit state transitions before queueing.
- Example classes:
- `AuctionStatus` (`Open -> Closed -> Resolved`)
- `GameState` in blackjack
- authority-only reveal in voting
4. Reuse compact callback accounts.
- Pass only required writable accounts to `CallbackAccount`.
## Idempotent Computation-Definition Initialization
Use this in tests/frontend helpers:
1. Derive comp-def PDA from:
- program id,
- `getCompDefAccOffset("<ix_name>")`.
2. Fetch account info.
- If account exists: return `already_initialized`.
- Else call `init_*_comp_def`.
This pattern appears in `encrypted_dna_matching/tests/encrypted_dna_matching.ts` and avoids re-init failures in repeated runs.
## ArgBuilder Contracts
### Shared encrypted input (`Enc<Shared, T>`)
Order:
1. `.x25519_pubkey(<client_pubkey>)`
2. `.plaintext_u128(<nonce>)`
3. encrypted fields in exact circuit argument order
### MXE encrypted input (`Enc<Mxe, T>`)
Order:
1. `.plaintext_u128(<mxe_nonce>)`
2. encrypted fields in exact circuit argument order
### Account-backed encrypted state
Use:
- `.account(<account_pubkey>, <offset>, <len>)`
Contract:
- `<offset>` includes discriminator and all preceding fields.
- `<len>` matches exact ciphertext byte span.
- Keep inline offset comments in code.
## Callback Contracts
### Simple output
```rust
Ok(<IxOutput> { field_0 }) => field_0
```
### Nested output struct
```rust
Ok(<IxOutput> {
field_0: <IxOutputStruct0> {
field_0: a,
field_1: b,
field_2: c,
},
}) => (a, b, c)
```
Hard requirements:
- `verify_output` first.
- Validate cardinality for expected ciphertext counts (`len() == N` or `>= N`).
- Persist validated data only.
## Offset and Layout Contracts
Use deterministic formula:
```text
offset = 8 (Anchor discriminator) + size(preceding fields)
```
Common account usage:
- `voting`: `8 + 1`, `len = 32 * 2`
- `sealed_bid_auction`: `8 + 1 + 32 + 1 + 1 + 8 + 8 + 1 + 16`, `len = 32 * 5`
- `encrypted_dna_matching`: `8 + 32 + 16 + 32`, `len = 32 * GENOME_MARKER_COUNT`
- `blackjack`: multiple fixed slices for deck/hand segments
## Offchain Circuit Flow
Use offchain circuits when circuit artifacts are large or deployment constraints favor remote source.
Program init pattern:
```rust
init_comp_def(
ctx.accounts,
Some(CircuitSource::OffChain(OffChainCircuitSource {
source: <CIRCUIT_URL>.to_string(),
hash: circuit_hash!("<IX_NAME>"),
})),
None,
)?;
```
Rules:
- Hash macro string must match the encrypted instruction name.
- Circuit URL must be stable and versioned.
- Keep fallback and retry logic in tests/client for network-dependent setup.
## Validation Contract
Minimum validation sequence:
```bash
arcium build
cargo check --all
arcium test
python3 /Users/grisahudozestvennyj/.codex/skills/.system/skill-creator/scripts/quick_validate.py /Users/grisahudozestvennyj/Documents/projects/arcium/dna/skills/arcium-program-development
rg -n "[\p{Cyrillic}]" /Users/grisahudozestvennyj/Documents/projects/arcium/dna/skills/arcium-program-development || true
```
Acceptance checks:
- Name mapping is exact (`#[instruction]` == `comp_def_offset` == callback macro `encrypted_ix`).
- ArgBuilder order matches ownership model.
- Offset constants match account layout.
- Callback output is verified and shape-checked before use.
- Finalization path is deterministic in tests (`awaitComputationFinalization`).
references/permission-and-state-machines.md
# Permission and State-Machine Patterns
## Table of Contents
- [Authorization Layers](#authorization-layers)
- [Anchor Constraint Patterns](#anchor-constraint-patterns)
- [State-Machine Gating Patterns](#state-machine-gating-patterns)
- [Permission PDA Pattern](#permission-pda-pattern)
- [Callback-Side State Updates](#callback-side-state-updates)
- [Audit Checklist](#audit-checklist)
## Authorization Layers
Use both layers:
1. Account constraints
- Enforce static ownership/seed/relationship properties in account validation.
2. Runtime `require!` checks
- Enforce dynamic conditions (status transitions, thresholds, caller intent).
Do not rely on only one layer for permission-sensitive flows.
## Anchor Constraint Patterns
### `has_one` ownership binding
```rust
#[account(mut, has_one = authority @ ErrorCode::Unauthorized)]
pub auction: Account<'info, Auction>,
```
Use when account embeds an authority pubkey field.
### Value constraints with explicit error
```rust
#[account(
constraint = requester_genome.owner == payer.key() @ ErrorCode::InvalidGenomeOwner,
)]
pub requester_genome: Account<'info, GenomeVault>,
```
Use when the relationship is not modeled by `has_one` or needs multiple comparisons.
### Permission flag checks
```rust
#[account(
constraint = match_permission.allowed @ ErrorCode::PermissionDenied,
)]
pub match_permission: Account<'info, MatchPermission>,
```
Use for explicit allow/deny records.
## State-Machine Gating Patterns
Model statuses as enums and gate every transition.
### Example transition model
- `Open -> Closed -> Resolved` (sealed bid auction)
- `Initial -> PlayerTurn -> DealerTurn -> Resolved` (blackjack-style flow)
Queue instruction checks should enforce prerequisites:
```rust
require!(auction.status == AuctionStatus::Closed, ErrorCode::AuctionNotClosed);
require!(auction.auction_type == AuctionType::Vickrey, ErrorCode::WrongAuctionType);
```
Guidelines:
1. Validate status before building `ArgBuilder`.
2. Validate caller permissions before queueing.
3. Update status in callback only after successful output verification.
## Permission PDA Pattern
Permission record account (`owner`, `matcher`, `allowed`) pattern:
1. Create/update permission PDA in dedicated instruction.
2. In queue instruction, load permission account with deterministic seeds.
3. Enforce `allowed == true` via constraint.
Use this for cross-user authorization (like requester/target-owner DNA matching).
## Callback-Side State Updates
Only callback should mark computation-complete state when callback output is verified.
Recommended callback sequence:
1. verify output,
2. validate output shape/cardinality,
3. update status + result fields,
4. emit event.
Never set `Completed` (or equivalent) before verify+parse.
## Audit Checklist
For every permissioned or stateful instruction:
1. Does account validation constrain owners and permission records?
2. Does runtime logic enforce current status and legal transition?
3. Is queueing blocked on invalid state/permissions?
4. Does callback verify output before mutating status/result fields?
5. Are callback writable accounts minimal and explicit?
references/docs-and-migrations.md
# Arcium Docs and Migration Notes
## Table of Contents
- [Primary Documentation](#primary-documentation)
- [Version Migration Matrix (v0.1.x -> v0.8.x)](#version-migration-matrix-v01x---v08x)
- [When to Pick Offchain Circuit Source](#when-to-pick-offchain-circuit-source)
- [Deployment Reliability Checklist](#deployment-reliability-checklist)
## Primary Documentation
Core docs:
- Intro: [https://docs.arcium.com/developers](https://docs.arcium.com/developers)
- Hello world: [https://docs.arcium.com/developers/hello-world](https://docs.arcium.com/developers/hello-world)
- Computation lifecycle: [https://docs.arcium.com/developers/computation-lifecycle](https://docs.arcium.com/developers/computation-lifecycle)
- Deployment: [https://docs.arcium.com/developers/deployment](https://docs.arcium.com/developers/deployment)
- Limitations: [https://docs.arcium.com/developers/limitations](https://docs.arcium.com/developers/limitations)
- Program overview: [https://docs.arcium.com/developers/program](https://docs.arcium.com/developers/program)
- Comp-def accounts: [https://docs.arcium.com/developers/program/computation-def-accs](https://docs.arcium.com/developers/program/computation-def-accs)
- Callback accounts: [https://docs.arcium.com/developers/program/callback-accs](https://docs.arcium.com/developers/program/callback-accs)
- Callback type generation: [https://docs.arcium.com/developers/program/callback-type-generation](https://docs.arcium.com/developers/program/callback-type-generation)
- JS client overview: [https://docs.arcium.com/developers/js-client-library](https://docs.arcium.com/developers/js-client-library)
- JS input encryption: [https://docs.arcium.com/developers/js-client-library/encryption](https://docs.arcium.com/developers/js-client-library/encryption)
- JS callback/finalization tracking: [https://docs.arcium.com/developers/js-client-library/callback](https://docs.arcium.com/developers/js-client-library/callback)
- Arcis overview: [https://docs.arcium.com/developers/arcis](https://docs.arcium.com/developers/arcis)
- Arcis best practices: [https://docs.arcium.com/developers/arcis/best-practices](https://docs.arcium.com/developers/arcis/best-practices)
- Arcis quick reference: [https://docs.arcium.com/developers/arcis/quick-reference](https://docs.arcium.com/developers/arcis/quick-reference)
- Migration index: [https://docs.arcium.com/developers/migration](https://docs.arcium.com/developers/migration)
- LLM index: [https://docs.arcium.com/llms.txt](https://docs.arcium.com/llms.txt)
## Version Migration Matrix (v0.1.x -> v0.8.x)
Use each official migration page as the source of truth. The matrix below highlights practical breaking surfaces to check in code reviews.
| From -> To | Official guide | Breaking surface to inspect first |
| --- | --- | --- |
| v0.1.x -> v0.2.0 | [guide](https://docs.arcium.com/developers/migration/migration-v0.1-to-v0.2) | Arcis syntax and ownership typing changes; generated type naming assumptions; client serialization assumptions |
| v0.2.x -> v0.3.0 | [guide](https://docs.arcium.com/developers/migration/migration-v0.2-to-v0.3) | Program macro updates, account context macro expectations, callback glue updates |
| v0.3.x -> v0.4.0 | [guide](https://docs.arcium.com/developers/migration/migration-v0.3-to-v0.4) | Encryption API updates, nonce/key handling in TS clients, output parsing expectations |
| v0.4.x -> v0.5.1 | [guide](https://docs.arcium.com/developers/migration/migration-v0.4-to-v0.5) | Queue/callback account interface tightening, account derivation helper changes |
| v0.5.x -> v0.6.3 | [guide](https://docs.arcium.com/developers/migration/migration-v0.5-to-v0.6) | `@arcium-hq/client` helper changes, dependency alignment between Rust and TS |
| v0.6.3 -> v0.7.0 | [guide](https://docs.arcium.com/developers/migration/migration-v0.6.3-to-v0.7.0) | Add LUT accounts to comp-def init contexts (`address_lookup_table`, `lut_program`), remove deprecated queue `callback_url`, refresh crate/npm versions |
| v0.7.0 -> v0.8.0 | [guide](https://docs.arcium.com/developers/migration/migration-v0.7.0-to-v0.8.0) | Dependency/tooling updates (`arcup`, crates, JS client), verify test flow compatibility; validate offchain-circuit workflows with `arcium test --skip-local-circuit` when needed |
### Breaking Surface Checklist Per Upgrade Step
For every migration step:
1. Rebuild encrypted instructions (`arcium build`) and regenerate callback output types.
2. Verify all `comp_def_offset("...")` strings still match instruction names.
3. Revalidate `init_computation_definition_accounts` account sets (especially LUT accounts on modern versions).
4. Revalidate `queue_computation(...)` signature and argument order.
5. Revalidate client helper usage (`getCompDefAccOffset`, PDA helpers, finalization helpers).
6. Rerun full e2e test suite with fresh comp-def initialization.
## When to Pick Offchain Circuit Source
Prefer `CircuitSource::OffChain` when one or more conditions are true:
1. Circuit artifacts are large or change often during active development.
2. You need independent artifact hosting/versioning workflows.
3. You want reproducible pinning by URL + hash.
4. Local test setup should skip local circuit execution (`--skip-local-circuit`).
Prefer default/local source when:
1. The circuit is small and stable.
2. You want fewer external runtime dependencies.
3. You prioritize local deterministic tests without network access.
Offchain non-negotiables:
- Hash must use `circuit_hash!("<instruction_name>")` with exact instruction name.
- URL must be stable and versioned.
- Rollout should include a fallback/retry policy in test and deployment scripts.
## Deployment Reliability Checklist
### Build and artifact checks
1. Run `arcium build` and `cargo check --all`.
2. Run e2e tests for queue -> callback -> finalization.
3. Confirm callback output parsers still match generated output structs.
### Cluster and PDA consistency checks
1. Pick one cluster offset for the deployment target.
2. Ensure the same offset is used by:
- deployment scripts,
- test/client `getArciumEnv()` values,
- all PDA helper calls (`getComputationAccAddress`, `getCompDefAccAddress`, `getClusterAccAddress`, `getLookupTableAddress`).
3. Confirm on-chain derived addresses match client-derived addresses before queueing.
### Comp-def initialization reliability
1. Initialize every encrypted instruction comp-def once (idempotent helpers recommended).
2. For v0.7+ style contexts, include LUT accounts in init calls.
3. Fail deployment if any required comp-def account is missing.
### Runtime reliability checks
1. Validate MXE key fetch path with retry.
2. Validate queue transaction confirmation and capture signatures.
3. Always wait for finalization (`awaitComputationFinalization`) before asserting callback state.
4. Emit actionable logs: computation offset, comp-def offset, queue tx, finalization tx.
references/test-client-patterns.md
# Test and Client Patterns
## Table of Contents
- [Canonical End-to-End Test Flow](#canonical-end-to-end-test-flow)
- [MXE Public Key Retry Pattern](#mxe-public-key-retry-pattern)
- [Comp-Def Idempotent Initialization](#comp-def-idempotent-initialization)
- [LUT Lookup Pattern](#lut-lookup-pattern)
- [Encryption/Decryption Roundtrip Contract](#encryptiondecryption-roundtrip-contract)
- [Callback Event Awaiting Pattern](#callback-event-awaiting-pattern)
- [Finalization Wait Contract](#finalization-wait-contract)
- [Minimal Regression Assertions](#minimal-regression-assertions)
## Canonical End-to-End Test Flow
1. Setup provider and program.
2. Resolve Arcium environment (`getArciumEnv`) and cluster account.
3. Initialize all required comp-defs idempotently.
4. Fetch MXE public key with retry.
5. Encrypt inputs with `x25519` + `RescueCipher`.
6. Queue program instruction with PDA addresses derived from same cluster offset.
7. Await callback event and finalization.
8. Decrypt outputs (if receiver-targeted encrypted outputs).
9. Assert functional result and state transition.
## MXE Public Key Retry Pattern
Use retry logic because MXE account availability can be eventually consistent in test environments.
Pattern:
1. call `getMXEPublicKey(...)`,
2. retry on transient failures with bounded attempts/backoff,
3. fail fast with actionable error after max retries.
Required logs:
- attempt count,
- last error,
- program id / cluster context.
## Comp-Def Idempotent Initialization
Pattern used in `encrypted_dna_matching` tests:
1. derive comp-def PDA from `getCompDefAccOffset("<ix_name>")`.
2. call `getAccountInfo`.
3. if exists: return sentinel (`already_initialized`).
4. else call `init_<ix>_comp_def`.
Benefit:
- test reruns do not fail on already-created comp-defs.
## LUT Lookup Pattern
For modern comp-def init contexts:
1. fetch MXE account (`arciumProgram.account.mxeAccount.fetch`).
2. derive LUT address with `getLookupTableAddress(programId, mxeAcc.lutOffsetSlot)`.
3. pass `addressLookupTable` in comp-def init accounts.
Do not hardcode LUT addresses.
## Encryption/Decryption Roundtrip Contract
Input encryption contract:
1. generate sender x25519 secret/public pair,
2. derive shared secret with MXE public key,
3. encrypt plaintext vector with `RescueCipher` and random 16-byte nonce,
4. serialize nonce into BN using `deserializeLE`.
Output decryption contract:
1. derive shared secret with receiver secret key and MXE public key,
2. decrypt callback ciphertexts using output nonce,
3. assert expected semantics (not only byte equality).
Nonce discipline:
- never reuse nonce with same key pair for distinct plaintext payloads.
## Callback Event Awaiting Pattern
Pattern:
1. register listener before queue tx,
2. hold `Promise` for target event,
3. queue tx,
4. await event,
5. remove listener.
Reason:
- avoids missing early callback events and nondeterministic test behavior.
## Finalization Wait Contract
Always wait for chain-level finalization after queueing:
```ts
await awaitComputationFinalization(provider, computationOffset, program.programId, "confirmed")
```
Rules:
1. Use exact same `computationOffset` passed into queue instruction.
2. Use the same program id and commitment level across queue/wait.
3. Log queue and finalize signatures for diagnostics.
## Minimal Regression Assertions
At minimum, assert:
1. callback handler updated expected state fields.
2. callback status transition happened (if state machine exists).
3. decrypted result matches expected deterministic computation.
4. permission rules were enforced (negative test path recommended).
references/troubleshooting-matrix.md
# Troubleshooting Matrix
## Table of Contents
- [How to Triage](#how-to-triage)
- [Symptom Matrix](#symptom-matrix)
- [Fast Inspection Commands](#fast-inspection-commands)
## How to Triage
Map failures to one of four stages first:
1. Encryption
2. Queueing and account/PDA wiring
3. Callback verification/parsing
4. Finalization waiting
Then apply the matrix below.
## Symptom Matrix
| Symptom | Probable root cause | Exact inspection point | Fix |
| --- | --- | --- | --- |
| Decrypted output is nonsense or mismatched | Wrong shared secret pairing or nonce serialization bug | Client encryption/decryption helper (`x25519` pair usage, `deserializeLE` conversion, nonce source) | Ensure encryption uses sender secret + MXE pubkey; decryption uses receiver secret + MXE pubkey; use unique 16-byte nonce and consistent LE conversion |
| Queue tx fails with account constraint error | Wrong PDA derivation or cluster offset mismatch | Queue `.accountsPartial(...)` and environment offset (`getArciumEnv().arciumClusterOffset`) | Use single cluster offset across deploy/tests; re-derive all Arcium PDAs with same offset/program id |
| Queue tx fails due to missing comp-def | Comp-def not initialized or wrong comp-def offset string | `comp_def_offset("...")` constant, `init_*_comp_def`, client `getCompDefAccOffset("...")` | Ensure instruction name string is exact and comp-def init ran successfully (idempotent helper recommended) |
| Queue tx confirmed but callback returns `AbortedComputation` | Callback verifying wrong computation/cluster pair or instruction mismatch | Callback macro `#[arcium_callback(encrypted_ix = "...")]`, callback output generic type, `verify_output` call context accounts | Align encrypted instruction name, comp-def account, callback output type; verify accounts are from same computation |
| Callback parse panic / index error | Wrong output shape assumption or missing ciphertext count checks | Callback parser (`field_0` vs `OutputStruct0`), ciphertext indexing | Update parser to generated shape and add `len()` guard before indexing |
| Callback account mutation fails | Missing writable callback account in `callback_ix` account list | Queue instruction callback account vector (`CallbackAccount { is_writable: true }`) | Add required writable callback account(s) explicitly and keep minimal set |
| Permissioned flow rejects valid caller unexpectedly | Wrong constraint seed/value relation | Account constraint expressions (`has_one`, `constraint = ... @ ErrorCode`) | Recompute seeds/owner fields; add explicit logs for owner/matcher keys |
| Finalization wait times out | Wrong computation offset passed to waiter or callback never emitted | `awaitComputationFinalization(...)` args and queue offset generation | Pass the same `computation_offset` object to queue and wait; verify queue tx succeeded and callback is wired |
| Comp-def init fails on modern versions | LUT accounts omitted in init context | `#[init_computation_definition_accounts(...)]` account struct | Include `address_lookup_table` + `lut_program` (`LUT_PROGRAM_ID`) |
| Offchain comp-def init fails | URL/hash mismatch or unreachable artifact | `CircuitSource::OffChain` config and `circuit_hash!("...")` literal | Ensure URL serves the correct artifact and hash string matches instruction name exactly |
## Fast Inspection Commands
```bash
# Find all comp-def offsets and callback macro names
rg -n "comp_def_offset\(|arcium_callback\(encrypted_ix" examples -S
# Find account offset constants/comments
rg -n "\.account\(|OFFSET|offset" examples -S
# Find output verify and parse patterns
rg -n "verify_output\(|OutputStruct0|field_0" examples -S
# Find finalization waits in tests/frontend
rg -n "awaitComputationFinalization" examples -S
```