Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
98 changes: 98 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Sage is a light wallet for the Chia blockchain built with Tauri v2 (Rust backend + React/TypeScript frontend). It supports desktop (macOS, Linux, Windows) and mobile (iOS, Android) platforms.

## Common Commands

### Frontend
```bash
pnpm dev # Vite dev server (port 1420)
pnpm tauri dev # Full app in dev mode
pnpm tauri dev --release # Dev mode with optimizations
pnpm build # Build frontend only
pnpm tauri build # Build complete application
pnpm lint # ESLint
pnpm prettier # Format code
pnpm prettier:check # Check formatting
pnpm extract # Extract i18n translations
pnpm compile # Compile i18n translations
```

### Backend (Rust)
```bash
cargo clippy --workspace --all-features --all-targets # Lint
cargo fmt --all -- --files-with-diff --check # Check formatting
cargo test -p sage-wallet # Run wallet tests (main test suite)
cargo test --workspace --all-features # Run all tests
```

### Database (requires sqlx-cli)
```bash
# Needs .env with DATABASE_URL=sqlite://./test.sqlite
sqlx db reset -y
cargo sqlx prepare --workspace
```

## Architecture

### Data Flow
```
React Frontend (src/) → IPC (tauri-specta) → Tauri Commands (src-tauri/) → sage crate → sage-wallet → sage-database (SQLite)
```

TypeScript bindings are auto-generated from Rust types via `specta`/`tauri-specta` into `src/bindings.ts`.

### Rust Workspace Crates (`crates/`)
- **sage** — Top-level orchestration, sync management
- **sage-wallet** — Core wallet logic, blockchain sync, coin drivers
- **sage-database** — SQLite via sqlx, compile-time checked queries
- **sage-api** — API definitions shared between Tauri and OpenAPI/RPC
- **sage-api/macro** — Proc macros for API generation
- **sage-keychain** — BIP39 mnemonics, AES-GCM encryption, Argon2 key derivation
- **sage-config** — TOML configuration management
- **sage-client** — RPC client
- **sage-rpc** — Axum-based RPC server
- **sage-cli** — CLI binary
- **sage-assets** — External asset fetching

### Frontend (`src/`)
- **components/ui/** — Shadcn UI components (New York style, Radix primitives)
- **pages/** — Route pages (hash router for desktop compatibility)
- **hooks/** — Custom React hooks
- **contexts/** — React contexts (Wallet, Peer, Price, Error, etc.)
- **state.ts** — Zustand global stores
- **locales/** — Lingui i18n (en-US, de-DE, zh-CN, es-MX)
- **themes/** — CSS variable-based theming with light/dark mode

### Key Patterns
- **State**: Zustand for global state, React Context for scoped state
- **Forms**: react-hook-form + zod validation
- **Tables**: @tanstack/react-table
- **i18n**: Lingui with PO format (extract → compile workflow)
- **Rust errors**: `thiserror` custom error types
- **Async**: Tokio runtime, Arc+Mutex for shared state, MPSC channels for sync events
- **Chia SDK**: `chia-wallet-sdk` v0.33.0 is the primary blockchain dependency

## Code Style

### Rust
- Edition 2024, toolchain 1.89.0
- `unsafe_code = "deny"` workspace-wide
- Strict clippy: `all = deny`, `pedantic = warn`
- Rustfmt with edition 2024 settings

### Frontend
- TypeScript strict mode, ESM modules
- pnpm (v10.13.1) as package manager
- Tailwind CSS for styling
- Path alias: `@/*` → `./src/*`

## Platform-Specific
- **src-tauri/** — Tauri wrapper and entry point
- **tauri-plugin-sage/** — Custom native plugin (iOS/Android platform code)
- Mobile uses conditional compilation (`cfg(mobile)`)
- Windows build requires CMake, Clang, NASM
1 change: 1 addition & 0 deletions crates/sage-api/endpoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"resync": true,
"generate_mnemonic": false,
"import_key": true,
"import_addresses": true,
"delete_key": false,
"delete_database": false,
"rename_key": false,
Expand Down
2 changes: 2 additions & 0 deletions crates/sage-api/src/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod pending_transaction;
mod token;
mod transaction;
mod transaction_summary;
mod wallet;

pub use coin::*;
pub use derivation::*;
Expand All @@ -25,3 +26,4 @@ pub use pending_transaction::*;
pub use token::*;
pub use transaction::*;
pub use transaction_summary::*;
pub use wallet::*;
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,30 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct KeyInfo {
pub struct WalletRecord {
pub name: String,
pub fingerprint: u32,
pub public_key: String,
pub kind: KeyKind,
pub has_secrets: bool,
#[serde(flatten)]
pub kind: WalletKind,
pub network_id: String,
pub emoji: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum KeyKind {
Bls,
#[serde(tag = "type", rename_all = "snake_case")]
pub enum WalletKind {
Bls {
public_key: String,
has_secrets: bool,
},
Vault {
launcher_id: String,
},
Watch {
addresses: Vec<String>,
},
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
50 changes: 45 additions & 5 deletions crates/sage-api/src/requests/keys.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};

use crate::{KeyInfo, SecretKeyInfo};
use crate::{SecretKeyInfo, WalletRecord};

/// Login to a wallet using a fingerprint
#[cfg_attr(
Expand Down Expand Up @@ -197,6 +197,46 @@ pub struct ImportKeyResponse {
pub fingerprint: u32,
}

/// Import a read-only wallet using a list of addresses. Optionally logs in.
#[cfg_attr(
feature = "openapi",
crate::openapi_attr(
tag = "Authentication & Keys",
description = "Import a read-only wallet using a list of addresses. Optionally logs in."
)
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ImportAddresses {
/// Display name for the wallet
pub name: String,
/// List of addresses
pub addresses: Vec<String>,
/// Whether to automatically login after import
#[serde(default = "yes")]
#[cfg_attr(feature = "openapi", schema(default = true))]
pub login: bool,
/// Optional emoji identifier
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub emoji: Option<String>,
}

/// Response with imported wallet fingerprint
#[cfg_attr(
feature = "openapi",
crate::openapi_attr(tag = "Authentication & Keys")
)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ImportAddressesResponse {
/// Fingerprint of the imported wallet
#[cfg_attr(feature = "openapi", schema(example = 1_234_567_890))]
pub fingerprint: u32,
}

/// Delete a wallet database
#[cfg_attr(
feature = "openapi",
Expand Down Expand Up @@ -331,8 +371,8 @@ pub struct GetKeys {}
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct GetKeysResponse {
/// List of wallet keys
pub keys: Vec<KeyInfo>,
/// List of wallet records
pub keys: Vec<WalletRecord>,
}

/// Get a specific wallet key
Expand Down Expand Up @@ -362,9 +402,9 @@ pub struct GetKey {
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct GetKeyResponse {
/// Key information if found
/// Wallet record if found
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub key: Option<KeyInfo>,
pub key: Option<WalletRecord>,
}

/// Get wallet secret key
Expand Down
2 changes: 0 additions & 2 deletions crates/sage-api/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ mod address_kind;
mod amount;
mod asset;
mod error_kind;
mod key_info;
mod unit;

pub use address_kind::*;
pub use amount::*;
pub use asset::*;
pub use error_kind::*;
pub use key_info::*;
pub use unit::*;
Loading
Loading