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
76 changes: 76 additions & 0 deletions finance/vault-strategy/anchor/PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Product

<!-- impeccable:product-schema 1 -->

## Platform

web

## Users

The frontend serves two roles first; a third exists in the program and is secondary for now.

- **Depositor (primary):** a retail user who deposits USDC into a manager-run strategy, receives shares representing proportional ownership of the whole basket, watches their position's value track the portfolio, and withdraws their slice in kind when they choose. Highest-volume, lowest-expertise surface.
- **Manager (primary):** a power user who creates and operates a strategy — registers curator-approved assets, sets target weights, rebalances as prices drift, and collects the management fee. An operations surface where correctness and confirmation matter more than reach.
- **Curator (secondary, not yet in scope):** governs the approved-asset registry that bounds what managers may hold. Low-frequency, high-trust. Recorded so future work does not treat the manager as the only privileged role.

## Product Purpose

An **educational demo dApp** shipped alongside the `vault-strategy` Solana program (part of a public Solana examples collection). Its job is to make the program's mechanics legible and touchable — show, through a real interface, how deposits are priced by NAV, how shares represent ownership, how managers configure weights and rebalance, and how in-kind withdrawal works. Success is a visitor understanding and exercising the full deposit → hold → withdraw and configure → allocate → rebalance loops against the deployed program. It teaches by letting people operate it, not by describing it.

## Positioning

A **multi-asset** strategy built from single-asset vaults, transparently priced on-chain. Unlike an ERC-4626-style single-asset vault, one "strategy" owns one vault per asset plus a USDC vault, and every deposit is deployed across the basket at its target weights in the same transaction — there is no idle-cash mode. Deposit pricing, slippage floors, and fees are all derived on-chain from the Pyth oracle and the strategy's own parameters rather than trusted from a caller, which is the truth the interface must make visible: the numbers a user sees are the numbers the program enforces.

## Operating Context

- Solana wallet–connected web app; users sign transactions in-wallet.
- Runs against the deployed program on **devnet with mock tokens** for the demo. Portfolio assets in the example are **TSLAx** (Tesla) and **NVDAx** (NVIDIA) — xStocks issued on Solana by Backed Finance; mock tokens in tests.
- Prices come from **Pyth Network** `PriceUpdateV2` accounts with a 60-second staleness window; the client must supply fresh price accounts for pricing operations.
- Swaps in the example route through a **test-only mock swap router** (a fake Jupiter) that would be replaced by real [Jupiter](https://jup.ag) in production.
- Baskets beyond ~3 assets exceed a legacy transaction's 1232-byte limit, so the client must send **v0 transactions with an Address Lookup Table**; `deposit` pulls `14 + 5N` accounts and `withdraw` `10 + 4N` for `N` assets.

## Capabilities and Constraints

Program instructions the frontend can surface:

- **Registry / curator:** `initialize_registry`, `approve_asset` (whitelist of assets managers may use).
- **Manager:** `initialize_strategy`, `add_asset`, `set_weight` (including set-to-zero to retire an asset), `rebalance`, `collect_fees`.
- **Depositor:** `deposit` (USDC → minted shares), `withdraw` (burn shares → in-kind slice of every vault).

Rules the UI must respect and reflect:

- Deposits are accepted only when target weights sum to **exactly 10,000 bps**; a strategy is either still being configured or fully allocated and live (`StrategyNotFullyAllocated` otherwise).
- Shares: first deposit is 1:1 with USDC minor units; later deposits mint `deposit_usdc × total_shares / NAV`. Share mint is a PDA owned by the strategy PDA.
- Management fee is charged by minting new shares to the manager (dilution), fixed at creation, capped at `MAX_FEE_BPS` = 1,000 bps (10%), no setter to raise it. `collect_fees` is permissionless.
- Slippage floors are computed on-chain from the Pyth price and `max_slippage_bps` (capped at 1,000 bps); a manager-supplied minimum is not trusted.
- `MAX_ASSETS` = 16. `deposit` re-derives the full `0..asset_count` PDA range and refuses to run if any asset account is missing (`IncompleteAssetAccounts`), so NAV can't be understated.
- Withdrawal is **in-kind**: the user must already hold a token account for each asset; there is no built-in sell-to-cash.

Two program ports exist in the repo — an **Anchor** implementation and a **Quasar** implementation of the same program. The frontend targets the same on-chain behavior regardless of port.

## Brand Commitments

Existing name: **Vault Strategy** (the on-chain construct is a "strategy" built from single-asset "vaults"; the README is deliberate about this distinction). No logo, palette, or visual identity is committed yet. Voice in existing docs is precise, plain, and educational — it defines terms and links canonical references rather than hyping. Future design should not contradict that register.

## Evidence on Hand

- `anchor/README.md` — thorough product and financial-concept documentation (NAV, shares, fees, weights, slippage, in-kind withdrawal, transaction-size limits).
- `VIDEO_SCRIPT.md` at the `vault-strategy` root — an existing narrative walkthrough of the product.
- `anchor/CHANGELOG.md`, `quasar/CHANGELOG.md` — history.
- Program source and tests under `anchor/programs/` and `quasar/`.
- No real users, testimonials, deployment addresses, or production metrics exist; the demo runs on devnet with mock tokens. Future work must not fabricate mainnet deployment claims, real AUM, or user counts.

## Product Principles

1. **The interface shows what the program enforces.** Every number a user sees (NAV, share price, slippage floor, fee) is derived on-chain; surface the real computed values, never a client-side approximation presented as truth.
2. **Teach by operating.** This is a demo whose purpose is comprehension — make each step of the deposit/withdraw and configure/rebalance loops legible as the user performs it.
3. **Respect the state machine.** A strategy is either being configured or live; deposits require exactly-10,000-bps allocation. The UI should make the current phase and what's required to advance it obvious rather than surfacing raw errors.
4. **Distinguish the roles.** Depositor and manager have different jobs, risks, and permissions; do not collapse them into one undifferentiated view.
5. **Devnet honesty.** Present the demo as a demo — mock tokens, devnet, a mock swap router — without dressing it as production.

## Open Decisions

- Whether this frontend ever targets **production/mainnet** with real funds is undecided; recorded here rather than assumed.
- Curator/registry-governance UI is out of first scope; not yet designed.
- Which program port (Anchor vs Quasar) the client library binds to is a later implementation decision.
20 changes: 20 additions & 0 deletions finance/vault-strategy/anchor/app/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copy to .env.local and fill in. All values are read at build/runtime by src/solana/config.ts.

# Your Quicknode devnet RPC endpoint (required for reliable reads/sends).
VITE_RPC_URL=https://your-endpoint.solana-devnet.quiknode.pro/xxxxxxxx/

# Program IDs. Defaults below are the repo's localnet IDs (Anchor.toml / declare_id!).
# A fresh devnet deploy will produce NEW ids — override them here after deploying.
VITE_VAULT_PROGRAM_ID=VLT5W7bqhRN4nCdRpXm8UfHRxZd9EuZGqiSAkGHQfGh
VITE_ROUTER_PROGRAM_ID=SWPR8Rk3aq3DrDGLdaANq7xCMnXoUFUJWJJmCWxc8Jm

# The USDC mint the strategy was created with. The demo creates its own 6-decimal
# mock USDC during seeding — paste that mint here. (No safe default exists.)
VITE_USDC_MINT=

# Which strategy index to view (PDA seed "strategy" + index). Managers can create others.
VITE_STRATEGY_INDEX=0

# Explorer cluster label: "devnet" | "mainnet-beta" | "custom".
# "custom" uses VITE_RPC_URL as the explorer's customUrl.
VITE_CLUSTER=devnet
8 changes: 8 additions & 0 deletions finance/vault-strategy/anchor/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
dist
dist-ssr
*.local
.env
.env.local
.env.*.local
.DS_Store
5 changes: 5 additions & 0 deletions finance/vault-strategy/anchor/app/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Don't re-run `install` before every `pnpm <script>`. This repo's pnpm enforces a
# supply-chain policy that treats optional native build scripts (esbuild, bufferutil,
# tiny-secp256k1, …) as errors; those packages have JS fallbacks / provide prebuilt
# platform binaries, so skipping their build scripts is fine for this app.
verify-deps-before-run=false
71 changes: 71 additions & 0 deletions finance/vault-strategy/anchor/app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Vault Strategy — web app

A small, data-forward frontend for the [`vault-strategy`](../README.md) Solana program:
an educational demo of a manager-run investment vault. Investors deposit USDC and hold
shares priced at net asset value; a manager allocates the pooled USDC across a basket and
rebalances it. Every number on screen is a real on-chain account read.

Stack: **Vite + React + TypeScript + Tailwind**, `@solana/wallet-adapter`, `@coral-xyz/anchor`.
Target cluster: **devnet**.

## Setup

```bash
pnpm install
cp .env.example .env.local # then edit — see below
pnpm dev
```

### Environment (`.env.local`)

| Variable | Meaning |
| --- | --- |
| `VITE_RPC_URL` | Your Quicknode devnet RPC endpoint. |
| `VITE_VAULT_PROGRAM_ID` | vault-strategy program id. Defaults to the repo's localnet id; **override after a devnet deploy**. |
| `VITE_ROUTER_PROGRAM_ID` | mock-swap-router program id (same note). |
| `VITE_USDC_MINT` | The USDC mint the strategy was created with (the demo mints its own). |
| `VITE_STRATEGY_INDEX` | Which strategy PDA to view (`"strategy" + index`). Default `0`. |
| `VITE_CLUSTER` | `devnet` \| `mainnet-beta` \| `custom` — controls Explorer links. |

Nothing is hardcoded: program ids come from config (defaulting to `Anchor.toml`), and the
asset list, fee, weights, NAV, and manager are all read from chain.

## Scripts

| Command | What it does |
| --- | --- |
| `pnpm dev` | Run the app. |
| `pnpm build` | Type-check and build for production. |
| `pnpm typecheck` | `tsc --noEmit`. |
| `pnpm verify` | Offline client verification (discriminators, IDL, encode/decode, PDAs). |

## Status: build layers

1. **Client + IDL + scaffold** ✅ — config-driven Anchor client, hand-authored IDL/types,
every instruction wired to match the Rust tests, offline-verified.
2. Investor view — _next_.
3. Manager view.
4. Polish.

## Devnet reality (read this)

The programs are **not deployed to devnet**, and this environment has no Solana toolchain,
so the client is **verified offline** (`pnpm verify`) rather than against a live cluster.
To bring the demo up you need `solana` + `anchor` (or `cargo build-sbf`) to:

1. Build & deploy both programs to devnet (you'll get **new** program ids — put them in `.env.local`).
2. Create a 6-decimal mock USDC + the basket mints; init the router, set rates, fund its treasury.
3. Create a registry, approve the basket assets (bound to Pyth `PriceUpdateV2` feeds), init a
strategy, and add the assets to 100% weight.

A one-command deploy + seed script is a planned deliverable; until then, see the Rust tests
(`programs/vault-strategy/tests/vault_strategy.rs`) for the exact sequence — the TypeScript
client mirrors it call-for-call.

## Why the IDL is hand-authored

`anchor build` emits `target/idl` + `target/types`, but no Solana/Anchor toolchain is
available here. The IDL was transcribed from the program source; its instruction/account
discriminators are `sha256("global:<ix>")` / `sha256("account:<Name>")` and are asserted by
`pnpm verify`. Once you can run `anchor build`, you may replace `src/idl/vault_strategy.json`
with the generated artifact.
19 changes: 19 additions & 0 deletions finance/vault-strategy/anchor/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="dark" />
<title>Vault Strategy — devnet</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500;600&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
37 changes: 37 additions & 0 deletions finance/vault-strategy/anchor/app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "vault-strategy-app",
"private": true,
"version": "0.1.0",
"type": "module",
"description": "Frontend for the vault-strategy Solana program (educational demo, devnet).",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"verify": "node scripts/verify-client.mjs"
},
"dependencies": {
"@coral-xyz/anchor": "^0.30.1",
"@solana/spl-token": "^0.4.9",
"@solana/wallet-adapter-base": "^0.9.23",
"@solana/wallet-adapter-react": "^0.15.35",
"@solana/wallet-adapter-react-ui": "^0.9.35",
"@solana/wallet-adapter-wallets": "^0.19.32",
"@solana/web3.js": "^1.95.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/node": "^20.14.15",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.4",
"vite": "^5.4.2",
"vite-plugin-node-polyfills": "^0.22.0"
}
}
Loading
Loading