# AGENTS.md Guidance for coding agents (Claude Code, Codex, …) working in this repository. ## Project Overview Arbiter is a **permissioned signing service** for cryptocurrency wallets: - **`server/`** — Rust gRPC daemon that holds encrypted keys and enforces policies - **`useragent/`** — Flutter app (desktop + mobile + web targets) with a Rust core via `flutter_rust_bridge` - **`protobufs/`** — Protocol Buffer definitions shared between server and clients - **`docs/`** — `ARCHITECTURE.md` (peer types, flows, threat model) and `IMPLEMENTATION.md`; treat them as the design source of truth and update them when behaviour changes - **`scripts/`** — helper scripts, e.g. `gen_erc20_registry.py` The vault never exposes key material; it only produces signatures when requests satisfy configured policies. ## Toolchain Setup Tools are managed via [mise](https://mise.jdx.dev/). Install all required tools: ```sh mise install ``` Key versions live in `mise.toml` (currently Rust 1.95.0 with clippy, Flutter 3.41.7-stable, protoc 29.6, diesel_cli 2.3.7 with `sqlite-bundled`, Python 3.14). Also provided there: `cargo-nextest`, `cargo-audit`, `cargo-vet`, `cargo-shear`, `cargo-mutants`, `cargo-features-manager`, `cargo-edit`, `ast-grep`, `flutter_rust_bridge_codegen`. ## Server (Rust workspace at `server/`) ### Crates | Crate | Purpose | |---|---| | `arbiter-proto` | Generated gRPC stubs + protobuf types (`tonic-prost-build`); also `ArbiterUrl`, `home_path()`, `BOOTSTRAP_PATH` | | `arbiter-crypto` | Shared crypto primitives: `authn` (ML-DSA), `safecell` (hardened memory), `hashing::Hashable`, re-exported `x-wing` | | `arbiter-macros` | `#[derive(Hashable)]` — canonical hashing of structs for the DB integrity layer | | `arbiter-server` | Main daemon — actors, peers, DB, EVM policy engine, gRPC service implementation | | `arbiter-client` | Rust client library for SDK clients (`ArbiterClient`, EVM wallet, key storage) | | `arbiter-tokens-registry` | Generated ERC-20 token registry used by token-transfer policies | Workspace lints (`server/Cargo.toml`) are strict: most of clippy `pedantic`/`nursery` plus a large restriction set. `as` casts, indexing/slicing, `dbg!`, float arithmetic and undocumented `unsafe` are denied or warned — expect to add an `#[expect(..., reason = "...")]` rather than to silence a lint globally. ### Common Commands ```sh cd server # Build cargo build # Run the server daemon cargo run -p arbiter-server # Run all tests (preferred over cargo test; CI uses --all-features) cargo nextest run # Run a single test cargo nextest run # Lint (CI runs it with -D warnings) cargo clippy --all -- -D warnings # Security audit cargo audit # Supply-chain review (config in server/supply-chain/) cargo vet # Check unused dependencies cargo shear # Mutation testing cargo mutants ``` ### CI Woodpecker pipelines in `.woodpecker/` run on `server/**` changes: `server-lint` (clippy), `server-test` (nextest, `--all-features`), `server-audit`, `server-vet`, plus `useragent-analyze` for the Flutter app. ### Architecture The server is actor-based using the **kameo** crate. Long-lived state lives in `GlobalActors` (`src/actors/mod.rs`): - **`Bootstrapper`** — one-time bootstrap token, written to `~/.arbiter/bootstrap_token` on first run - **`Vault`** — encrypted root key and the Sealed/Unsealed state machine; on unseal decrypts the root key into a `memsafe`-backed `SafeCell` - **`FlowCoordinator`** — cross-connection flow between operators and SDK clients - **`OperatorRegistry`** — tracks currently connected operators - **`EvmActor`** — EVM transaction policy enforcement and signing - **`events`** — a `kameo_actors::MessageBus` (`DeliveryStrategy::Guaranteed`) for cross-actor notifications Per-connection state lives under **`src/peers/`**, not `actors/`: `peers/client/` and `peers/operator/`, each with `auth` (challenge-response) and `session` (post-auth) sub-modules; the operator side additionally has `vault_gate/` for the unseal handshake. The gRPC surface lives in **`src/grpc/`**, split per peer (`client/`, `operator/`, `common/`) and per direction (`inbound.rs` — requests to the daemon, `outbound.rs` — server-initiated streams), with `request_tracker.rs` correlating the two. EVM logic is in `src/evm/`: `policies/ether_transfer/`, `policies/token_transfers/`, `abi.rs`, `safe_signer.rs`. **Database:** SQLite via `diesel-async` + `bb8`. Schema in `src/db/schema.rs`, models in `src/db/models.rs`, embedded migrations in `crates/arbiter-server/migrations/`. DB file lives at `~/.arbiter/arbiter.sqlite`; tests use a temp-file DB via `db::create_test_pool()`. Entity ids are newtypes generated by the `declare_id!` macro in `db::models` (`OperatorId`, `ChainId`, …), each a `#[repr(transparent)]` wrapper over `i32` with `to_raw`/`from_raw`. Pass these around instead of bare `i32`. **Row integrity:** sensitive rows are covered by an HMAC-SHA256 envelope (`src/crypto/integrity/`, table `integrity_envelope`), keyed from the vault root key. A struct becomes coverable by deriving `arbiter_macros::Hashable` and implementing `Integrable` (`KIND` + `VERSION`). When adding or changing a covered entity, keep the derive and the payload version in sync — a mismatch surfaces as `PayloadVersionMismatch` or `MacMismatch` at runtime. **Cryptography:** - Authentication: **ML-DSA-87** (post-quantum, `arbiter-crypto::authn::v1`), challenge-response with per-peer nonce tracking - Encryption at rest: XChaCha20-Poly1305, versioned modules (`crypto/encryption/v1.rs`) with a `schema_version` column for transparent migration on unseal - Password KDF: Argon2 - Unseal transport: X25519 ephemeral key exchange (`peers/operator/vault_gate/`); `x-wing` (hybrid PQ KEM) is available via `arbiter-crypto` - TLS: self-signed certificate (rustls + aws-lc-rs, `prefer-post-quantum`), fingerprint distributed via `ArbiterUrl` Crypto modules are versioned by convention: `mod.rs` re-exports the current `vN`. Add a `v(N+1)` rather than editing an existing version in place. **Protocol:** gRPC with Protocol Buffers. `ArbiterUrl` encodes host, port, CA cert and bootstrap token into a single shareable string (printed to console on first run). ### Proto Regeneration `arbiter-proto/build.rs` compiles `arbiter.proto`, `operator.proto`, `client.proto` and `evm.proto` (with their `shared/`, `operator/`, `client/` includes) on build: ```sh cd server && cargo build -p arbiter-proto ``` Dart protobuf stubs are generated separately, from the repo root: ```sh mise run codegen # protoc --dart_out=grpc:useragent/lib/proto ``` ### Database Migrations ```sh # Create a new migration diesel migration generate --migration-dir crates/arbiter-server/migrations # Run migrations manually (server also runs them on startup) diesel migration run --migration-dir crates/arbiter-server/migrations ``` Pre-release policy: there is a single `init` migration and no deployed databases yet, so schema changes are made by editing that migration directly instead of stacking new ones. Regenerate `src/db/schema.rs` after changing it. ### Code Conventions **`#[must_use]` Attribute:** Apply the `#[must_use]` attribute to return types of functions where the return value is critical and should not be accidentally ignored. This is commonly used for: - Methods that return `bool` indicating success/failure or validation state - Any function where ignoring the return value indicates a logic error Do not apply `#[must_use]` redundantly to items (types or functions) that are already annotated with `#[must_use]`. Example: ```rust #[must_use] pub fn verify(&self, nonce: i32, context: &[u8], signature: &Signature) -> bool { // verification logic } ``` This forces callers to either use the return value or explicitly ignore it with `let _ = ...;`, preventing silent failures. ## User Agent (Flutter + flutter_rust_bridge at `useragent/`) The Flutter app calls Rust through [flutter_rust_bridge](https://cjycode.com/flutter_rust_bridge/) 2.12.0. The Rust side is the `rust_lib_arbiter` crate at `useragent/rust/`; everything exposed to Dart is declared in `useragent/rust/src/api/` and lands in `useragent/lib/src/rust/` (see `useragent/flutter_rust_bridge.yaml`). Dart UI code is organised as `lib/features/`, `lib/screens/`, `lib/widgets/`, `lib/providers/`, `lib/theme/`, with routing in `lib/router.dart` (`router.gr.dart` is generated). ### Common Commands ```sh cd useragent # Run the app flutter run # Regenerate Rust↔Dart bindings after editing rust/src/api/ mise run codegen # flutter_rust_bridge_codegen generate # Analyze Dart code (also run in CI) flutter analyze ``` Note: `app/` contains only stale generated Flutter artifacts and is not the application source.