Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 40 additions & 45 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,87 +3,82 @@
Guidance for agents and developers working in this repo. This file is the source
of truth for how to build, run and not break things. `CLAUDE.md` points here.

It deliberately does **not** restate versions, decisions or known upstream
issues — those live in [`docs/roadmap-m1.md`](./docs/roadmap-m1.md), and a
second copy would go stale. What the product _is_ — and is not — is specified in
[`docs/m1-brief.md`](./docs/m1-brief.md). Work items live in
It deliberately does **not** restate the design or the dependency versions. The
design is in [`docs/architecture.md`](./docs/architecture.md); versions are
pinned in `Cargo.toml` and `rust-toolchain.toml`. A second copy of either would
go stale. Work items live in
[milestone M1](https://github.com/BootNodeDev/strata-vault-kit/milestone/1).

## What this is

A white-label tokenized vault on Stellar/Soroban, built on the OpenZeppelin
Soroban vault. An approved investor deposits USDC and receives `bvUSDC` shares;
they redeem and get USDC back.
A white-label RWA vault kit on Stellar/Soroban. Shares are a claim on an
off-chain asset whose value is attested on chain, so no price exists at the
moment an investor acts. Entry and exit are therefore requests: what goes in is
escrowed, the next accepted attestation prices it, and the investor claims the
result. Three states per side, none skipped: **pending, priced, claimed**.

Two invariants the contract enforces:
What the contracts enforce:

1. **Entry is gated** by a post-KYC allowlist — the `deposit`/`mint` receiver,
and both sides of `transfer`/`transfer_from`.
2. **Exit is never gated and never pausable.** A de-listed holder can always
leave. `withdraw`/`redeem` carry no allowlist check by design.
1. **Entry is gated** by a post-KYC allowlist, checked on the receiver of a
subscription and on every share transfer.
2. **A covered claim always pays.** Once priced and covered, a cash claim cannot
be blocked by a pause, a stale valuation, or the investor losing their
allowlist place. Priced claims are never re-priced and never identity-gated;
a delisted, non-frozen investor leaves through the exit-only cash path.
3. **Cancellation is atomic and single-step**, open only until the attestation
that prices the request is accepted. There is no instant exit.

Milestone 1 has no yield: shares stay 1:1. Testnet only. Not audited.

## State of the repo

Bootstrap only: documentation, issue templates and the toolchain pin. The Rust
workspace arrives with the first contract crate, and the interface after that.
The sections below describe how the project is built as each piece lands.
Five authorities, each a native Stellar multisig: governance, compliance,
attestation, treasury, guardian. Testnet only. Not audited.

## Reference base

[`stellar-vault-demo-dapp`](https://github.com/BootNodeDev/stellar-vault-demo-dapp)
is our own working testnet demo. Its 188-line contract proves the design. Read
it while building; do not port it wholesale. Its frontend is not carried over —
M1 builds a new one designed around the role model.
is our own working testnet demo, but it is **synchronous**: deposit and withdraw
are priced at call time. It does not model the request lifecycle and its flow
does not carry over. Read it for Soroban and OZ mechanics only.

## Build & run

- **Contracts:** `stellar contract build` — **not** `cargo build`. The OZ crates
enable an experimental `soroban-sdk` feature (`spec_shaking_v2`) that only
works through the CLI wrapper (Stellar CLI ≥ 25.2).
- **Tests:** `cargo test -p vault` (unit tests run against the in-memory `Env`).
- **Toolchain:** pinned in `rust-toolchain.toml`. rustup installs it on first
build.
works through the CLI wrapper. The devshell pins the Stellar CLI it expects.
- **Tests:** `cargo test` from the repo root runs every workspace member against
the in-memory `Env`. There is no unit-test runner for `app/` or `app-lib/`;
`e2e/` runs Playwright separately. CI does not run the Rust tests yet.
- **Toolchain:** `nix develop` provides it, or rustup honours
`rust-toolchain.toml`.

## Gotchas

Carried over from the reference base, where each one cost real debugging. They
apply as the corresponding code lands here.

- Build with `stellar contract build`, not `cargo build` (see above).
- OZ vault wiring: `#[contractimpl(contracttrait)]` on **both** `FungibleToken`
and `FungibleVault`; `type ContractType = Vault` goes **only** on
`FungibleToken`; import `soroban_sdk::MuxedAddress` (the contracttrait macro
references it).
- Do **not** call `operator.require_auth()` inside overridden vault methods —
`Vault::*` already authorizes, and a second call fails with
`Error(Auth, ExistingValue)`.
- `ed25519-dalek` v3 breaks the test build; pin to `2.2.0` if it resolves
higher.
- `motion` must be v12+ (`motion/react`); a bare `npm i motion` pulls v10
(Motion One), which has no React entry.
- USDC is a **classic asset** → an account needs a trustline to hold it.
`bvUSDC` is a **Soroban contract token** → no trustline. Deposit is a single
transaction with nested authorization; there is no separate `approve`. Get
test USDC from Circle's faucet (pick Stellar) after establishing the
trustline.
- A SEP-56 vault is **not** the base here: its interface assumes the price
exists at call time, which a request lifecycle cannot express.
- `ed25519-dalek` is transitive and unpinned by any manifest. A newer major
breaks the test build; hold it back in the lockfile if compilation fails
there.
- The deposit asset is a **classic asset** → an account needs a trustline to
hold it. The share token is a **Soroban contract token** → no trustline.
Deposit is a single transaction with nested authorization; there is no
separate `approve`. When the deposit asset is USDC, Circle's faucet (pick
Stellar) issues test units once the trustline exists.
- Two network configs must agree: `environments.toml` is the network the
CLI/scaffold **deploys** to, `app/.env` (`PUBLIC_STELLAR_*`) is the network
the **frontend** reads at runtime. The scaffold default is local, so both need
setting or the UI talks to the wrong chain.
- Generated contract clients ship their `src/` but not their `dist/`. A fresh
clone builds the client before the app, or `tsc` cannot resolve the module.
- No `Cargo.lock` is committed until the first contract crate lands (#20); see
the comment in `Cargo.toml`.
- `app-lib/clients/index.ts` is auto-generated and rewritten on every build or
redeploy. Do not hand-edit it; customize by importing the client under `app/`.

## Deployed addresses

Not recorded here on purpose. The reference base kept them in this file and they
drifted: its `AGENTS.md` and its generated client pointed at two different vault
contracts. The addresses emitted by the deploy script (#15) are authoritative.
contracts. The addresses emitted by the deploy script are authoritative.

## Conventions

Expand Down
10 changes: 8 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@ See [AGENTS.md](./AGENTS.md) for what this repo is, how to build and run it,
network configuration, and the Stellar-specific gotchas. It is the single source
of truth for this project's conventions.

Versions, decisions and known upstream issues live in
[`docs/roadmap-m1.md`](./docs/roadmap-m1.md) — not duplicated anywhere else.
The design lives in [`docs/architecture.md`](./docs/architecture.md) — the
source of truth for what the protocol does and why. It wins on the **what**:
properties, invariants and guarantees. On the **how**, a disagreement with the
code is resolved explicitly rather than by default — a mechanism that proves
better in the code is a reason to amend the document, not a defect.

Dependency versions are pinned in `Cargo.toml` and `rust-toolchain.toml` — read
them there rather than from prose.
Loading