> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stoffelmpc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Your First MPC Project

> Build and run a small secret-shared eligibility example with the Stoffel CLI and Rust SDK workflow.

This tutorial builds a small eligibility-score computation. The goal is to model one client value as a share, compute over that share, and reconstruct the derived score explicitly.

<Note>
  This is a small local example. Use the same modeling steps with your own deployment and security validation when moving beyond the tutorial.
</Note>

<img src="https://mintcdn.com/stoffellabs/WsJVEVCX73RSUEma/images/campaign/stoffel-how-it-works-architecture.png?fit=max&auto=format&n=WsJVEVCX73RSUEma&q=85&s=7cb8865dfce89c3a2b5018c005413c02" alt="Campaign-style Stoffel architecture diagram showing how a StoffelLang program becomes app-prepared inputs, a build contract, and networked MPC runtime execution." width="1800" height="1125" data-path="images/campaign/stoffel-how-it-works-architecture.png" />

## What you will use

* The `stoffel` CLI.
* The Rust SDK from crates.io when you run or adapt the Rust wrapper path.
* A `Share`-based StoffelLang program.

Finish [Installation](./installation) before starting.

## Create a project

```bash theme={null}
stoffel init eligibility-demo
cd eligibility-demo
```

Check the generated program first:

```bash theme={null}
stoffel check
stoffel build
stoffel run --program-info
```

## Replace `src/main.stfl`

Use this simple `Share`-based program:

```stoffel theme={null}
def normalize_score(raw_score: Share) -> Share:
  var adjusted = raw_score.add_scalar(25)
  return adjusted.mul_scalar(2)

def main() -> int64:
  var private_score = ClientStore.take_share(0, 0)
  var eligibility = normalize_score(private_score)
  return eligibility.open()
```

The program keeps the input as a `Share` while it computes, then opens only the final eligibility score as the allowed tutorial output.

## Validate and build

```bash theme={null}
stoffel check
stoffel build
```

Inspect the bytecode if needed:

```bash theme={null}
stoffel compile --disassemble target/debug/eligibility-demo.stflb
```

If your project name produces a different bytecode file name, list `target/debug` and use that path.

## Run locally with a client input

Run the project through local MPC. This starts several MPC nodes/processes locally on your machine and feeds client slot `0` with the private input value:

```bash theme={null}
cd /path/to/eligibility-demo
stoffel run \
  --client-input 0=42 \
  --parties 5 \
  --threshold 1
```

The returned value is `(42 + 25) * 2`, not the raw client input.

## Iterate in watch mode

```bash theme={null}
stoffel dev \
  --client-input 0=42 \
  --parties 5 \
  --threshold 1
```

For scripts, add `--once`.

## Run the same idea from Rust

The Rust SDK is the primary application API for embedding Stoffel programs in Rust apps. Use the bytecode you already built from `src/main.stfl` instead of embedding a second sample program in Rust. In this example, `.execute_local().await?` performs local MPC testing by spawning several MPC nodes/processes on your machine:

```rust theme={null}
use stoffel::prelude::*;

#[tokio::main]
async fn main() -> stoffel::Result<()> {
    let result = Stoffel::load_file("target/debug/eligibility-demo.stflb")?
        .parties(5)
        .threshold(1)
        .with_client_input(0, &[42_i64])
        .execute_local()
        .await?;

    println!("Result: {}", result[0]);
    Ok(())
}
```

## Shape it like an app integration

A larger app usually separates the code that owns the MPC network from the code that handles user/client requests. MPC nodes run away from input and output clients; the client-facing app identifies the private portion of a request and hands that portion to the Stoffel-backed workflow.

For this tutorial, keep everything on your development machine but split the project like an app would be split:

```text theme={null}
src/
├── app.rs                  # ordinary app types and non-private decision logic
├── client.rs               # client/request handling boundary
├── lib.rs                  # shared module exports
├── main.stfl               # private MPC program
└── bin/
    ├── local_mpc.rs        # initializes the Stoffel program and local MPC loop
    └── eligibility_client.rs # command-line client handler
```

Create `src/lib.rs`:

```rust theme={null}
pub mod app;
pub mod client;
```

Create `src/app.rs` for the ordinary application types:

```rust theme={null}
#[derive(Debug, Clone)]
pub struct EligibilityRequest {
    pub account_id: String,
    pub private_score: i64,
    pub public_region: String,
}

#[derive(Debug, Clone)]
pub struct EligibilityDecision {
    pub account_id: String,
    pub public_region: String,
    pub eligibility_score: i64,
    pub approved: bool,
}

pub fn decision_from_score(
    request: EligibilityRequest,
    eligibility_score: i64,
) -> EligibilityDecision {
    EligibilityDecision {
        account_id: request.account_id,
        public_region: request.public_region,
        eligibility_score,
        approved: eligibility_score >= 100,
    }
}
```

Create `src/bin/main.rs`. This is the local development network owner: it loads the bytecode built from `src/main.stfl`, waits for client requests, and runs each private score through local MPC. Stop it with `Ctrl-C` when you are done.

```rust theme={null}
use std::{fs, path::Path, time::Duration};

use stoffel::prelude::*;
use tokio::time::sleep;

const WORK_DIR: &str = "target/dev-mpc";
const REQUEST_FILE: &str = "target/dev-mpc/request.txt";
const RESPONSE_FILE: &str = "target/dev-mpc/response.txt";

#[tokio::main]
async fn main() -> stoffel::Result<()> {
    fs::create_dir_all(WORK_DIR)?;
    let runtime = Stoffel::load_file("target/debug/eligibility-demo.stflb")?
        .parties(5)
        .threshold(1)
        .build()?;

    println!("Local MPC process ready. Waiting for requests in {REQUEST_FILE}.");

    let mut last_request = String::new();
    loop {
        if Path::new(REQUEST_FILE).exists() {
            let request = fs::read_to_string(REQUEST_FILE)?;
            if request != last_request {
                last_request = request.clone();
                let private_score: i64 = request.trim().parse().map_err(|error| {
                    Error::InvalidInput(format!("invalid private score: {error}"))
                })?;

                let result = runtime
                    .clone()
                    .with_client_input(0, &[private_score])
                    .execute_local()
                    .await?;

                let eligibility_score = result[0]
                    .as_i64()
                    .ok_or_else(|| Error::InvalidInput("expected int64 eligibility score".into()))?;

                fs::write(RESPONSE_FILE, eligibility_score.to_string())?;
                println!("Handled private score for client slot 0.");
            }
        }

        sleep(Duration::from_millis(250)).await;
    }
}
```

Create `src/client.rs`. This file is the client-handling boundary: it accepts an application request, sends only the private score to the local MPC side, then combines the returned score with ordinary app fields.

```rust theme={null}
use std::{fs, path::Path, thread, time::Duration};

use stoffel::prelude::*;

use crate::app::{decision_from_score, EligibilityDecision, EligibilityRequest};

const WORK_DIR: &str = "target/dev-mpc";
const REQUEST_FILE: &str = "target/dev-mpc/request.txt";
const RESPONSE_FILE: &str = "target/dev-mpc/response.txt";

pub fn handle_eligibility_request(
    request: EligibilityRequest,
) -> stoffel::Result<EligibilityDecision> {
    fs::create_dir_all(WORK_DIR)?;
    if Path::new(RESPONSE_FILE).exists() {
        fs::remove_file(RESPONSE_FILE)?;
    }

    fs::write(REQUEST_FILE, request.private_score.to_string())?;

    let eligibility_score = loop {
        if Path::new(RESPONSE_FILE).exists() {
            let raw = fs::read_to_string(RESPONSE_FILE)?;
            break raw.trim().parse::<i64>().map_err(|error| {
                Error::InvalidInput(format!("invalid eligibility score: {error}"))
            })?;
        }
        thread::sleep(Duration::from_millis(250));
    };

    Ok(decision_from_score(request, eligibility_score))
}
```

Create `src/bin/eligibility_client.rs` as a command-line entry point for the client-handling code:

```rust theme={null}
use eligibility_demo::app::EligibilityRequest;
use eligibility_demo::client::handle_eligibility_request;

fn main() -> stoffel::Result<()> {
    let mut args = std::env::args().skip(1);
    let account_id = args.next().unwrap_or_else(|| "acct_123".to_owned());
    let private_score = args
        .next()
        .as_deref()
        .unwrap_or("42")
        .parse::<i64>()
        .map_err(|error| stoffel::Error::InvalidInput(format!("invalid score: {error}")))?;
    let public_region = args.next().unwrap_or_else(|| "ca".to_owned());

    let decision = handle_eligibility_request(EligibilityRequest {
        account_id,
        private_score,
        public_region,
    })?;

    println!("{decision:?}");
    Ok(())
}
```

Run the local MPC owner in one terminal:

```bash theme={null}
cargo run --bin main
```

Then run the client handler from another terminal:

```bash theme={null}
cargo run --bin eligibility_client -- acct_123 42 ca
```

The important separation is that ordinary app logic stays in Rust, client handling lives in its own module, and the Stoffel program owns the private computation. In this local tutorial, `main.rs` starts local MPC nodes on the same development machine. In a deployed setup, that network would run elsewhere, and the client-handling side would connect to it instead of using this local file-based handoff.

<Note>
  Want to deploy MPC apps instead of running local development networks? [Sign up for Stoffel updates](https://buttondown.com/stoffel) to hear when we launch the platform for deploying MPC apps.
</Note>

## Design notes

* Identify which input is secret-shared, which policy logic is public, and which computed result should be reconstructed.
* Keep sensitive values as `Share` values while computing.
* Use `open()` only for outputs you intentionally reveal.
* For client-directed share outputs beyond this tutorial, prefer `MpcOutput.send_to_client` or `Share.send_to_client`.
* Use `stoffel check --print-ir`, `stoffel run --program-info`, and bytecode disassembly when debugging.

## Next steps

* [Basic Usage](./basic-usage)
* [StoffelLang Overview](../stoffel-lang/overview)
* [Rust SDK Examples](../rust-sdk/examples)
