---
title: "Stack Definitions"
description: "Introduction to the Arete Rust DSL for defining streaming data specifications."
editUrl: true
head: []
template: "doc"
sidebar: {"order":2,"hidden":false,"attrs":{}}
pagefind: true
draft: false
---

Arete uses a declarative Rust DSL (Domain Specific Language) to define how on-chain Solana data should be transformed, aggregated, and streamed to your application. Instead of writing complex ETL pipelines, you simply declare the final state you want, and Arete handles the rest.

---

## Why Declarative?

Building data pipelines for Solana typically involves manual account parsing, complex event handling, and managing state synchronization. Arete replaces this imperative approach with a declarative model:

| Imperative Approach (Traditional)             | Declarative Approach (Arete)                  |
| --------------------------------------------- | --------------------------------------------- |
| Write custom decoding logic for every account | Use `#[map]` to link IDL fields to your state |
| Manually track and sum event values           | Use `#[aggregate(strategy = Sum)]`            |
| Manage WebSocket connections and state diffs  | Define entities and let Arete stream updates  |
| Build custom backend services for data        | Deploy a stack and use generated SDKs         |

---

## Anatomy of a Stack Definition

A Arete definition is a Rust module annotated with `#[arete]`. Inside this module, you define **Entities** — the structured data objects your application will consume. A single module can contain multiple `#[entity]` structs, all packaged into one stack.

The ORE stack is a real example. Here's a simplified version showing the core concepts with a single IDL:

```rust
use arete::prelude::*;

#[arete(idl = "idl/ore.json")]
pub mod ore_stream {
    use arete::macros::Stream;
    use serde::{Deserialize, Serialize};

    // OreRound is the main entity -- one instance per mining round.
    // The `latest` view sorts rounds descending by round_id.
    #[entity(name = "OreRound")]
    #[view(name = "latest", sort_by = "id.round_id", order = "desc")]
    pub struct OreRound {
        pub id: RoundId,
        pub state: RoundState,
        pub metrics: RoundMetrics,
    }

    #[derive(Debug, Clone, Serialize, Deserialize, Stream)]
    pub struct RoundId {
        // Primary key -- set once, never overwritten
        #[map(ore_sdk::accounts::Round::id, primary_key, strategy = SetOnce)]
        pub round_id: u64,

        #[map(ore_sdk::accounts::Round::__account_address, lookup_index, strategy = SetOnce)]
        pub round_address: String,
    }

    #[derive(Debug, Clone, Serialize, Deserialize, Stream)]
    pub struct RoundState {
        // LastWrite: overwritten each time the account updates
        #[map(ore_sdk::accounts::Round::motherlode, strategy = LastWrite)]
        pub motherlode: Option<u64>,

        #[map(ore_sdk::accounts::Round::total_deployed, strategy = LastWrite)]
        pub total_deployed: Option<u64>,

        // Computed field: derived from other fields in this entity
        #[computed(state.total_deployed.map(|d| d / 1_000_000_000))]
        pub total_deployed_sol: Option<u64>,
    }

    #[derive(Debug, Clone, Serialize, Deserialize, Stream)]
    pub struct RoundMetrics {
        // Aggregate: counts Deploy instructions referencing this round
        #[aggregate(from = ore_sdk::instructions::Deploy, strategy = Count, lookup_by = accounts::round)]
        pub deploy_count: Option<u64>,
    }
}
```

:::note[SDK Module Naming]
Arete derives the program name from the IDL metadata and generates a typed SDK module named `{name}_sdk`. The ORE IDL produces `ore_sdk::accounts::*` and `ore_sdk::instructions::*`. The Entropy IDL produces `entropy_sdk::*`. All `#[map]` and `#[aggregate]` paths use these prefixes.
:::

### Key Components

1. **`#[arete]` Module** — The container for your definition. The `idl` argument accepts a single path or an array for multi-program stacks.
2. **`#[entity]` Struct** — An entity is an individual definition of some part of your app's data. Each entity represents a distinct concept — a round, a miner, a treasury — and declares exactly which on-chain fields belong to it, across as many accounts or programs as needed. A stack can have many entities.
3. **`#[view]`** — A view is a projection over an entity's data. It defines what slice of the stream a client subscribes to. Every entity gets `state` (one item by key) and `list` (all items) by default. `#[view]` adds custom sorted or filtered projections on top — like `OreRound/latest` which streams rounds sorted by `round_id` descending.
4. **`#[derive(Stream)]` Structs** — Nested structs containing the actual field mappings. Must derive `Stream`, `Debug`, `Clone`, `Serialize`, and `Deserialize`.
5. **Primary Key** — Every entity needs one (annotated `primary_key`). This is how Arete tracks individual entity instances.
6. **Field Mappings** — Attributes on struct fields defining where data comes from and how it's processed.
7. **Resolvers** — Fetch off-chain data (e.g. token metadata) and make it available to transforms.

---

## Mapping Types

Arete provides several mapping attributes to populate your entity fields:

| Attribute             | Source              | Description                                                                                                                                                   |
| --------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `#[map]`              | Account State       | Tracks fields within a Solana account. Updates whenever the account changes. Supports `lookup_index(register_from = [...])` for cross-account PDA resolution. |
| `#[from_instruction]` | Instructions        | Extracts arguments or account keys from a specific instruction.                                                                                               |
| `#[aggregate]`        | Events/Instructions | Computes running values (Sum, Count, etc.) from a stream of events.                                                                                           |
| `#[event]`            | Events              | Captures specific instructions as a log of events within the entity.                                                                                          |
| `#[snapshot]`         | Account State       | Captures the entire state of an account at a specific point in time.                                                                                          |
| `#[computed]`         | Local Fields        | Derives a new value by performing calculations on other fields in the same entity.                                                                            |
| `#[derive_from]`      | Instructions        | Populates fields by deriving data from instruction context.                                                                                                   |

---

## Population Strategies

When data arrives, **Strategies** determine how the field value is updated. This is particularly powerful for aggregations.

| Strategy      | Behavior                                                              |
| ------------- | --------------------------------------------------------------------- |
| `LastWrite`   | (Default) Overwrites the field with the latest value.                 |
| `SetOnce`     | Sets the value once and ignores subsequent updates (perfect for IDs). |
| `Sum`         | Adds the incoming value to the existing total.                        |
| `Count`       | Increments the total by 1 for every matching event.                   |
| `Append`      | Adds the incoming value to a list (creating an event log).            |
| `Max` / `Min` | Keeps only the highest or lowest value seen.                          |

---

## Multi-Program Stacks

A single stack can consume data from multiple Solana programs by passing an array of IDL files:

```rust
#[arete(idl = ["idl/ore.json", "idl/entropy.json"])]
pub mod ore_stream {
    // ore_sdk::accounts::* and ore_sdk::instructions::* are available
    // entropy_sdk::accounts::* and entropy_sdk::instructions::* are available
}
```

Each IDL generates its own namespaced SDK module (e.g., `ore_sdk`, `entropy_sdk`). You can then map fields from any program's accounts and reference instructions from any program in `register_from`, `#[aggregate]`, `#[event]`, etc.

To link accounts across programs, use `lookup_index(register_from = [...])` — see the [Macro Reference](/building-stacks/rust-dsl/macros#cross-account-resolution-with-register_from) for the full syntax and examples.

---

## Example WebSocket Frames

The stack definition above produces WebSocket frames with the following structure. An `upsert` frame contains the full entity state:

```json
{
  "op": "upsert",
  "mode": "state",
  "entity": "Token",
  "key": "So11111111111111111111111111111111111111112",
  "data": {
    "id": {
      "mint": "So11111111111111111111111111111111111111112"
    },
    "state": {
      "reserves": 1500000000,
      "tvl": 150000000000
    },
    "metrics": {
      "total_volume": 42000000000
    }
  }
}
```

When only specific fields change, a `patch` frame contains just the updated values:

```json
{
  "op": "patch",
  "mode": "state",
  "entity": "Token",
  "key": "So11111111111111111111111111111111111111112",
  "data": {
    "state": {
      "reserves": 1520000000,
      "tvl": 152000000000
    }
  }
}
```

The SDK merges patches into local state automatically, so your application always sees the complete entity.

---

## Next Steps

- [Mapping Macros](./rust-dsl/macros) — Deep dive into every mapping attribute and its parameters.
- [Aggregation Strategies](./rust-dsl/strategies) — Learn how to build complex metrics using different strategies.
- [CLI Reference](/cli/commands) — Learn how to build and deploy your stacks.
