Documentation Index
Fetch the complete documentation index at: https://docs.arcium.com/llms.txt
Use this file to discover all available pages before exploring further.
What this solves
When you write confidential instructions in Arcium, the results come back as structured data. Previously, developers had to manually parse raw bytes - tracking offsets, sizes, and converting back to the right types. This was error-prone and tedious. Arcium’s type generation system analyzes your circuit’s return type and automatically creates typed Rust structs. This means you can work directly with structured data instead of byte arrays.Mental model: from functions to structs
Here’s the transformation that happens automatically:What you’ll learn
After reading this guide, you’ll know how to:- Work with automatically generated Rust structs for confidential computation outputs
- Predict what struct names and fields Arcium will create
- Handle different confidentiality types (Shared vs MXE) in callbacks
- Debug type generation issues when they arise
30-second quick start
-
Write your circuit:
-
Generate types:
arcium build -
Use in callback:
Your first generated type: simple addition
Here’s a concrete example. Consider this confidential instruction that adds two numbers:Enc<Shared, u16>, it automatically generates this output struct:
- Name:
add_togetherbecomesAddTogetherOutput - Field: Always
field_0for single return values - Type:
SharedEncryptedStruct<1>because it’s shared with 1 value (the u16)
How type generation works
Previously, decoding computation results required manual byte parsing. Now Arcium auto-generates typed structs, letting you focus on application logic.How output types are generated
Type generation is a two-step process:arcium buildcompiles your Arcis circuits and writes.idarcinterface files describing each circuit’s return type#[callback_accounts]macro reads the.idarcfile at compile time and generates the corresponding Rust struct in your program crate
arcium build, the #[callback_accounts("add_together")] macro reads build/add_together.idarc and generates:
AddTogetherOutput in your callback even though you never explicitly defined it.
Build dependency
You must runarcium build before compiling your program so the .idarc files exist for the macro to read. Run arcium build again whenever you change a confidential instruction’s return type.
Behind the scenes: the two-step pipeline
Understanding how types flow from your circuit to your Solana program helps explain whyarcium build must run first.
Step 1: arcium build produces .idarc files
When you run arcium build, the compiler processes each #[instruction] function inside your #[encrypted] module:
- Parse the return type: The compiler examines your function signature and extracts the return type
- Analyze the structure: It breaks down complex types (tuples, structs, confidentiality wrappers) into components
- Write the interface file: It serializes the type information to
build/{circuit_name}.idarc
Step 2: #[callback_accounts] generates Rust structs
When you compile your Solana program, the #[callback_accounts("circuit_name")] macro:
- Reads the
.idarcfile: Loads the circuit interface frombuild/{circuit_name}.idarc - Generates struct definitions: Creates typed Rust structs matching the circuit’s return type
- Injects into scope: The generated types become available in your program module
What gets generated
For different return types, the pipeline generates different struct patterns:Why this approach works
This two-step approach provides several benefits:- Type safety: You get compile-time type checking for confidential results
- No manual definition: You don’t need to define output structs yourself
- Consistency: All generated types follow the same predictable patterns
- Automatic updates: If you change your function’s return type, re-run
arcium buildand the structs update automatically
arcium build produces the interface files, and #[callback_accounts] turns them into Rust types during compilation.
Understanding LEN parameters
In ouradd_together example, you saw SharedEncryptedStruct<1>. The <LEN> number tells you how many confidential scalar values are stored inside.
The <LEN> number represents the count of individual confidential scalar values:
| Return Type | LEN Value | Why |
|---|---|---|
Enc<Shared, u32> | 1 | Single scalar |
Enc<Shared, (u32, bool)> | 2 | Two scalars |
Enc<Shared, [u32; 5]> | 5 | Five array elements |
Enc<Shared, MyStruct> | field count | Count all scalar fields in struct |
Type availability and scope
Where generated types live
Generated types are scoped to the module where#[callback_accounts] is used. After running arcium build, the types become available during program compilation:
No import required
Unlike external types, you don’t need to import generated types.#[callback_accounts] injects them directly into your module’s namespace:
Generated struct properties
All generated structs automatically receive standard derives that make them work with Anchor:Multiple instructions, multiple types
Each#[instruction] produces its own .idarc file, so #[callback_accounts] generates a separate output type per circuit:
Generation process
When you define a confidential instruction:arcium buildcompiles your circuit and writes a.idarcinterface file describing its return type#[callback_accounts]reads the.idarcfile at compile time and generates corresponding Rust structs with predictable names- Confidentiality patterns are detected automatically and produce specialized types
- The generated types are available in your
#[arcium_callback]functions
How the naming works
The naming follows predictable patterns:Your circuit gets an output struct
If your confidential instruction is calledadd_together, you get a struct called AddTogetherOutput. Arcium converts your circuit name to PascalCase and adds “Output” at the end.
Fields are numbered
Since Anchor doesn’t support tuple structs (yet), Arcium uses numbered fields instead. So if your function returns multiple values, you’ll getfield_0, field_1, field_2, and so on. Not the prettiest names, but they’re consistent and predictable.
Complex types get their own structs
When your function returns complex nested data (like tuples or custom structs), Arcium generates additional helper structs with a unified naming convention:- All output structs use
{CircuitName}OutputStruct{index}pattern - Nested structs within outputs use
{ParentName}OutputStruct{parent_index}{field_index}pattern - The naming ensures uniqueness while maintaining consistency
Confidentiality types: Shared vs MXE
Arcium automatically detects different confidentiality patterns and generates the right struct type. Understanding when each type is used helps you predict the generated structs.SharedEncryptedStruct<N>
When your circuit returns Enc<Shared, T>, Arcium knows this is data that both the client and the MXE can reveal. It generates a struct that includes everything needed to reveal it:
<N> part tells you how many confidential values are packed inside. So SharedEncryptedStruct<1> has one confidential value, SharedEncryptedStruct<3> has three, and so on.
In your callback, you can access everything you need:
MXEEncryptedStruct<N>
For Enc<Mxe, T> data, only the MXE cluster can reveal it - clients can’t. Since there’s no shared secret needed, the struct is simpler:
encryption_key field here - that’s because clients can’t reveal MXE data.
EncDataStruct<N>
For confidential data without key exchange metadata (used when the observer tracks confidentiality context client-side):EncDataStruct<N> is used when only ciphertext data is needed without additional metadata. See EncData<T> for when to use this pattern. Most applications use SharedEncryptedStruct<N> or MXEEncryptedStruct<N> instead.
Moving to real-world applications
Now that you understand the basics with our simple addition example, here’s how this works in real applications. The key difference is that real apps often:- Return multiple values: Functions return tuples or complex structs instead of single values
- Mix confidentiality types: Some data for users (
Shared), some for MXE only (Mxe) - Handle complex data: Custom structs with multiple fields instead of simple numbers
Real-world examples
Here’s how this type generation works in actual Arcium applications:Simple tuple example
Let’s start with something in between - a function that returns two related values:(Enc<Shared, u32>, Enc<Shared, u32>), Arcium generates:
field_0, and its elements become field_0, field_1, etc.
Voting application
Now let’s look at a more realistic example. The confidential voting example shows a perfect use case. You have poll data that only the MXE should see, and a user’s vote that should be shared between the user and the MXE:(Enc<Mxe, PollData>, Enc<Shared, bool>), Arcium generates:
Coinflip application: back to basics
After seeing complex tuples and mixed confidentiality types, let’s look at the simplest possible case. The coinflip example returns just a single confidential boolean:Enc<Shared, bool> and creates:
Blackjack application
From the blackjack example with complex game state:Complex nested structures
For more complex outputs with nested data structures:ComplexExampleOutputStruct00 and ComplexExampleOutputStruct02, but no ComplexExampleOutputStruct01. This is because:
field_0(UserData) needs a custom struct →ComplexExampleOutputStruct00field_1(SharedEncryptedStruct) uses a predefined type → no custom struct neededfield_2((u64, f32) tuple) needs a custom struct →ComplexExampleOutputStruct02field_3(MXEEncryptedStruct) uses a predefined type → no custom struct needed
Working with generated types
Pattern matching
Use destructuring to access nested data:Error handling
Always handle computation failures:Best practices
1. Use descriptive variable names
2. Document your circuit interfaces
3. Handle all computation states
4. Emit events for client tracking
When things go wrong
Here are the most common issues and how to fix them:“Type not found” errors
- Typo in the circuit name - Check that
MyCircuitexactly matches your#[instruction]function name (case matters!) - You forgot to rebuild - Run
arcium buildagain after making changes to your confidential instructions
”No field found” errors
field_0, field_1, etc. There’s no field called result unless you specifically named your function that way.
Try this instead:
Confidentiality type mismatches
Enc<Mxe, T> but your callback expects Enc<Shared, T> (or vice versa). Double-check your confidential instruction’s return type - it needs to match what you’re expecting in the callback.
Callback not working? Check these:
- Circuit name matches exactly (case sensitive)
- Ran
arcium buildafter changing circuit - Handling
verify_outputOk/Err - Using correct field numbers (field_0, field_1, etc.)
- Array access within bounds (ciphertexts.len())
Finding generated types
The best way to see generated types:Array and complex type handling
Fixed-size arrays
When your circuit returns arrays, each element becomes a separate scalar in the LEN count:SharedEncryptedStruct<3> because the array has 3 elements:
Nested structures
For deeply nested data, LEN counts all scalar values at any depth:SharedEncryptedStruct<4> because Entity contains 4 total scalar values.
Migration from v0.1.x
If you’re upgrading from an older version, the new type generation system replaces manual byte parsing: Old way (v0.1.x):Type generation limitations
Supported return types
The type generation system works with most common Rust types, but has some constraints: ✅ Supported:- Primitive types:
u8,u16,u32,u64,u128,i8,i16,i32,i64,i128,bool - Fixed-size arrays:
[T; N]where N is a compile-time constant - Tuples:
(T, U, V)with any number of elements - Custom structs with supported field types
- Nested combinations of the above
- Dynamic types:
Vec<T>,String,HashMap<K, V> - Reference types:
&T,&mut T(except for input parameters) - Generic types with lifetime parameters
- Recursive or self-referencing structs
Option<T>orResult<T, E>as return types
Practical constraints
Size limitations:- Very large structs (1000+ fields) may impact compilation time
- Arrays with thousands of elements create correspondingly large LEN values
- Deep nesting (10+ levels) may cause macro expansion issues
Working within constraints
If you need unsupported types, consider these patterns:Common patterns and performance tips
Choosing the right confidentiality type
- Use
Enc<Shared, T>when users need to reveal and verify results (votes, game outcomes, personal data) - Use
Enc<Mxe, T>for internal state that users shouldn’t access (system secrets, aggregate statistics, protocol data)
Performance considerations
- Large arrays:
[u8; 1000]becomesSharedEncryptedStruct<1000>- consider if you really need all elements confidential - Complex nesting: Deep struct hierarchies increase LEN values - flatten when possible
- Mixed returns:
(Enc<Shared, T>, Enc<Mxe, U>)creates separate confidential structs for optimal access patterns
Testing your callbacks
Test callbacks in two layers:- Rust unit tests — extract the post-
verify_output()business logic into a pure function and test that directly. - TypeScript integration tests — queue a real computation with
arcium test, wait for finalization withawaitComputationFinalization(...), then assert the callback result via emitted events or updated account state. See Building and testing for thearcium testrunner and Tracking callbacks forawaitComputationFinalization.
verify_output() performs real BLS signature verification against the cluster’s BLS key, so a mocked SignedComputationOutputs with a dummy signature always fails verification in a unit test. That’s why unit tests cover the pure logic only — exercise the full verification wiring through the arcium test flow instead: queue a computation, awaitComputationFinalization(...), then assert on the emitted event or updated account state.
verify_output(), destructure the result, and delegate to the pure function that holds your business logic. The pure function is what you unit-test; the arcium test flow is what exercises the callback end-to-end.
Quick reference
| Return Type | Generated Struct | Access Pattern |
|---|---|---|
Enc<Shared, T> | SharedEncryptedStruct<1> | result.ciphertexts[0], result.nonce |
Enc<Mxe, T> | MXEEncryptedStruct<1> | result.ciphertexts[0], result.nonce |
(T, U, V) | {Circuit}OutputStruct0 | result.field_0, result.field_1 |
| Custom struct | {Circuit}OutputStruct0 | result.field_0, result.field_1 |
The callback type generation system automatically handles confidential computation results, eliminating manual byte parsing and offset tracking. With properly typed structs, you can work directly with structured data and focus on building your applications rather than handling low-level data conversion. These generated types provide type safety and predictable patterns that make working with confidential computation outputs straightforward and reliable.
What’s next?
Callback accounts
Pass additional accounts to your callback for storing results.
Deployment
Deploy your MXE program to devnet or mainnet.