diff --git a/README.md b/README.md index a85da49a..8fc9573f 100644 --- a/README.md +++ b/README.md @@ -35,34 +35,44 @@ For a comprehensive overview of the smart contract architecture, module responsi ## Getting Started -**For complete local development setup with service dependencies, startup order, and troubleshooting, see:** +**For complete local development setup, startup order, and troubleshooting, see:** -- **[Local Development Quickstart](./docs/LOCAL_DEVELOPMENT_QUICKSTART.md)** – Complete setup guide with step-by-step instructions -- **[Service Dependency Matrix](./docs/SERVICE_DEPENDENCY_MATRIX.md)** – Visual dependency graph and service specifications +- **[Local Development Quickstart](./docs/LOCAL_DEVELOPMENT_QUICKSTART.md)** - Backend, contracts, and frontend bootstrap +- **[Service Dependency Matrix](./docs/SERVICE_DEPENDENCY_MATRIX.md)** - Service relationships and operational notes -### Quick Start (5 minutes) +### Quick Start -1. **Start infrastructure** (PostgreSQL + Redis): +1. Start the backend: ```bash - docker-compose up -d postgres redis + cd backend + cp .env.example .env + npm install + npx prisma migrate dev + npm run dev ``` -2. **Start backend API** (in one terminal): +2. Start the frontend in a second terminal: ```bash - cd backend && npm install && npx prisma migrate dev && npm run dev + cd frontend + cp .env.example .env + npm install + npm run dev ``` -3. **Start frontend** (in another terminal): +3. Optional: run contract tests from the repo root: ```bash - cd frontend && npm install && npm run dev + rustup target add wasm32-unknown-unknown + cargo test ``` -4. **Open browser**: http://localhost:5173 +4. Open the app at `http://localhost:5173` + +The default local workflow uses Prisma's SQLite database and in-memory fallbacks for Redis-backed features unless you explicitly configure additional infrastructure. -For detailed setup instructions, prerequisites, and troubleshooting, see **[Local Development Quickstart](./docs/LOCAL_DEVELOPMENT_QUICKSTART.md)**. +For detailed setup instructions, prerequisites, validation steps, and troubleshooting, see **[Local Development Quickstart](./docs/LOCAL_DEVELOPMENT_QUICKSTART.md)**. For a complete environment variable reference with defaults, required flags, and production recommendations, see **[Environment Variable Matrix](./docs/ENV_VARIABLE_MATRIX.md)**. diff --git a/backend/README.md b/backend/README.md index 05e42b30..5aa6b7e2 100644 --- a/backend/README.md +++ b/backend/README.md @@ -25,6 +25,9 @@ npm install # Create environment file cp .env.example .env + +# Create/update the local Prisma database +npx prisma migrate dev ``` ### Development @@ -36,6 +39,23 @@ npm run dev The server will start on `http://localhost:3000`. +For the default local workflow, PostgreSQL and Redis are optional: + +- Prisma uses the SQLite datasource in [`prisma/schema.prisma`](/Users/macbook/stellar/YieldVault-RWA/backend/prisma/schema.prisma:1) when `DATABASE_URL` is not set. +- Redis-backed features fall back to in-memory behavior when `REDIS_URL` is not configured. + +Minimum local environment values: + +```env +PORT=3000 +NODE_ENV=development +STELLAR_RPC_URL=https://soroban-testnet.stellar.org +STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 +VAULT_CONTRACT_ID= +``` + +For the full monorepo bootstrap order, see [`docs/LOCAL_DEVELOPMENT_QUICKSTART.md`](/Users/macbook/stellar/YieldVault-RWA/docs/LOCAL_DEVELOPMENT_QUICKSTART.md:1). + ### Production ```bash diff --git a/backend/src/aws-sdk-client-s3-shim.d.ts b/backend/src/aws-sdk-client-s3-shim.d.ts new file mode 100644 index 00000000..58dfc6de --- /dev/null +++ b/backend/src/aws-sdk-client-s3-shim.d.ts @@ -0,0 +1,28 @@ +declare module '@aws-sdk/client-s3' { + export interface S3ClientConfig { + region?: string; + endpoint?: string; + forcePathStyle?: boolean; + credentials?: { + accessKeyId: string; + secretAccessKey: string; + }; + } + + export class S3Client { + constructor(config?: S3ClientConfig); + send(command: unknown): Promise; + } + + export class PutObjectCommand { + constructor(input?: unknown); + } + + export class ListObjectsV2Command { + constructor(input?: unknown); + } + + export class DeleteObjectCommand { + constructor(input?: unknown); + } +} diff --git a/backend/src/dbBackupJob.ts b/backend/src/dbBackupJob.ts index 493d4964..ec20d480 100644 --- a/backend/src/dbBackupJob.ts +++ b/backend/src/dbBackupJob.ts @@ -46,6 +46,14 @@ export interface BackupResult { deletedCount: number; } +interface S3ListObjectsResponse { + Contents?: Array<{ + Key?: string; + LastModified?: Date; + }>; + NextContinuationToken?: string; +} + // ─── Config helpers ─────────────────────────────────────────────────────────── function getRetentionDays(): number { @@ -156,13 +164,13 @@ export async function pruneOldBackups(): Promise { let continuationToken: string | undefined; do { - const listResp = await client.send( + const listResp = (await client.send( new ListObjectsV2Command({ Bucket: bucket, Prefix: getS3Prefix(), ...(continuationToken ? { ContinuationToken: continuationToken } : {}), }), - ); + )) as S3ListObjectsResponse; for (const obj of listResp.Contents ?? []) { if (obj.LastModified && obj.Key && obj.LastModified < cutoff) { diff --git a/backend/src/middleware/validate.ts b/backend/src/middleware/validate.ts index a0ed65aa..5939c811 100644 --- a/backend/src/middleware/validate.ts +++ b/backend/src/middleware/validate.ts @@ -122,9 +122,7 @@ export const WebhookRegisterSchema = z { message: 'url must be a valid http or https URL' }, ), eventTypes: z - .array(z.enum(WEBHOOK_EVENT_TYPES), { - invalid_type_error: 'eventTypes must be an array of valid event type strings', - }) + .array(z.enum(WEBHOOK_EVENT_TYPES)) .min(1, 'eventTypes must contain at least one event type') .optional(), enabled: z.boolean().optional(), diff --git a/backend/src/sorobanBatchClient.ts b/backend/src/sorobanBatchClient.ts index 1d361566..6f7dba66 100644 --- a/backend/src/sorobanBatchClient.ts +++ b/backend/src/sorobanBatchClient.ts @@ -54,6 +54,12 @@ export interface BatchClientOptions { maxConcurrency?: number; } +interface SimulationSourceAccount { + accountId(): string; + sequenceNumber(): string; + incrementSequenceNumber(): void; +} + // ── Semaphore ───────────────────────────────────────────────────────────────── /** @@ -124,11 +130,11 @@ export const defaultRpcReader: RpcReader = async ( // Build a dummy source account for simulation (sequence=0, no real funds needed) const dummyKeypair = Keypair.random(); - const sourceAccount = { + const sourceAccount: SimulationSourceAccount = { accountId: () => dummyKeypair.publicKey(), sequenceNumber: () => '0', incrementSequenceNumber: () => {}, - } as Parameters[0]; + }; const scArgs = (args ?? []).map((a) => nativeToScVal(a as Parameters[0]), @@ -150,7 +156,7 @@ export const defaultRpcReader: RpcReader = async ( } // Return the raw result for the caller to decode - return (sim as rpc.Api.SimulateTransactionSuccessResponse).result?.retval ?? null; + return (sim as { result?: { retval?: unknown } }).result?.retval ?? null; }; // ── SorobanBatchClient ──────────────────────────────────────────────────────── diff --git a/backend/src/stellar-sdk-shim.d.ts b/backend/src/stellar-sdk-shim.d.ts index bc2105eb..c96d89bc 100644 --- a/backend/src/stellar-sdk-shim.d.ts +++ b/backend/src/stellar-sdk-shim.d.ts @@ -1,8 +1,15 @@ declare module '@stellar/stellar-sdk' { + export interface Account { + accountId(): string; + sequenceNumber(): string; + incrementSequenceNumber(): void; + } + export const BASE_FEE: string; export class Keypair { static fromSecret(secret: string): Keypair; + static random(): Keypair; publicKey(): string; sign(data: Buffer): Buffer; } @@ -21,6 +28,11 @@ declare module '@stellar/stellar-sdk' { namespace Api { function isSimulationError(input: unknown): boolean; function isSimulationRestore(input: unknown): boolean; + interface SimulateTransactionSuccessResponse { + result?: { + retval?: unknown; + }; + } } function assembleTransaction(tx: unknown, sim: unknown): { build(): { sign(kp: Keypair): void } }; @@ -41,7 +53,7 @@ declare module '@stellar/stellar-sdk' { } export class TransactionBuilder { - constructor(source: unknown, opts: unknown); + constructor(source: Account, opts: unknown); addOperation(op: unknown): TransactionBuilder; setTimeout(timeout: number): TransactionBuilder; build(): unknown; diff --git a/backend/src/webhookDelivery.ts b/backend/src/webhookDelivery.ts index b560b839..760a7cbd 100644 --- a/backend/src/webhookDelivery.ts +++ b/backend/src/webhookDelivery.ts @@ -5,6 +5,8 @@ export type TransactionEventType = | 'transaction.deposit.created' | 'transaction.withdrawal.created'; +export const WEBHOOK_SCHEMA_VERSION = 1; + export interface TransactionEventPayload { transactionId: string; amount: string; @@ -521,6 +523,7 @@ export function createWebhookSignature(secret: string, payload: unknown): string } export interface WebhookSignedEnvelope { + schemaVersion: number; eventType: TransactionEventType; sentAt: string; payload: TransactionEventPayload; @@ -566,6 +569,7 @@ export function buildWebhookSignedEnvelope( payload: TransactionEventPayload, ): WebhookSignedEnvelope { return { + schemaVersion: WEBHOOK_SCHEMA_VERSION, eventType: delivery.eventType, sentAt: new Date().toISOString(), payload, diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 1fbae6de..5d9c6626 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "target": "ES2020", "module": "commonjs", + "moduleResolution": "node", "lib": ["ES2020", "DOM"], "outDir": "./dist", "rootDir": "./src", diff --git a/docs/LOCAL_DEVELOPMENT_QUICKSTART.md b/docs/LOCAL_DEVELOPMENT_QUICKSTART.md index dea46e57..ae072bdc 100644 --- a/docs/LOCAL_DEVELOPMENT_QUICKSTART.md +++ b/docs/LOCAL_DEVELOPMENT_QUICKSTART.md @@ -1,682 +1,197 @@ -# Local Development Quickstart Guide - -This guide provides a complete walkthrough for setting up YieldVault RWA for local development, including service dependencies, startup order, and common troubleshooting steps. - -## Service Dependency Matrix - -```mermaid -graph TD - A[Node.js 18+] --> B[Backend API] - A --> C[Frontend] - D[PostgreSQL] --> B - E[Redis] --> B - F[Rust/Cargo] --> G[Smart Contracts] - B --> H[Stellar Testnet RPC] - C --> H - C --> B - G --> H -``` - -### Services Overview - -| Service | Purpose | Default Port | Status Check | Dependency | -| ------------------- | ----------------------- | ------------ | ----------------------------------- | ------------------------------ | -| **PostgreSQL** | Data persistence | 5432 | `psql -c "SELECT 1"` | None (external or Docker) | -| **Redis** | Caching & rate limiting | 6379 | `redis-cli ping` | Backend | -| **Backend API** | Express.js REST API | 3000 | `curl http://localhost:3000/health` | PostgreSQL, Redis, Stellar RPC | -| **Frontend** | React + Vite UI | 5173 | `http://localhost:5173` | Backend API, Stellar Testnet | -| **Smart Contracts** | Soroban Rust contracts | N/A | Build succeeds | Cargo + wasm32 target | -| **Stellar RPC** | External service | N/A | Handled by SDK | None (external) | - -## Prerequisites - -Before starting, ensure you have the following installed: - -### Core Requirements - -- **Node.js 18+** – Check with `node --version` -- **npm** or **pnpm** – Check with `npm --version` or `pnpm --version` -- **Git** – For version control -- **Rust 1.74+** – Check with `rustc --version` (needed for smart contracts) -- **Docker & Docker Compose** – For PostgreSQL and Redis (or install them separately) +# Local Development Quickstart -### Optional Tools +This guide documents the fastest supported way to boot the repository locally for backend, contract, and frontend development. -- **Stellar CLI** – For contract deployments -- **Foundry** – For advanced testing (optional) -- **VS Code** – Recommended editor with Rust Analyzer extension +## What You Need -### System-Specific Installation +- Node.js 18+ and npm +- Rust and Cargo +- `wasm32-unknown-unknown` Rust target for Soroban contract builds -#### Windows +Optional: -```powershell -# Install Node.js from https://nodejs.org (LTS recommended) -# Install Git from https://git-scm.com +- Freighter or another Stellar wallet for frontend testing +- Docker, PostgreSQL, and Redis only if you want to test non-default infrastructure paths -# Install Rust using rustup-init.exe (included in repo): -./rustup-init.exe -y +## Repo Layout -# Add wasm32 target -rustc target add wasm32-unknown-unknown +- `backend/` - Express + TypeScript API with Prisma +- `contracts/vault/` - main Soroban vault contract +- `contracts/mock-strategy/` - mock contract used by tests +- `frontend/` - React + Vite app -# Install Docker Desktop from https://www.docker.com/products/docker-desktop -``` +## Bootstrap Order -#### macOS +Use this order for a clean first boot: -```bash -# Install Homebrew if not already installed -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +1. Install backend dependencies and create the local database +2. Build or test contracts if you are changing on-chain code +3. Install frontend dependencies and point it at your local backend +4. Start backend and frontend in separate terminals -# Install dependencies -brew install node@18 git rustup docker +## 1. Backend Bootstrap -# Install Rust -rustup-init -rustup target add wasm32-unknown-unknown - -# Start Docker Desktop (from Applications folder) -``` - -#### Linux (Ubuntu/Debian) - -```bash -# Install Node.js -curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - -sudo apt-get install -y nodejs git - -# Install Rust -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source "$HOME/.cargo/env" -rustup target add wasm32-unknown-unknown - -# Install Docker -curl -fsSL https://get.docker.com -o get-docker.sh -sudo sh get-docker.sh -sudo usermod -aG docker $USER -``` - -## Local Setup Order - -Follow these steps in order to ensure all dependencies are properly initialized: - -### Step 1: Clone Repository - -```bash -git clone https://github.com/your-org/YieldVault-RWA.git -cd YieldVault-RWA -``` - -### Step 2: Start Infrastructure Services - -Start PostgreSQL and Redis first (they have no dependencies). - -#### Using Docker Compose - -```bash -# Start PostgreSQL and Redis containers -docker-compose up -d postgres redis - -# Verify services are running -docker ps - -# Expected output should show both 'postgres' and 'redis' containers -``` - -#### Or Manual Installation - -If Docker is not available, install and run services separately: - -```bash -# PostgreSQL -# Install from https://www.postgresql.org/download -# Run: postgres -D /usr/local/var/postgres - -# Redis -# Install from https://redis.io/download -# Run: redis-server -``` - -**Verify PostgreSQL:** - -```bash -psql -U postgres -d postgres -c "SELECT version();" -``` - -**Verify Redis:** - -```bash -redis-cli ping -# Expected: PONG -``` - -### Step 3: Setup Backend +The default local backend path does not require PostgreSQL or Redis. Prisma falls back to the SQLite database defined in [`backend/prisma/schema.prisma`](/Users/macbook/stellar/YieldVault-RWA/backend/prisma/schema.prisma:1), and Redis-backed features fall back to in-memory behavior when `REDIS_URL` is not set. ```bash cd backend - -# Copy environment template -cp .env.local.example .env.local - -# Install dependencies +cp .env.example .env npm install - -# Initialize database npx prisma migrate dev - -# Verify database is ready -npm run db:check-drift - -# Start development server npm run dev - -# In another terminal, verify health check -curl http://localhost:3000/health -``` - -**Expected output from health check:** - -```json -{ - "status": "healthy", - "checks": { - "api": "up", - "cache": "up", - "stellarRpc": "up" - } -} -``` - -### Step 4: Setup Frontend - -```bash -cd ../frontend - -# Copy environment template -cp .env.local.example .env.local - -# Install dependencies -npm install - -# Start development server -npm run dev - -# Open in browser: http://localhost:5173 ``` -### Step 5: Setup Smart Contracts (Optional) +Backend defaults: -Only needed if you plan to modify contracts: +- API base URL: `http://localhost:3000` +- Health endpoint: `http://localhost:3000/health` +- Readiness endpoint: `http://localhost:3000/ready` +- Local Prisma DB: `backend/prisma/dev.db` -```bash -cd ../contracts/vault - -# Install Rust dependencies (auto on first build) -cargo build --target wasm32-unknown-unknown --release - -# Run contract tests -cargo test - -# View generated docs -cargo doc --open -``` - -## Complete Startup Sequence - -Once everything is set up, here's the recommended startup order for future development sessions: - -### Terminal 1: Infrastructure - -```bash -docker-compose up -d postgres redis -# Wait 5-10 seconds for services to be ready -``` - -### Terminal 2: Backend API - -```bash -cd backend -npm run dev -# Wait for "Server running on port 3000" message -``` - -### Terminal 3: Frontend - -```bash -cd frontend -npm run dev -# Wait for "Local: http://localhost:5173" message -``` - -### Terminal 4: Optional - Contract Development - -```bash -cd contracts/vault -cargo watch -x "test --target wasm32-unknown-unknown" -``` - -## Environment Configuration - -### Backend Environment Variables - -Create `backend/.env.local`: +Recommended minimum local env updates in `backend/.env`: ```env -# Server PORT=3000 NODE_ENV=development - -# Database (must match Docker Compose or local installation) -DATABASE_URL=postgresql://postgres:postgres@localhost:5432/yieldvault_dev - -# Stellar Network STELLAR_RPC_URL=https://soroban-testnet.stellar.org STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 -STELLAR_NETWORK=testnet -VAULT_CONTRACT_ID=your_testnet_contract_id_here - -# Cache -REDIS_URL=redis://localhost:6379 - -# API Configuration -RATE_LIMIT_WINDOW_MS=900000 -RATE_LIMIT_MAX_REQUESTS=100 - -# Optional - Log verbosity -LOG_LEVEL=debug -``` - -### Frontend Environment Variables - -Create `frontend/.env.local`: - -```env -# Vite configuration -VITE_API_BASE_URL=http://localhost:3000 - -# Stellar Network -VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org -VITE_STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 - -# Contract -VITE_VAULT_CONTRACT_ID=your_testnet_contract_id_here - -# Optional - Analytics & Error Tracking -VITE_FF_DEBUG_MODE=true +VAULT_CONTRACT_ID= ``` -## Health Checks - -After all services are started, verify everything is working: - -```bash -# Backend API health -curl http://localhost:3000/health - -# Backend readiness -curl http://localhost:3000/ready - -# Frontend (should see HTML) -curl -I http://localhost:5173 - -# Database connection -cd backend && npm run db:check-drift - -# Redis connectivity -redis-cli ping -``` +Notes: -## Troubleshooting Guide +- Leave `DATABASE_URL` unset to keep the default SQLite workflow. +- Leave `REDIS_URL` unset unless you are explicitly testing Redis-backed rate limiting or nonce storage. +- Routes that invoke Soroban transactions need a real `VAULT_CONTRACT_ID`, and some flows also require backend signing credentials such as `STELLAR_SECRET_KEY`. -### PostgreSQL Connection Issues +## 2. Contract Bootstrap -**Problem:** `Error: connect ECONNREFUSED 127.0.0.1:5432` +You only need this section if you are working on the smart contracts. -**Solutions:** +From the repo root: ```bash -# Check if PostgreSQL is running -docker ps | grep postgres - -# If not running, start it: -docker-compose up -d postgres - -# Verify connection string in .env.local -# Default: postgresql://postgres:postgres@localhost:5432/yieldvault_dev - -# Check PostgreSQL logs -docker logs yieldvault_rwa-postgres-1 - -# Test connection manually -psql -U postgres -d yieldvault_dev -h localhost -``` - -### Redis Connection Issues - -**Problem:** `Error: connect ECONNREFUSED 127.0.0.1:6379` - -**Solutions:** - -```bash -# Check if Redis is running -docker ps | grep redis - -# If not running, start it: -docker-compose up -d redis - -# Verify connection -redis-cli ping # Should return: PONG - -# Check Redis logs -docker logs yieldvault_rwa-redis-1 - -# Check REDIS_URL in backend .env.local -# Default: redis://localhost:6379 +rustup target add wasm32-unknown-unknown +cargo test ``` -### Database Migration Failures - -**Problem:** `Error: P1000 Authentication failed` or migration errors - -**Solutions:** +To build the main contract artifact directly: ```bash -# Reset database (WARNING: Loses all data) -cd backend -npx prisma migrate reset --force - -# Or manually drop and recreate -psql -U postgres -h localhost -c "DROP DATABASE yieldvault_dev;" -psql -U postgres -h localhost -c "CREATE DATABASE yieldvault_dev;" -npx prisma migrate deploy +cargo build -p vault --target wasm32-unknown-unknown --release ``` -### Backend Won't Start - -**Problem:** `Port 3000 already in use` or other startup errors - -**Solutions:** - -```bash -# Check what's using port 3000 -# On Windows: -netstat -ano | findstr :3000 - -# On macOS/Linux: -lsof -i :3000 - -# Kill the process if needed (Windows): -taskkill /PID /F +Useful paths: -# Or use different port: -PORT=3001 npm run dev -``` - -### Frontend Build Issues +- Main contract crate: [`contracts/vault`](/Users/macbook/stellar/YieldVault-RWA/contracts/vault) +- Mock strategy crate: [`contracts/mock-strategy`](/Users/macbook/stellar/YieldVault-RWA/contracts/mock-strategy) +- Deployment notes: [`contracts/vault/DEPLOYMENT.md`](/Users/macbook/stellar/YieldVault-RWA/contracts/vault/DEPLOYMENT.md:1) -**Problem:** `node_modules issues` or build failures +## 3. Frontend Bootstrap -**Solutions:** +The frontend expects a local backend plus Stellar network settings. ```bash cd frontend - -# Clear node_modules and cache -rm -rf node_modules package-lock.json -npm install - -# Clear Vite cache -rm -rf node_modules/.vite - -# Reinstall +cp .env.example .env npm install npm run dev ``` -### Stellar RPC Connection Issues - -**Problem:** `Error: Network request failed` or `Stellar RPC timeout` +Recommended minimum local env in `frontend/.env`: -**Solutions:** - -```bash -# Test RPC endpoint directly -curl https://soroban-testnet.stellar.org/health - -# Check your VITE_SOROBAN_RPC_URL in frontend/.env.local -# Check STELLAR_RPC_URL in backend/.env.local - -# If testnet is down, try using soroban cli: -soroban network list-known - -# Use a different RPC if available -VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org npm run dev +```env +VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org +VITE_STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 +VITE_VAULT_CONTRACT_ID= ``` -### Docker Issues +Frontend default: -**Problem:** `docker: command not found` or `permission denied` +- App URL: `http://localhost:5173` -**Solutions:** +Important: -```bash -# Verify Docker is installed and running -docker --version -docker ps - -# On Linux, add user to docker group: -sudo usermod -aG docker $USER -newgrp docker - -# Restart Docker service if needed: -# Windows: Restart Docker Desktop -# macOS: Restart Docker Desktop -# Linux: sudo systemctl restart docker -``` +- Set `VITE_VAULT_CONTRACT_ID` to the same contract ID used by the backend when you want the UI to target a deployed vault. +- Some views can still boot without a contract ID, but transaction flows will not work end-to-end. -### Dependency Version Conflicts +## Daily Startup -**Problem:** `npm ERR! peer dep missing` or conflicting versions +Once dependencies are installed, the normal dev loop is: -**Solutions:** +Terminal 1: ```bash -# Use exact versions from lock file -rm -rf node_modules -npm ci # Use this instead of npm install - -# Update all dependencies carefully -npm audit fix - -# For backend/frontend separately: -cd backend && npm ci -cd ../frontend && npm ci +cd backend +npm run dev ``` -### "Module not found" Errors - -**Problem:** `Cannot find module '@stellar/stellar-sdk'` or similar - -**Solutions:** +Terminal 2: ```bash -# Reinstall all dependencies -npm install - -# For monorepo issues, install at project root too: -cd ../.. && npm install -cd frontend && npm install - -# Clear npm cache -npm cache clean --force -npm install +cd frontend +npm run dev ``` -### Contract Build Failures - -**Problem:** `error: could not compile wasm artifact` - -**Solutions:** +Optional Terminal 3 for contract work: ```bash -cd contracts/vault - -# Check Rust version -rustc --version # Should be 1.74 or higher - -# Update Rust -rustup update - -# Ensure wasm32 target is installed -rustup target add wasm32-unknown-unknown - -# Clean and rebuild -cargo clean -cargo build --target wasm32-unknown-unknown --release - -# Check for compile errors -cargo check +cargo test ``` -## Development Workflow +## Validation Checklist -### Running Tests +Use these commands after bootstrapping: ```bash -# Backend unit tests cd backend -npm run test - -# Frontend unit tests -cd ../frontend -npm run test - -# E2E tests -npm run test:e2e - -# Contract tests -cd ../contracts/vault -cargo test +npm test ``` -### Code Quality - ```bash -# Lint all code -cd backend && npm run lint -cd ../frontend && npm run lint - -# Format code -cd backend && npm run format -cd ../frontend && npm run format - -# Security audit -cd backend && npm audit -cd ../frontend && npm audit +cd frontend +npm run test:run ``` -### Database Changes - ```bash -# Create new migration -cd backend -npx prisma migrate dev --name - -# Generate Prisma client after schema changes -npx prisma generate - -# View database in Prisma Studio -npx prisma studio +cd /Users/macbook/stellar/YieldVault-RWA +cargo test ``` -## Performance Optimization - -### Local Development Tips - -1. **Use `npm ci` instead of `npm install`** – Faster and more reproducible -2. **Keep docker containers running** – Don't stop/start them repeatedly -3. **Enable source maps for debugging** – Already enabled in dev config -4. **Use VS Code extensions** – Prettier, ESLint, Rust Analyzer for better DX -5. **Monitor ports** – Keep HTTP/2 enabled for Vite for faster reload +Manual checks: -### Memory Management +- Open `http://localhost:5173` +- Verify `http://localhost:3000/health` returns a healthy response +- Confirm the frontend can reach the backend without CORS errors -If experiencing memory issues: +## Troubleshooting -```bash -# Backend with more memory -NODE_OPTIONS="--max-old-space-size=4096" npm run dev +### `VAULT_CONTRACT_ID environment variable is not set` -# Frontend with more memory -NODE_OPTIONS="--max-old-space-size=2048" npm run dev -``` +Set the contract ID in both `backend/.env` and `frontend/.env` before testing real vault actions. -## Common Development Tasks +### Prisma migration or DB issues -### Accessing Swagger API Docs +Re-run: ```bash -# Docs available at: -# http://localhost:3000/api-docs -``` - -### Viewing Database - -```bash -# Open Prisma Studio cd backend -npx prisma studio - -# Opens http://localhost:5555 with database browser -``` - -### Testing Webhook Events - -```bash -# Backend includes test endpoints: -# POST http://localhost:3000/admin/test-webhook +npx prisma migrate dev ``` -### Debugging with VS Code - -1. Install **Debugger for Chrome** extension -2. Create `.vscode/launch.json`: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Backend", - "skipFiles": ["/**"], - "program": "${workspaceFolder}/backend/src/index.ts", - "preLaunchTask": "npm: dev" - } - ] -} -``` +If you want a clean local SQLite reset, remove `backend/prisma/dev.db` and rerun the migration. -## Additional Resources +### Redis warnings in backend logs -- **Architecture Overview** – See [docs/CONTRACTS_ARCHITECTURE.md](./CONTRACTS_ARCHITECTURE.md) -- **Environment Setup** – See [ENVIRONMENT_SETUP_GUIDE.md](../ENVIRONMENT_SETUP_GUIDE.md) -- **API Documentation** – See [docs/api/README.md](./api/README.md) -- **Contributing Guide** – See [CONTRIBUTING.md](../CONTRIBUTING.md) -- **Stellar Documentation** – https://developers.stellar.org/ -- **Soroban Documentation** – https://developers.stellar.org/docs/build/smart-contracts +Expected in the default local path. Redis is optional unless you are specifically testing Redis-backed behavior. -## Getting Help +### Frontend points at the wrong backend -- **Check logs** – Always the first troubleshooting step -- **Search issues** – Check GitHub issues for similar problems -- **Review documentation** – Most common issues are covered above -- **Ask in discussions** – Create a new discussion for help +Check backend port `3000`, then verify any frontend API configuration in the app matches your local backend URL. ---- +## Related Docs -**Last Updated:** May 2026 -**Maintained by:** Development Team -**Version:** 1.0.0 +- Root overview: [`README.md`](/Users/macbook/stellar/YieldVault-RWA/README.md:1) +- Backend details: [`backend/README.md`](/Users/macbook/stellar/YieldVault-RWA/backend/README.md:1) +- Frontend details: [`frontend/README.md`](/Users/macbook/stellar/YieldVault-RWA/frontend/README.md:1) +- Environment matrix: [`docs/ENV_VARIABLE_MATRIX.md`](/Users/macbook/stellar/YieldVault-RWA/docs/ENV_VARIABLE_MATRIX.md:1) diff --git a/frontend/README.md b/frontend/README.md index d2e77611..5da3cb36 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,73 +1,45 @@ -# React + TypeScript + Vite +# YieldVault Frontend -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +React + TypeScript + Vite frontend for the YieldVault RWA application. -Currently, two official plugins are available: +## Local Development -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +```bash +cp .env.example .env +npm install +npm run dev +``` -## React Compiler +Default local URL: -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). +- `http://localhost:5173` -## Expanding the ESLint configuration +Minimum local environment: -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: +```env +VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org +VITE_STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015 +VITE_VAULT_CONTRACT_ID= +``` -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... +Notes: - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, +- Set `VITE_VAULT_CONTRACT_ID` before testing contract-backed UI flows. +- The frontend is intended to run alongside the local backend in `../backend`. +- For the full repo bootstrap order, see [`docs/LOCAL_DEVELOPMENT_QUICKSTART.md`](/Users/macbook/stellar/YieldVault-RWA/docs/LOCAL_DEVELOPMENT_QUICKSTART.md:1). - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` +## Scripts -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: +```bash +npm run dev +npm run build +npm run lint +npm run test:run +npm run test:e2e +``` -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +## Related Docs -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` +- API docs output: [`docs/api/frontend`](/Users/macbook/stellar/YieldVault-RWA/docs/api/frontend) +- Sentry notes: [`SENTRY_GUIDE.md`](/Users/macbook/stellar/YieldVault-RWA/frontend/SENTRY_GUIDE.md:1) +- Security patterns: [`SECURITY_PATTERNS.md`](/Users/macbook/stellar/YieldVault-RWA/frontend/SECURITY_PATTERNS.md:1) diff --git a/packages/api-schemas/src/index.d.ts b/packages/api-schemas/src/index.d.ts new file mode 100644 index 00000000..42176e51 --- /dev/null +++ b/packages/api-schemas/src/index.d.ts @@ -0,0 +1,6 @@ +export { StellarAddressSchema, AmountSchema, AmountInputSchema, ShareCountSchema, AssetCodeSchema, IsoDatestamp, SlippageBpsSchema, } from "./primitives"; +export { DepositRequestSchema, WithdrawalRequestSchema, VaultHistoryQuerySchema, PortfolioQuerySchema, WalletAddressSchema, TransactionQuerySchema, type DepositRequest, type WithdrawalRequest, type VaultHistoryQuery, type PortfolioQuery, type WalletAddressParam, type TransactionQuery, type TransactionQueryInput, } from "./requests"; +export { VaultOperationResponseSchema, type VaultOperationResponse, } from "./responses"; +export { VaultDepositBodySchema, VaultWithdrawalBodySchema, SignedVaultDepositBodySchema, SignedVaultWithdrawalBodySchema, VaultOperationSchema, type VaultDepositBody, type VaultWithdrawalBody, } from "./vault"; +export { WEBHOOK_SCHEMA_VERSION, WebhookEventTypeSchema, WebhookEventPayloadSchemas, WebhookEnvelopeSchema, TransactionDepositCreatedPayloadSchema, TransactionWithdrawalCreatedPayloadSchema, parseWebhookEnvelope, type WebhookEventType, type WebhookEnvelope, type TransactionDepositCreatedPayload, type TransactionWithdrawalCreatedPayload, } from "./webhookEvents"; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/api-schemas/src/index.d.ts.map b/packages/api-schemas/src/index.d.ts.map new file mode 100644 index 00000000..ed8da5c2 --- /dev/null +++ b/packages/api-schemas/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,4BAA4B,EAC5B,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,4BAA4B,EAC5B,+BAA+B,EAC/B,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACrB,sCAAsC,EACtC,yCAAyC,EACzC,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,gCAAgC,EACrC,KAAK,mCAAmC,GACzC,MAAM,iBAAiB,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/index.js b/packages/api-schemas/src/index.js new file mode 100644 index 00000000..a0e0e01a --- /dev/null +++ b/packages/api-schemas/src/index.js @@ -0,0 +1,35 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseWebhookEnvelope = exports.TransactionWithdrawalCreatedPayloadSchema = exports.TransactionDepositCreatedPayloadSchema = exports.WebhookEnvelopeSchema = exports.WebhookEventPayloadSchemas = exports.WebhookEventTypeSchema = exports.WEBHOOK_SCHEMA_VERSION = exports.VaultOperationSchema = exports.SignedVaultWithdrawalBodySchema = exports.SignedVaultDepositBodySchema = exports.VaultWithdrawalBodySchema = exports.VaultDepositBodySchema = exports.VaultOperationResponseSchema = exports.TransactionQuerySchema = exports.WalletAddressSchema = exports.PortfolioQuerySchema = exports.VaultHistoryQuerySchema = exports.WithdrawalRequestSchema = exports.DepositRequestSchema = exports.SlippageBpsSchema = exports.IsoDatestamp = exports.AssetCodeSchema = exports.ShareCountSchema = exports.AmountInputSchema = exports.AmountSchema = exports.StellarAddressSchema = void 0; +var primitives_1 = require("./primitives"); +Object.defineProperty(exports, "StellarAddressSchema", { enumerable: true, get: function () { return primitives_1.StellarAddressSchema; } }); +Object.defineProperty(exports, "AmountSchema", { enumerable: true, get: function () { return primitives_1.AmountSchema; } }); +Object.defineProperty(exports, "AmountInputSchema", { enumerable: true, get: function () { return primitives_1.AmountInputSchema; } }); +Object.defineProperty(exports, "ShareCountSchema", { enumerable: true, get: function () { return primitives_1.ShareCountSchema; } }); +Object.defineProperty(exports, "AssetCodeSchema", { enumerable: true, get: function () { return primitives_1.AssetCodeSchema; } }); +Object.defineProperty(exports, "IsoDatestamp", { enumerable: true, get: function () { return primitives_1.IsoDatestamp; } }); +Object.defineProperty(exports, "SlippageBpsSchema", { enumerable: true, get: function () { return primitives_1.SlippageBpsSchema; } }); +var requests_1 = require("./requests"); +Object.defineProperty(exports, "DepositRequestSchema", { enumerable: true, get: function () { return requests_1.DepositRequestSchema; } }); +Object.defineProperty(exports, "WithdrawalRequestSchema", { enumerable: true, get: function () { return requests_1.WithdrawalRequestSchema; } }); +Object.defineProperty(exports, "VaultHistoryQuerySchema", { enumerable: true, get: function () { return requests_1.VaultHistoryQuerySchema; } }); +Object.defineProperty(exports, "PortfolioQuerySchema", { enumerable: true, get: function () { return requests_1.PortfolioQuerySchema; } }); +Object.defineProperty(exports, "WalletAddressSchema", { enumerable: true, get: function () { return requests_1.WalletAddressSchema; } }); +Object.defineProperty(exports, "TransactionQuerySchema", { enumerable: true, get: function () { return requests_1.TransactionQuerySchema; } }); +var responses_1 = require("./responses"); +Object.defineProperty(exports, "VaultOperationResponseSchema", { enumerable: true, get: function () { return responses_1.VaultOperationResponseSchema; } }); +var vault_1 = require("./vault"); +Object.defineProperty(exports, "VaultDepositBodySchema", { enumerable: true, get: function () { return vault_1.VaultDepositBodySchema; } }); +Object.defineProperty(exports, "VaultWithdrawalBodySchema", { enumerable: true, get: function () { return vault_1.VaultWithdrawalBodySchema; } }); +Object.defineProperty(exports, "SignedVaultDepositBodySchema", { enumerable: true, get: function () { return vault_1.SignedVaultDepositBodySchema; } }); +Object.defineProperty(exports, "SignedVaultWithdrawalBodySchema", { enumerable: true, get: function () { return vault_1.SignedVaultWithdrawalBodySchema; } }); +Object.defineProperty(exports, "VaultOperationSchema", { enumerable: true, get: function () { return vault_1.VaultOperationSchema; } }); +var webhookEvents_1 = require("./webhookEvents"); +Object.defineProperty(exports, "WEBHOOK_SCHEMA_VERSION", { enumerable: true, get: function () { return webhookEvents_1.WEBHOOK_SCHEMA_VERSION; } }); +Object.defineProperty(exports, "WebhookEventTypeSchema", { enumerable: true, get: function () { return webhookEvents_1.WebhookEventTypeSchema; } }); +Object.defineProperty(exports, "WebhookEventPayloadSchemas", { enumerable: true, get: function () { return webhookEvents_1.WebhookEventPayloadSchemas; } }); +Object.defineProperty(exports, "WebhookEnvelopeSchema", { enumerable: true, get: function () { return webhookEvents_1.WebhookEnvelopeSchema; } }); +Object.defineProperty(exports, "TransactionDepositCreatedPayloadSchema", { enumerable: true, get: function () { return webhookEvents_1.TransactionDepositCreatedPayloadSchema; } }); +Object.defineProperty(exports, "TransactionWithdrawalCreatedPayloadSchema", { enumerable: true, get: function () { return webhookEvents_1.TransactionWithdrawalCreatedPayloadSchema; } }); +Object.defineProperty(exports, "parseWebhookEnvelope", { enumerable: true, get: function () { return webhookEvents_1.parseWebhookEnvelope; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/api-schemas/src/index.js.map b/packages/api-schemas/src/index.js.map new file mode 100644 index 00000000..8528491a --- /dev/null +++ b/packages/api-schemas/src/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAAA,2CAQsB;AAPpB,kHAAA,oBAAoB,OAAA;AACpB,0GAAA,YAAY,OAAA;AACZ,+GAAA,iBAAiB,OAAA;AACjB,8GAAA,gBAAgB,OAAA;AAChB,6GAAA,eAAe,OAAA;AACf,0GAAA,YAAY,OAAA;AACZ,+GAAA,iBAAiB,OAAA;AAGnB,uCAcoB;AAblB,gHAAA,oBAAoB,OAAA;AACpB,mHAAA,uBAAuB,OAAA;AACvB,mHAAA,uBAAuB,OAAA;AACvB,gHAAA,oBAAoB,OAAA;AACpB,+GAAA,mBAAmB,OAAA;AACnB,kHAAA,sBAAsB,OAAA;AAUxB,yCAGqB;AAFnB,yHAAA,4BAA4B,OAAA;AAI9B,iCAQiB;AAPf,+GAAA,sBAAsB,OAAA;AACtB,kHAAA,yBAAyB,OAAA;AACzB,qHAAA,4BAA4B,OAAA;AAC5B,wHAAA,+BAA+B,OAAA;AAC/B,6GAAA,oBAAoB,OAAA;AAKtB,iDAYyB;AAXvB,uHAAA,sBAAsB,OAAA;AACtB,uHAAA,sBAAsB,OAAA;AACtB,2HAAA,0BAA0B,OAAA;AAC1B,sHAAA,qBAAqB,OAAA;AACrB,uIAAA,sCAAsC,OAAA;AACtC,0IAAA,yCAAyC,OAAA;AACzC,qHAAA,oBAAoB,OAAA"} \ No newline at end of file diff --git a/packages/api-schemas/src/primitives.d.ts b/packages/api-schemas/src/primitives.d.ts new file mode 100644 index 00000000..792dd373 --- /dev/null +++ b/packages/api-schemas/src/primitives.d.ts @@ -0,0 +1,24 @@ +/** + * Stellar / Soroban public key: G... base-32 address, 56 characters. + * Validates format only — not an on-chain account existence check. + */ +export declare const StellarAddressSchema: any; +/** + * Positive decimal amount represented as a string (preserves precision). + * Allows up to 7 decimal places to match Stellar's stroop precision. + */ +export declare const AmountSchema: any; +/** + * API boundary amount: accepts canonical string amounts or legacy numeric JSON. + * Normalizes to a string so frontend and backend share one wire format. + */ +export declare const AmountInputSchema: any; +/** Positive integer share count (UI / portfolio display). */ +export declare const ShareCountSchema: any; +/** Supported asset codes. Extend as new assets are on-boarded. */ +export declare const AssetCodeSchema: any; +/** ISO 8601 date string (YYYY-MM-DD). */ +export declare const IsoDatestamp: any; +/** Optional slippage tolerance in basis points (0–500). */ +export declare const SlippageBpsSchema: any; +//# sourceMappingURL=primitives.d.ts.map \ No newline at end of file diff --git a/packages/api-schemas/src/primitives.d.ts.map b/packages/api-schemas/src/primitives.d.ts.map new file mode 100644 index 00000000..1f35f9d9 --- /dev/null +++ b/packages/api-schemas/src/primitives.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["primitives.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,KAM7B,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,YAAY,KASrB,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,iBAAiB,KAQ8C,CAAC;AAE7E,6DAA6D;AAC7D,eAAO,MAAM,gBAAgB,KAIqC,CAAC;AAEnE,kEAAkE;AAClE,eAAO,MAAM,eAAe,KAE1B,CAAC;AAEH,yCAAyC;AACzC,eAAO,MAAM,YAAY,KAIrB,CAAC;AAEL,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,KAKjB,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/primitives.js b/packages/api-schemas/src/primitives.js new file mode 100644 index 00000000..103a39ea --- /dev/null +++ b/packages/api-schemas/src/primitives.js @@ -0,0 +1,66 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SlippageBpsSchema = exports.IsoDatestamp = exports.AssetCodeSchema = exports.ShareCountSchema = exports.AmountInputSchema = exports.AmountSchema = exports.StellarAddressSchema = void 0; +const zod_1 = require("zod"); +/** + * Stellar / Soroban public key: G... base-32 address, 56 characters. + * Validates format only — not an on-chain account existence check. + */ +exports.StellarAddressSchema = zod_1.z + .string() + .trim() + .min(1, { message: "Wallet address is required" }) + .regex(/^G[A-Z2-7]{55}$/, { + message: "Must be a valid Stellar public key (starts with G, 56 chars)", +}); +/** + * Positive decimal amount represented as a string (preserves precision). + * Allows up to 7 decimal places to match Stellar's stroop precision. + */ +exports.AmountSchema = zod_1.z + .string() + .trim() + .min(1, { message: "Amount is required" }) + .regex(/^\d+(\.\d{1,7})?$/, { + message: "Amount must be a positive number with up to 7 decimal places", +}) + .refine((value) => parseFloat(value) > 0, { + message: "Amount must be greater than zero", +}); +/** + * API boundary amount: accepts canonical string amounts or legacy numeric JSON. + * Normalizes to a string so frontend and backend share one wire format. + */ +exports.AmountInputSchema = zod_1.z + .union([ + exports.AmountSchema, + zod_1.z + .number({ error: "Amount is required" }) + .positive("Amount must be greater than zero") + .finite("Amount must be a finite number"), +]) + .transform((value) => (typeof value === "number" ? String(value) : value)); +/** Positive integer share count (UI / portfolio display). */ +exports.ShareCountSchema = zod_1.z + .number({ error: "Share count is required" }) + .int("Share count must be a whole number") + .positive("Share count must be greater than zero") + .max(1000000000, "Share count exceeds maximum allowed value"); +/** Supported asset codes. Extend as new assets are on-boarded. */ +exports.AssetCodeSchema = zod_1.z.enum(["XLM", "USDC", "yUSDC", "RWA"], { + error: "Asset must be one of: XLM, USDC, yUSDC, RWA", +}); +/** ISO 8601 date string (YYYY-MM-DD). */ +exports.IsoDatestamp = zod_1.z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/, { + message: "Date must be in YYYY-MM-DD format", +}); +/** Optional slippage tolerance in basis points (0–500). */ +exports.SlippageBpsSchema = zod_1.z + .number() + .int("Slippage must be a whole number of basis points") + .min(0, "Slippage cannot be negative") + .max(500, "Slippage tolerance may not exceed 500 bps (5%)") + .optional(); +//# sourceMappingURL=primitives.js.map \ No newline at end of file diff --git a/packages/api-schemas/src/primitives.js.map b/packages/api-schemas/src/primitives.js.map new file mode 100644 index 00000000..3f1fc9ad --- /dev/null +++ b/packages/api-schemas/src/primitives.js.map @@ -0,0 +1 @@ +{"version":3,"file":"primitives.js","sourceRoot":"","sources":["primitives.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB;;;GAGG;AACU,QAAA,oBAAoB,GAAG,OAAC;KAClC,MAAM,EAAE;KACR,IAAI,EAAE;KACN,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,4BAA4B,EAAE,CAAC;KACjD,KAAK,CAAC,iBAAiB,EAAE;IACxB,OAAO,EAAE,8DAA8D;CACxE,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,YAAY,GAAG,OAAC;KAC1B,MAAM,EAAE;KACR,IAAI,EAAE;KACN,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;KACzC,KAAK,CAAC,mBAAmB,EAAE;IAC1B,OAAO,EAAE,8DAA8D;CACxE,CAAC;KACD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IACxC,OAAO,EAAE,kCAAkC;CAC5C,CAAC,CAAC;AAEL;;;GAGG;AACU,QAAA,iBAAiB,GAAG,OAAC;KAC/B,KAAK,CAAC;IACL,oBAAY;IACZ,OAAC;SACE,MAAM,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;SACvC,QAAQ,CAAC,kCAAkC,CAAC;SAC5C,MAAM,CAAC,gCAAgC,CAAC;CAC5C,CAAC;KACD,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAE7E,6DAA6D;AAChD,QAAA,gBAAgB,GAAG,OAAC;KAC9B,MAAM,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC;KAC5C,GAAG,CAAC,oCAAoC,CAAC;KACzC,QAAQ,CAAC,uCAAuC,CAAC;KACjD,GAAG,CAAC,UAAa,EAAE,2CAA2C,CAAC,CAAC;AAEnE,kEAAkE;AACrD,QAAA,eAAe,GAAG,OAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAU,EAAE;IAC9E,KAAK,EAAE,6CAA6C;CACrD,CAAC,CAAC;AAEH,yCAAyC;AAC5B,QAAA,YAAY,GAAG,OAAC;KAC1B,MAAM,EAAE;KACR,KAAK,CAAC,qBAAqB,EAAE;IAC5B,OAAO,EAAE,mCAAmC;CAC7C,CAAC,CAAC;AAEL,2DAA2D;AAC9C,QAAA,iBAAiB,GAAG,OAAC;KAC/B,MAAM,EAAE;KACR,GAAG,CAAC,iDAAiD,CAAC;KACtD,GAAG,CAAC,CAAC,EAAE,6BAA6B,CAAC;KACrC,GAAG,CAAC,GAAG,EAAE,gDAAgD,CAAC;KAC1D,QAAQ,EAAE,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/requests.d.ts b/packages/api-schemas/src/requests.d.ts new file mode 100644 index 00000000..d677e82f --- /dev/null +++ b/packages/api-schemas/src/requests.d.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +/** + * Payload sent when a user deposits assets into a vault. + */ +export declare const DepositRequestSchema: any; +export type DepositRequest = z.infer; +/** + * Payload sent when a user redeems vault shares for underlying assets. + */ +export declare const WithdrawalRequestSchema: any; +export type WithdrawalRequest = z.infer; +/** + * Query-string parameters for the vault performance history endpoint. + */ +export declare const VaultHistoryQuerySchema: any; +export type VaultHistoryQuery = z.infer; +/** + * Query-string parameters for the portfolio holdings endpoint. + */ +export declare const PortfolioQuerySchema: any; +export type PortfolioQuery = z.infer; +/** + * Single-param schema used when an endpoint only needs the caller's address. + */ +export declare const WalletAddressSchema: any; +export type WalletAddressParam = z.infer; +/** + * Query-string parameters for the transaction history endpoint. + */ +export declare const TransactionQuerySchema: any; +export type TransactionQuery = z.infer; +export type TransactionQueryInput = z.input; +//# sourceMappingURL=requests.d.ts.map \ No newline at end of file diff --git a/packages/api-schemas/src/requests.d.ts.map b/packages/api-schemas/src/requests.d.ts.map new file mode 100644 index 00000000..03071ce3 --- /dev/null +++ b/packages/api-schemas/src/requests.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"requests.d.ts","sourceRoot":"","sources":["requests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB;;GAEG;AACH,eAAO,MAAM,oBAAoB,KAM/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;GAEG;AACH,eAAO,MAAM,uBAAuB,KAMlC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,uBAAuB,KAmBjC,CAAC;AAEJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,oBAAoB,KAG/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;GAEG;AACH,eAAO,MAAM,mBAAmB,KAE9B,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAErE;;GAEG;AACH,eAAO,MAAM,sBAAsB,KAWjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACtE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/requests.js b/packages/api-schemas/src/requests.js new file mode 100644 index 00000000..c1b1fd76 --- /dev/null +++ b/packages/api-schemas/src/requests.js @@ -0,0 +1,74 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TransactionQuerySchema = exports.WalletAddressSchema = exports.PortfolioQuerySchema = exports.VaultHistoryQuerySchema = exports.WithdrawalRequestSchema = exports.DepositRequestSchema = void 0; +const zod_1 = require("zod"); +const primitives_1 = require("./primitives"); +/** + * Payload sent when a user deposits assets into a vault. + */ +exports.DepositRequestSchema = zod_1.z.object({ + walletAddress: primitives_1.StellarAddressSchema, + amount: primitives_1.AmountSchema, + asset: primitives_1.AssetCodeSchema, + slippageBps: primitives_1.SlippageBpsSchema, + referralCode: zod_1.z.string().optional(), +}); +/** + * Payload sent when a user redeems vault shares for underlying assets. + */ +exports.WithdrawalRequestSchema = zod_1.z.object({ + walletAddress: primitives_1.StellarAddressSchema, + amount: primitives_1.AmountSchema, + asset: primitives_1.AssetCodeSchema, + destinationAddress: primitives_1.StellarAddressSchema.optional(), + slippageBps: primitives_1.SlippageBpsSchema, +}); +/** + * Query-string parameters for the vault performance history endpoint. + */ +exports.VaultHistoryQuerySchema = zod_1.z + .object({ + from: primitives_1.IsoDatestamp.optional(), + to: primitives_1.IsoDatestamp.optional(), + limit: zod_1.z + .number() + .int("Limit must be a whole number") + .min(1, "Limit must be at least 1") + .max(365, "Limit may not exceed 365 data points") + .optional(), +}) + .refine((query) => { + if (query.from && query.to) { + return query.from <= query.to; + } + return true; +}, { message: '"from" date must not be later than "to" date', path: ["from"] }); +/** + * Query-string parameters for the portfolio holdings endpoint. + */ +exports.PortfolioQuerySchema = zod_1.z.object({ + walletAddress: primitives_1.StellarAddressSchema, + status: zod_1.z.enum(["active", "pending", "all"]).optional().default("all"), +}); +/** + * Single-param schema used when an endpoint only needs the caller's address. + */ +exports.WalletAddressSchema = zod_1.z.object({ + walletAddress: primitives_1.StellarAddressSchema, +}); +/** + * Query-string parameters for the transaction history endpoint. + */ +exports.TransactionQuerySchema = zod_1.z.object({ + walletAddress: primitives_1.StellarAddressSchema, + limit: zod_1.z + .number() + .int("Limit must be a whole number") + .min(1, "Limit must be at least 1") + .max(200, "Limit may not exceed 200 records") + .optional() + .default(50), + order: zod_1.z.enum(["asc", "desc"]).optional().default("desc"), + type: zod_1.z.enum(["deposit", "withdrawal", "all"]).optional().default("all"), +}); +//# sourceMappingURL=requests.js.map \ No newline at end of file diff --git a/packages/api-schemas/src/requests.js.map b/packages/api-schemas/src/requests.js.map new file mode 100644 index 00000000..561385ba --- /dev/null +++ b/packages/api-schemas/src/requests.js.map @@ -0,0 +1 @@ +{"version":3,"file":"requests.js","sourceRoot":"","sources":["requests.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,6CAMsB;AAEtB;;GAEG;AACU,QAAA,oBAAoB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3C,aAAa,EAAE,iCAAoB;IACnC,MAAM,EAAE,yBAAY;IACpB,KAAK,EAAE,4BAAe;IACtB,WAAW,EAAE,8BAAiB;IAC9B,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAC;AAIH;;GAEG;AACU,QAAA,uBAAuB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC9C,aAAa,EAAE,iCAAoB;IACnC,MAAM,EAAE,yBAAY;IACpB,KAAK,EAAE,4BAAe;IACtB,kBAAkB,EAAE,iCAAoB,CAAC,QAAQ,EAAE;IACnD,WAAW,EAAE,8BAAiB;CAC/B,CAAC,CAAC;AAIH;;GAEG;AACU,QAAA,uBAAuB,GAAG,OAAC;KACrC,MAAM,CAAC;IACN,IAAI,EAAE,yBAAY,CAAC,QAAQ,EAAE;IAC7B,EAAE,EAAE,yBAAY,CAAC,QAAQ,EAAE;IAC3B,KAAK,EAAE,OAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,8BAA8B,CAAC;SACnC,GAAG,CAAC,CAAC,EAAE,0BAA0B,CAAC;SAClC,GAAG,CAAC,GAAG,EAAE,sCAAsC,CAAC;SAChD,QAAQ,EAAE;CACd,CAAC;KACD,MAAM,CACL,CAAC,KAAK,EAAE,EAAE;IACR,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,EACD,EAAE,OAAO,EAAE,8CAA8C,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAC5E,CAAC;AAIJ;;GAEG;AACU,QAAA,oBAAoB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3C,aAAa,EAAE,iCAAoB;IACnC,MAAM,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACvE,CAAC,CAAC;AAIH;;GAEG;AACU,QAAA,mBAAmB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC1C,aAAa,EAAE,iCAAoB;CACpC,CAAC,CAAC;AAIH;;GAEG;AACU,QAAA,sBAAsB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC7C,aAAa,EAAE,iCAAoB;IACnC,KAAK,EAAE,OAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,8BAA8B,CAAC;SACnC,GAAG,CAAC,CAAC,EAAE,0BAA0B,CAAC;SAClC,GAAG,CAAC,GAAG,EAAE,kCAAkC,CAAC;SAC5C,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;IACd,KAAK,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IACzD,IAAI,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACzE,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/responses.d.ts b/packages/api-schemas/src/responses.d.ts new file mode 100644 index 00000000..aa98cb41 --- /dev/null +++ b/packages/api-schemas/src/responses.d.ts @@ -0,0 +1,5 @@ +import { z } from "zod"; +/** Successful vault deposit / withdrawal response body. */ +export declare const VaultOperationResponseSchema: any; +export type VaultOperationResponse = z.infer; +//# sourceMappingURL=responses.d.ts.map \ No newline at end of file diff --git a/packages/api-schemas/src/responses.d.ts.map b/packages/api-schemas/src/responses.d.ts.map new file mode 100644 index 00000000..a9395104 --- /dev/null +++ b/packages/api-schemas/src/responses.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["responses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,2DAA2D;AAC3D,eAAO,MAAM,4BAA4B,KAW9B,CAAC;AAEZ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/responses.js b/packages/api-schemas/src/responses.js new file mode 100644 index 00000000..404c6018 --- /dev/null +++ b/packages/api-schemas/src/responses.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VaultOperationResponseSchema = void 0; +const zod_1 = require("zod"); +const primitives_1 = require("./primitives"); +/** Successful vault deposit / withdrawal response body. */ +exports.VaultOperationResponseSchema = zod_1.z + .object({ + id: zod_1.z.string(), + type: zod_1.z.enum(["deposit", "withdrawal"]), + amount: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]), + asset: primitives_1.AssetCodeSchema, + walletAddress: primitives_1.StellarAddressSchema, + transactionHash: zod_1.z.string(), + status: zod_1.z.string(), + timestamp: zod_1.z.string(), +}) + .strict(); +//# sourceMappingURL=responses.js.map \ No newline at end of file diff --git a/packages/api-schemas/src/responses.js.map b/packages/api-schemas/src/responses.js.map new file mode 100644 index 00000000..68641a5a --- /dev/null +++ b/packages/api-schemas/src/responses.js.map @@ -0,0 +1 @@ +{"version":3,"file":"responses.js","sourceRoot":"","sources":["responses.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,6CAAqE;AAErE,2DAA2D;AAC9C,QAAA,4BAA4B,GAAG,OAAC;KAC1C,MAAM,CAAC;IACN,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACvC,MAAM,EAAE,OAAC,CAAC,KAAK,CAAC,CAAC,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,KAAK,EAAE,4BAAe;IACtB,aAAa,EAAE,iCAAoB;IACnC,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE;IAC3B,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;CACtB,CAAC;KACD,MAAM,EAAE,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/vault.d.ts b/packages/api-schemas/src/vault.d.ts new file mode 100644 index 00000000..8dda4ee6 --- /dev/null +++ b/packages/api-schemas/src/vault.d.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; +/** + * POST /api/v1/vault/deposits request body (shared with frontend client). + * Accepts string or numeric amounts at the JSON boundary and normalizes to string. + */ +export declare const VaultDepositBodySchema: any; +export type VaultDepositBody = z.infer; +/** + * POST /api/v1/vault/withdrawals request body (shared with frontend client). + */ +export declare const VaultWithdrawalBodySchema: any; +export type VaultWithdrawalBody = z.infer; +/** Vault write body when wallet nonce enforcement is strict. */ +export declare const SignedVaultDepositBodySchema: any; +export declare const SignedVaultWithdrawalBodySchema: any; +/** @deprecated Use VaultDepositBodySchema or VaultWithdrawalBodySchema */ +export declare const VaultOperationSchema: any; +//# sourceMappingURL=vault.d.ts.map \ No newline at end of file diff --git a/packages/api-schemas/src/vault.d.ts.map b/packages/api-schemas/src/vault.d.ts.map new file mode 100644 index 00000000..a2e1933d --- /dev/null +++ b/packages/api-schemas/src/vault.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["vault.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,KAGxB,CAAC;AAEZ,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,yBAAyB,KAG3B,CAAC;AAEZ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,gEAAgE;AAChE,eAAO,MAAM,4BAA4B,KAK9B,CAAC;AAEZ,eAAO,MAAM,+BAA+B,KAKjC,CAAC;AAEZ,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB,KAAyB,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/vault.js b/packages/api-schemas/src/vault.js new file mode 100644 index 00000000..4e2bbd9f --- /dev/null +++ b/packages/api-schemas/src/vault.js @@ -0,0 +1,47 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VaultOperationSchema = exports.SignedVaultWithdrawalBodySchema = exports.SignedVaultDepositBodySchema = exports.VaultWithdrawalBodySchema = exports.VaultDepositBodySchema = void 0; +const zod_1 = require("zod"); +const primitives_1 = require("./primitives"); +const requests_1 = require("./requests"); +const signedActionFields = { + nonce: zod_1.z.string().min(16).max(128), + signature: zod_1.z.string().min(32).max(512), +}; +const vaultOperationExtras = { + email: zod_1.z.string().email().optional(), + referralCode: zod_1.z.string().max(64).optional(), + nonce: signedActionFields.nonce.optional(), + signature: signedActionFields.signature.optional(), +}; +/** + * POST /api/v1/vault/deposits request body (shared with frontend client). + * Accepts string or numeric amounts at the JSON boundary and normalizes to string. + */ +exports.VaultDepositBodySchema = requests_1.DepositRequestSchema.extend({ + amount: primitives_1.AmountInputSchema, + ...vaultOperationExtras, +}).strict(); +/** + * POST /api/v1/vault/withdrawals request body (shared with frontend client). + */ +exports.VaultWithdrawalBodySchema = requests_1.WithdrawalRequestSchema.extend({ + amount: primitives_1.AmountInputSchema, + ...vaultOperationExtras, +}).strict(); +/** Vault write body when wallet nonce enforcement is strict. */ +exports.SignedVaultDepositBodySchema = requests_1.DepositRequestSchema.extend({ + amount: primitives_1.AmountInputSchema, + email: zod_1.z.string().email().optional(), + referralCode: zod_1.z.string().max(64).optional(), + ...signedActionFields, +}).strict(); +exports.SignedVaultWithdrawalBodySchema = requests_1.WithdrawalRequestSchema.extend({ + amount: primitives_1.AmountInputSchema, + email: zod_1.z.string().email().optional(), + referralCode: zod_1.z.string().max(64).optional(), + ...signedActionFields, +}).strict(); +/** @deprecated Use VaultDepositBodySchema or VaultWithdrawalBodySchema */ +exports.VaultOperationSchema = exports.VaultDepositBodySchema; +//# sourceMappingURL=vault.js.map \ No newline at end of file diff --git a/packages/api-schemas/src/vault.js.map b/packages/api-schemas/src/vault.js.map new file mode 100644 index 00000000..0888a095 --- /dev/null +++ b/packages/api-schemas/src/vault.js.map @@ -0,0 +1 @@ +{"version":3,"file":"vault.js","sourceRoot":"","sources":["vault.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,6CAAiD;AACjD,yCAA2E;AAE3E,MAAM,kBAAkB,GAAG;IACzB,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAClC,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACvC,CAAC;AAEF,MAAM,oBAAoB,GAAG;IAC3B,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IACpC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC3C,KAAK,EAAE,kBAAkB,CAAC,KAAK,CAAC,QAAQ,EAAE;IAC1C,SAAS,EAAE,kBAAkB,CAAC,SAAS,CAAC,QAAQ,EAAE;CACnD,CAAC;AAEF;;;GAGG;AACU,QAAA,sBAAsB,GAAG,+BAAoB,CAAC,MAAM,CAAC;IAChE,MAAM,EAAE,8BAAiB;IACzB,GAAG,oBAAoB;CACxB,CAAC,CAAC,MAAM,EAAE,CAAC;AAIZ;;GAEG;AACU,QAAA,yBAAyB,GAAG,kCAAuB,CAAC,MAAM,CAAC;IACtE,MAAM,EAAE,8BAAiB;IACzB,GAAG,oBAAoB;CACxB,CAAC,CAAC,MAAM,EAAE,CAAC;AAIZ,gEAAgE;AACnD,QAAA,4BAA4B,GAAG,+BAAoB,CAAC,MAAM,CAAC;IACtE,MAAM,EAAE,8BAAiB;IACzB,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IACpC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC3C,GAAG,kBAAkB;CACtB,CAAC,CAAC,MAAM,EAAE,CAAC;AAEC,QAAA,+BAA+B,GAAG,kCAAuB,CAAC,MAAM,CAAC;IAC5E,MAAM,EAAE,8BAAiB;IACzB,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IACpC,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC3C,GAAG,kBAAkB;CACtB,CAAC,CAAC,MAAM,EAAE,CAAC;AAEZ,0EAA0E;AAC7D,QAAA,oBAAoB,GAAG,8BAAsB,CAAC"} \ No newline at end of file diff --git a/packages/api-schemas/src/webhookEvents.d.ts b/packages/api-schemas/src/webhookEvents.d.ts new file mode 100644 index 00000000..78375a20 --- /dev/null +++ b/packages/api-schemas/src/webhookEvents.d.ts @@ -0,0 +1,55 @@ +import { z } from "zod"; +/** + * Every event type YieldVault can deliver to a registered webhook endpoint. + * + * This is the *outbound webhook* catalog — the events an off-chain HTTP + * consumer receives from the backend's delivery service + * (`backend/src/webhookDelivery.ts`). It is intentionally narrower than the + * on-chain Soroban contract event catalog documented in + * `docs/WEBHOOK_INTEGRATION.md`: the vault contract emits ~28 distinct + * ledger events (admin rotation, emergency actions, fee changes, strategy + * bookkeeping, etc.), but only transaction-level activity is currently + * surfaced through the webhook delivery pipeline. Consumers that need the + * full contract event set should query Soroban RPC directly rather than + * relying on webhooks for those events. + * + * Keep this list in sync with `TransactionEventType` in + * `backend/src/webhookDelivery.ts` — that file is the source of truth for + * what the server actually emits. + */ +export declare const WebhookEventTypeSchema: any; +export type WebhookEventType = z.infer; +/** + * Monotonically increasing schema version for the outbound webhook envelope. + * Mirrors `WEBHOOK_SCHEMA_VERSION` in `backend/src/webhookDelivery.ts`. + * Consumers should gate on `schemaVersion` for forward-compatibility rather + * than assuming the envelope shape is fixed. + */ +export declare const WEBHOOK_SCHEMA_VERSION = 1; +/** Payload for `transaction.deposit.created`. */ +export declare const TransactionDepositCreatedPayloadSchema: any; +export type TransactionDepositCreatedPayload = z.infer; +/** Payload for `transaction.withdrawal.created`. */ +export declare const TransactionWithdrawalCreatedPayloadSchema: any; +export type TransactionWithdrawalCreatedPayload = z.infer; +/** Maps each event type to its payload schema. Used to validate by discriminant. */ +export declare const WebhookEventPayloadSchemas: { + readonly "transaction.deposit.created": any; + readonly "transaction.withdrawal.created": any; +}; +/** + * The full outbound envelope written to the wire and stored in + * dead-letter records. Matches `WebhookEnvelope` in + * `backend/src/webhookDelivery.ts`. + */ +export declare const WebhookEnvelopeSchema: any; +export type WebhookEnvelope = z.infer; +/** + * Parses and validates a raw webhook delivery body against the envelope + * schema, then re-validates `payload` against the schema specific to its + * `eventType`. Prefer this over `WebhookEnvelopeSchema.parse` directly so + * that payload drift for a specific event type is caught even if the + * generic envelope shape still matches. + */ +export declare function parseWebhookEnvelope(data: unknown): WebhookEnvelope; +//# sourceMappingURL=webhookEvents.d.ts.map \ No newline at end of file diff --git a/packages/api-schemas/src/webhookEvents.d.ts.map b/packages/api-schemas/src/webhookEvents.d.ts.map new file mode 100644 index 00000000..77bad9eb --- /dev/null +++ b/packages/api-schemas/src/webhookEvents.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"webhookEvents.d.ts","sourceRoot":"","sources":["webhookEvents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,sBAAsB,KAGjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAoBxC,iDAAiD;AACjD,eAAO,MAAM,sCAAsC,KAChB,CAAC;AACpC,MAAM,MAAM,gCAAgC,GAAG,CAAC,CAAC,KAAK,CACpD,OAAO,sCAAsC,CAC9C,CAAC;AAEF,oDAAoD;AACpD,eAAO,MAAM,yCAAyC,KACnB,CAAC;AACpC,MAAM,MAAM,mCAAmC,GAAG,CAAC,CAAC,KAAK,CACvD,OAAO,yCAAyC,CACjD,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,0BAA0B;;;CAGoB,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,KAOvB,CAAC;AAEZ,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,CAKnE"} \ No newline at end of file diff --git a/packages/api-schemas/src/webhookEvents.js b/packages/api-schemas/src/webhookEvents.js new file mode 100644 index 00000000..5534f42a --- /dev/null +++ b/packages/api-schemas/src/webhookEvents.js @@ -0,0 +1,88 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parseWebhookEnvelope = exports.WebhookEnvelopeSchema = exports.WebhookEventPayloadSchemas = exports.TransactionWithdrawalCreatedPayloadSchema = exports.TransactionDepositCreatedPayloadSchema = exports.WEBHOOK_SCHEMA_VERSION = exports.WebhookEventTypeSchema = void 0; +const zod_1 = require("zod"); +const primitives_1 = require("./primitives"); +/** + * Every event type YieldVault can deliver to a registered webhook endpoint. + * + * This is the *outbound webhook* catalog — the events an off-chain HTTP + * consumer receives from the backend's delivery service + * (`backend/src/webhookDelivery.ts`). It is intentionally narrower than the + * on-chain Soroban contract event catalog documented in + * `docs/WEBHOOK_INTEGRATION.md`: the vault contract emits ~28 distinct + * ledger events (admin rotation, emergency actions, fee changes, strategy + * bookkeeping, etc.), but only transaction-level activity is currently + * surfaced through the webhook delivery pipeline. Consumers that need the + * full contract event set should query Soroban RPC directly rather than + * relying on webhooks for those events. + * + * Keep this list in sync with `TransactionEventType` in + * `backend/src/webhookDelivery.ts` — that file is the source of truth for + * what the server actually emits. + */ +exports.WebhookEventTypeSchema = zod_1.z.enum([ + "transaction.deposit.created", + "transaction.withdrawal.created", +]); +/** + * Monotonically increasing schema version for the outbound webhook envelope. + * Mirrors `WEBHOOK_SCHEMA_VERSION` in `backend/src/webhookDelivery.ts`. + * Consumers should gate on `schemaVersion` for forward-compatibility rather + * than assuming the envelope shape is fixed. + */ +exports.WEBHOOK_SCHEMA_VERSION = 1; +/** + * Payload shared by every current transaction event. Both + * `transaction.deposit.created` and `transaction.withdrawal.created` use + * this same shape today; they are kept as separate schemas below so each + * event type can diverge independently as new fields are added. + */ +const BaseTransactionEventPayloadSchema = zod_1.z + .object({ + transactionId: zod_1.z.string().min(1), + amount: zod_1.z.string().min(1), + asset: primitives_1.AssetCodeSchema, + walletAddress: primitives_1.StellarAddressSchema, + transactionHash: zod_1.z.string().min(1), + status: zod_1.z.string().min(1), + timestamp: zod_1.z.iso.datetime(), +}) + .strict(); +/** Payload for `transaction.deposit.created`. */ +exports.TransactionDepositCreatedPayloadSchema = BaseTransactionEventPayloadSchema; +/** Payload for `transaction.withdrawal.created`. */ +exports.TransactionWithdrawalCreatedPayloadSchema = BaseTransactionEventPayloadSchema; +/** Maps each event type to its payload schema. Used to validate by discriminant. */ +exports.WebhookEventPayloadSchemas = { + "transaction.deposit.created": exports.TransactionDepositCreatedPayloadSchema, + "transaction.withdrawal.created": exports.TransactionWithdrawalCreatedPayloadSchema, +}; +/** + * The full outbound envelope written to the wire and stored in + * dead-letter records. Matches `WebhookEnvelope` in + * `backend/src/webhookDelivery.ts`. + */ +exports.WebhookEnvelopeSchema = zod_1.z + .object({ + schemaVersion: zod_1.z.number().int().positive(), + eventType: exports.WebhookEventTypeSchema, + sentAt: zod_1.z.iso.datetime(), + payload: BaseTransactionEventPayloadSchema, +}) + .strict(); +/** + * Parses and validates a raw webhook delivery body against the envelope + * schema, then re-validates `payload` against the schema specific to its + * `eventType`. Prefer this over `WebhookEnvelopeSchema.parse` directly so + * that payload drift for a specific event type is caught even if the + * generic envelope shape still matches. + */ +function parseWebhookEnvelope(data) { + const envelope = exports.WebhookEnvelopeSchema.parse(data); + const payloadSchema = exports.WebhookEventPayloadSchemas[envelope.eventType]; + payloadSchema.parse(envelope.payload); + return envelope; +} +exports.parseWebhookEnvelope = parseWebhookEnvelope; +//# sourceMappingURL=webhookEvents.js.map \ No newline at end of file diff --git a/packages/api-schemas/src/webhookEvents.js.map b/packages/api-schemas/src/webhookEvents.js.map new file mode 100644 index 00000000..b5bdff98 --- /dev/null +++ b/packages/api-schemas/src/webhookEvents.js.map @@ -0,0 +1 @@ +{"version":3,"file":"webhookEvents.js","sourceRoot":"","sources":["webhookEvents.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,6CAAqE;AAErE;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,sBAAsB,GAAG,OAAC,CAAC,IAAI,CAAC;IAC3C,6BAA6B;IAC7B,gCAAgC;CACjC,CAAC,CAAC;AAIH;;;;;GAKG;AACU,QAAA,sBAAsB,GAAG,CAAC,CAAC;AAExC;;;;;GAKG;AACH,MAAM,iCAAiC,GAAG,OAAC;KACxC,MAAM,CAAC;IACN,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAChC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,KAAK,EAAE,4BAAe;IACtB,aAAa,EAAE,iCAAoB;IACnC,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAClC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,SAAS,EAAE,OAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;CAC5B,CAAC;KACD,MAAM,EAAE,CAAC;AAEZ,iDAAiD;AACpC,QAAA,sCAAsC,GACjD,iCAAiC,CAAC;AAKpC,oDAAoD;AACvC,QAAA,yCAAyC,GACpD,iCAAiC,CAAC;AAKpC,oFAAoF;AACvE,QAAA,0BAA0B,GAAG;IACxC,6BAA6B,EAAE,8CAAsC;IACrE,gCAAgC,EAAE,iDAAyC;CAClB,CAAC;AAE5D;;;;GAIG;AACU,QAAA,qBAAqB,GAAG,OAAC;KACnC,MAAM,CAAC;IACN,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC1C,SAAS,EAAE,8BAAsB;IACjC,MAAM,EAAE,OAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;IACxB,OAAO,EAAE,iCAAiC;CAC3C,CAAC;KACD,MAAM,EAAE,CAAC;AAIZ;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,IAAa;IAChD,MAAM,QAAQ,GAAG,6BAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,aAAa,GAAG,kCAA0B,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACrE,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,QAAQ,CAAC;AAClB,CAAC;AALD,oDAKC"} \ No newline at end of file