Skip to main content

Monorepo Structure

lapis/
├── packages/
│   ├── shared/          # @lapis/shared — TypeScript types
│   ├── ai-agent/        # @lapis/ai-agent — Express API server
│   ├── xrpl-contracts/  # @lapis/xrpl-contracts — XRPL primitives
│   └── metalex/         # @lapis/metalex — SAFE contracts on Base Sepolia
└── apps/
    └── web/             # Next.js frontend (lapis.bet)

Package Dependencies

@lapis/shared must build first since both ai-agent and xrpl-contracts reference its types.

Build Order

npm run build
# Equivalent to:
# 1. npm run build --workspace=packages/shared
# 2. npm run build --workspace=packages/xrpl-contracts
# 3. npm run build --workspace=packages/ai-agent
For development, tsx watch handles transpilation on-the-fly:
npm run dev:agent

Key Design Decisions

ESM-Only

The entire codebase uses ES modules ("type": "module" in all package.json files). All imports require .js extensions, even for TypeScript files:
import { ReportCard } from "@lapis/shared/types.js";

Storage Layer

By default, state is stored in JavaScript Map objects (in-memory). Optionally, set REDIS_URL to enable Redis-backed persistence:
  • Without Redis — State is lost on server restart, no setup required
  • With Redis — Reports, markets, and settlements persist across restarts

Fire-and-Forget Pipeline

POST /analyze returns immediately with a report ID. The analysis pipeline (scrape → score → audit) runs asynchronously. Clients poll /report/:id/score for status updates.

Settlement Mutex

XRPL transactions require sequential sequence numbers. A mutex ensures only one settlement runs at a time — concurrent requests queue up rather than failing.

Response Format

All API responses use a typed wrapper:
interface ApiResponse<T> {
  success: boolean;
  data?: T;      // present on success
  error?: string; // present on failure
}