{"path":"docs/wiki/Architecture.md","size":10508,"locked":false,"binary":false,"truncated":false,"content":"# Architecture\n\nMappers operates through five integrated layers: an on-chain escrow engine, an oracle middleware, a dual-model AI consensus loop, a REST API server, and a client dashboard. This page describes the full system architecture, on-chain account design, job lifecycle, and security properties.\n\n---\n\n## System Overview\n\n```\nDashboard (Vite + React 19)\n      | HTTP (TanStack Query hooks)\n      v\nAPI Server (Express 5 + Drizzle ORM + PostgreSQL)\n      | fetch() to oracle\n      v\nOracle Middleware (Node.js)\n      | gRPC event stream (Helius)       | parallel AI calls\n      v                                  v\nOn-Chain Escrow Engine          Dual-Model AI Consensus\n  (Anchor / Rust)                 Gemini + Claude\n  |-- GigEscrow PDA                    |\n  |-- Vault PDA                        | consensus\n      ^                                v\n      |---- release_payment / cancel_job (on-chain tx) ----|\n```\n\n### Data Flow\n\n1. **Client creates a job** — The dashboard calls the API server, which registers the job in PostgreSQL. Separately, the client signs an `initialize_job` transaction on-chain.\n2. **Oracle detects the job** — The oracle middleware subscribes to Helius gRPC and detects new escrow accounts in real time.\n3. **Freelancer submits a deliverable** — The dashboard sends the deliverable to the API server, which proxies it to the oracle.\n4. **AI verification** — The oracle dispatches the deliverable to Gemini and Claude in parallel. Both return structured verdicts.\n5. **Settlement** — If consensus is reached, the oracle signs either `release_payment` or `cancel_job` on-chain.\n\n---\n\n## Layer 1 — On-Chain Escrow Engine (Anchor / Rust)\n\nTracks job lifecycle state and holds client funds inside dual Program Derived Address (PDA) vaults. All state transitions are irreversible and publicly auditable on-chain.\n\n### PDA Architecture\n\nMappers uses **two separate PDAs per job**:\n\n**GigEscrow Account** — stores job metadata and lifecycle state.\n\n```\nseeds = [\"gig-escrow\", client_pubkey, job_id]\n```\n\n**Vault Account** — a data-less, System Program-owned account holding locked SOL.\n\n```\nseeds = [\"vault\", client_pubkey, job_id]\n```\n\nThe separation is required because the vault must be owned by the System Program for native lamport transfers to work. If the vault and escrow were merged, Anchor's ownership model would conflict with the System Program's signer authority requirements during CPI.\n\nBoth bumps (`escrow_bump` and `vault_bump`) are stored on the GigEscrow account at initialization, eliminating `find_program_address` calls on resolution instructions (~50,000 CU saved per transaction).\n\n### GigEscrow State Schema\n\n```rust\npub struct GigEscrow {\n    pub client:      Pubkey,    // 32 bytes\n    pub freelancer:  Pubkey,    // 32 bytes\n    pub oracle:      Pubkey,    // 32 bytes\n    pub amount:      u64,       // 8 bytes\n    pub job_id:      String,    // 4 + 32 bytes (max)\n    pub status:      JobStatus, // 1 byte\n    pub escrow_bump: u8,        // 1 byte\n    pub vault_bump:  u8,        // 1 byte\n}\n// Total: 151 bytes (including 8-byte Anchor discriminator)\n```\n\n### Instruction Set\n\n| Instruction                      | Caller           | Effect                                                                                                      |\n| -------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- |\n| `initialize_job(job_id, amount)` | Client           | Validates inputs, derives PDAs, transfers SOL to vault, sets status to `Pending`.                           |\n| `release_payment()`              | Client or Oracle | Validates `Pending` status, transfers vault balance to freelancer, closes escrow (rent returned to client). |\n| `cancel_job()`                   | Oracle only      | Validates `Pending` status, refunds vault to client, closes escrow.                                         |\n\n---\n\n## Layer 2 — Oracle Middleware (Node.js)\n\nA persistent off-chain service that bridges on-chain events to the AI verification pipeline.\n\n**Event Detection** — Maintains a gRPC subscription to Helius, streaming all transactions that touch the Mappers program ID. On detecting an `InitializeJob` event:\n\n1. Deserializes the GigEscrow account using the program IDL.\n2. Extracts job metadata (job_id, freelancer, oracle, amount).\n3. Stores a pending verification record.\n\n**Artifact Ingestion** — When a freelancer submits a deliverable (URL, IPFS hash, text, or JSON), the oracle retrieves it and packages a verification request containing the original job description, acceptance criteria, and the deliverable content.\n\n**Endpoints:**\n\n```\nGET  /health      — liveness + pending job count\nGET  /jobs/:jobId — tracked job state\nPOST /submit      — trigger AI verification\n```\n\n---\n\n## Layer 3 — Dual-Model AI Consensus Loop\n\nThe Oracle middleware dispatches verification requests to Gemini and Claude in parallel, with no knowledge sharing between models. The service owns request validation, artifact handling, consensus evaluation, and settlement authorization; it does not treat either model as a trusted authority by itself.\n\n### Verdict Schema (per model)\n\n```json\n{\n  \"verdict\": \"APPROVED | REJECTED\",\n  \"confidence\": 0.0-1.0,\n  \"reasoning\": \"string\",\n  \"criteria_met\": [\"...\"],\n  \"criteria_failed\": [\"...\"]\n}\n```\n\n### Consensus Resolution\n\n| Gemini             | Claude             | Outcome                             |\n| ------------------ | ------------------ | ----------------------------------- |\n| APPROVED (>= 0.80) | APPROVED (>= 0.80) | `release_payment` — freelancer paid |\n| REJECTED (>= 0.75) | REJECTED (>= 0.75) | `cancel_job` — client refunded      |\n| Divergent          | --                 | Escalate to human arbitration       |\n| Sub-threshold      | --                 | Escalate to human arbitration       |\n\n### Why Two Models\n\nA single AI gating payments is a single point of failure. Two independent models with different architectures must agree before funds move. Deliverable artifacts are sanitized with XML delimiters to defend against prompt injection.\n\n---\n\n## Layer 4 — API Server (Express 5)\n\nThe REST API (`apps/api-server/`) provides the data layer between the dashboard and the on-chain/oracle systems.\n\n- **Framework:** Express 5 with async route handlers\n- **Database:** PostgreSQL via Drizzle ORM (`lib/db/`)\n- **Validation:** Zod schemas (`lib/api-zod/`) generated from the OpenAPI spec\n- **Logging:** Pino (structured JSON logs)\n- **Error handling:** Global error middleware prevents stack trace leaks\n\nThe API server mirrors on-chain job state in PostgreSQL for efficient querying, filtering, and aggregation that would be impractical with direct RPC calls.\n\n---\n\n## Layer 5 — Dashboard (Vite + React 19)\n\nThe frontend (`apps/dashboard/`) is a single-page application providing real-time visibility into all escrow jobs.\n\n- **Build:** Vite\n- **UI:** React 19 + shadcn/ui + Tailwind CSS\n- **Data fetching:** TanStack Query via generated hooks (`lib/api-client-react/`)\n- **Type safety:** Shared Zod schemas ensure API responses are validated at runtime\n\n---\n\n## Job Lifecycle State Machine\n\nEvery job moves through a deterministic state graph enforced on-chain:\n\n```\nPENDING ---- (oracle or client approves) ----> COMPLETED\n   |\n   +---- (oracle arbitrates) ----------------> CANCELLED\n```\n\nState transitions are irreversible. Once a job reaches a terminal state, the escrow account is closed and rent is returned to the client. There is no re-open path.\n\n---\n\n## Workspace Package Graph\n\n```\nlib/api-spec          (OpenAPI spec, generates code for:)\n  |-- lib/api-zod     (Zod schemas for request/response validation)\n  |-- lib/api-client-react  (TanStack Query hooks + custom fetch)\n\noracle               (workspace package: event listener, verification, and settlement bridge)\nlib/db                (Drizzle schema + PostgreSQL connection)\nlib/sdk               (MappersClient + OracleClient)\n\napps/api-server       (imports: lib/db, lib/api-zod)\napps/dashboard        (imports: lib/api-client-react)\n```\n\nCode generation flows from `lib/api-spec` (via [orval](https://orval.dev/)) into both `lib/api-zod` and `lib/api-client-react`, ensuring the API contract is always in sync across the server and client.\n\n---\n\n## Security Properties\n\n- **Dual Bump Storage** — Both PDA bumps stored on-chain at initialization, preventing wrong-bump CPI failures.\n- **Reentrancy Mitigation** — All state fields cached to stack before any mutable borrow or CPI.\n- **Rent Reclamation** — `close = client` constraint ensures zero lamports are permanently locked.\n- **Rent-Exempt Floor Guard** — Deposits below ~890,880 lamports are rejected.\n- **Double-Spend Prevention** — `JobNotPending` guard + Solana's atomic transactions eliminate race conditions.\n- **Signer Forgery Prevention** — `has_one` constraints verify accounts match stored pubkeys before instruction logic runs.\n- **Pinned Bump Constraints** — Pinned at the account validation layer, saving compute units.\n- **Global Error Handler** — API server catches unhandled errors and returns generic 500 responses (no stack trace leaks).\n- **Non-JSON Response Safety** — OracleClient gracefully handles non-JSON responses (e.g., proxy 502 pages) without crashing.\n\n---\n\n## Trust Model\n\n**What is trustless:**\n\n- Client funds cannot be moved by anyone other than the oracle or the client.\n- The oracle cannot redirect funds to any address other than the stored freelancer or client.\n- Completed escrows are fully settled — no state can be reopened post-resolution.\n- All state transitions are publicly auditable on-chain.\n\n**Trust assumptions:**\n\n- **Oracle key security** — A compromised oracle key could arbitrarily release or cancel. Mitigation: multisig-controlled key in production.\n- **AI model correctness** — Models can be wrong or manipulated. Mitigations: dual-model consensus + human arbitration fallback.\n- **Artifact integrity** — Artifacts are ingested off-chain; tampering before reaching the oracle is an attack surface.\n\nMappers does not solve identity verification. Wallets are pseudonymous. Reputation and credentials are composable concerns layered on top.\n\n---\n\nSee the [SDK Reference](SDK-Reference.md) for programmatic access, [API Reference](API-Reference.md) for the REST endpoints, or [Getting Started](Getting-Started.md) to run the full stack locally.\n"}