Skip to content

Latest commit

 

History

History
276 lines (237 loc) · 11.6 KB

File metadata and controls

276 lines (237 loc) · 11.6 KB

ARCHITECTURE.md — Stellect Technical Architecture

System Overview

┌─────────────────────────────────────────────────────────────┐
│ AI Agent (Claude Desktop / GPT / LangChain)                  │
│ Uses MCP protocol to call Stellect tools                  │
└──────────────────────┬──────────────────────────────────────┘
                       │ stdio (MCP)
┌──────────────────────▼──────────────────────────────────────┐
│ @stellect/mcp-server                                      │
│ Tools: list_services, call_paid_api, check_balance,          │
│        get_spending_history                                   │
└──────────┬──────────────────────────────┬───────────────────┘
           │                              │
┌──────────▼──────────┐    ┌──────────────▼───────────────────┐
│ @stellect/core    │    │ @stellect/wallet-native        │
│ - X402Client         │    │ - Keystore (AES-256-GCM)          │
│ - ServiceRegistry    │    │ - Signer (isolated scope)         │
│ - BudgetGuard        │    │ - PolicyEngine                    │
└──────────┬──────────┘    └──────────────┬───────────────────┘
           │                              │
           │    ┌─────────────────────────┤
           │    │                         │
┌──────────▼────▼──┐    ┌────────────────▼────────────────────┐
│ OpenZeppelin      │    │ Stellar Network                      │
│ Facilitator       │    │ - Soroban budget-policy contract      │
│ (verify + settle) │    │ - USDC settlement                    │
└───────────────────┘    │ - Account balances                   │
                         └─────────────────────────────────────┘

Package Details

@stellect/wallet (interface only)

Purpose: Define the contract that all wallet adapters must implement.

Files:

  • src/interface.ts — WalletProvider interface
  • src/types.ts — All shared types (WalletDescriptor, SignRequest, SignResult, SpendingPolicy, Balance, etc.)
  • src/errors.ts — Custom error classes (PolicyRejectionError, KeystoreError, InsufficientBalanceError)
  • src/index.ts — Re-exports everything

Key design: This package has ZERO runtime dependencies. It's pure TypeScript types and interfaces. It compiles to .d.ts files that other packages import.

@stellect/wallet-native

Purpose: Stellar-native wallet implementation. Our primary adapter and main IP.

Files:

  • src/native-provider.ts — NativeStellarProvider implements WalletProvider
  • src/keystore.ts — AES-256-GCM encrypted key storage at ~/.stellect/wallets/
  • src/signer.ts — Isolated signing (decrypt → sign → wipe pattern)
  • src/policy-engine.ts — Local + Soroban policy evaluation
  • src/stellar-utils.ts — Helper functions (balance queries, account creation, friendbot)
  • src/index.ts
  • src/__tests__/keystore.test.ts
  • src/__tests__/policy-engine.test.ts
  • src/__tests__/signer.test.ts
  • src/__tests__/native-provider.test.ts

Key design:

sign() flow:
1. policyEngine.evaluate(context)     ← Check BEFORE touching keys
2. if rejected → return error         ← No key material touched
3. keystore.decrypt(name)             ← Key in memory (scoped)
4. keypair.sign(payload)              ← Produce signature
5. [key goes out of scope → GC]      ← Key wiped
6. policyEngine.recordSpend(context)  ← Update spending tracker
7. return { signature, evaluation }

Storage format (~/.stellect/wallets/agent-name.json):

{
  "version": 1,
  "algorithm": "aes-256-gcm",
  "salt": "hex...",
  "iv": "hex...",
  "tag": "hex...",
  "encrypted": "hex...",
  "metadata": {
    "publicKey": "G...",
    "network": "stellar:testnet"
  },
  "createdAt": "2026-04-02T..."
}

@stellect/core

Purpose: x402 payment engine and service registry.

Files:

  • src/x402-client.ts — X402Client class (request → 402 → pay → retry)
  • src/service-registry.ts — ServiceRegistry (load bazaar JSON, filter, search)
  • src/budget-guard.ts — BudgetGuard (Soroban contract interaction)
  • src/facilitator.ts — FacilitatorClient (OpenZeppelin API wrapper)
  • src/types.ts — X402PaymentRequired, ServiceEntry, etc.
  • src/index.ts
  • src/__tests__/x402-client.test.ts
  • src/__tests__/service-registry.test.ts
  • src/__tests__/budget-guard.test.ts

x402 Payment Flow:

1. fetch(url)                         → HTTP response
2. if status !== 402 → return         → Done (free endpoint)
3. parse payment instructions         → { price, network, payTo, scheme }
4. find stellar:testnet accept option → Must match our network
5. wallet.sign(authEntry, context)    → Signed Soroban auth entry
6. facilitator.settle(signature)      → On-chain USDC transfer
7. fetch(url, { X-PAYMENT header })   → Retry with payment proof
8. return response                    → API data

Service Registry (bazaar) format — services.json:

{
  "version": 1,
  "services": [
    {
      "id": "weather-openweathermap",
      "name": "Real-time Weather Data",
      "description": "Current weather for any city worldwide via OpenWeatherMap",
      "endpoint": "https://gateway.stellect.dev/api/weather",
      "method": "GET",
      "params": { "city": "string (city name)" },
      "price": "$0.001",
      "network": "stellar:testnet",
      "payTo": "G...",
      "category": "data",
      "provider": "Stellect Demo"
    }
  ]
}

@stellect/mcp-server

Purpose: MCP server that exposes Stellect tools to AI agents.

Files:

  • src/server.ts — McpServer setup + transport
  • src/tools/list-services.ts — List available paid services from bazaar
  • src/tools/call-paid-api.ts — Call a service, handle x402 payment automatically
  • src/tools/check-balance.ts — Query wallet USDC/XLM balance
  • src/tools/get-spending-history.ts — Recent transactions
  • src/index.ts
  • src/__tests__/tools.test.ts

MCP Tool Definitions:

Tool Input Output What it does
list_services { category?: string } JSON array of services Reads service registry, returns available services with prices
call_paid_api { endpoint, method, params } API response JSON Makes x402 request, handles 402+payment, returns data
check_balance {} { usdc, xlm } Queries Stellar for wallet balances
get_spending_history { limit?: number } Transaction array Returns recent spending from local log

@stellect/demo-apis

Purpose: Express server with x402-paywalled endpoints that proxy to real external APIs.

Files:

  • src/server.ts — Express app + x402 middleware setup
  • src/endpoints/weather.ts — Proxies to OpenWeatherMap API
  • src/endpoints/crypto-price.ts — Proxies to CoinGecko API
  • src/endpoints/ai-summary.ts — Simple text summarization
  • src/services.json — Bazaar registry file
  • src/index.ts

Endpoint pricing:

Endpoint Method Price Real data source
/api/weather?city=Istanbul GET $0.001 OpenWeatherMap API
/api/crypto-price?symbol=XLM GET $0.002 CoinGecko API
/api/ai-summary POST $0.005 Local summarization
/api/services GET FREE Service registry JSON

contracts/budget-policy (Soroban / Rust)

Purpose: On-chain spending policy enforcement.

Functions:

  • create_policy(owner, agent, max_per_tx, max_daily, allowed_services) → Creates policy
  • check_spend(agent, amount, service) → Returns bool (can agent spend?)
  • record_spend(agent, amount) → Updates daily_spent counter
  • get_policy(agent) → Returns current policy state
  • deactivate(owner, agent) → Disables policy

Storage: One AgentPolicy struct per agent address in persistent storage. Daily counter resets after 86400 seconds (24h).

Data Flow: End-to-End Payment

User: "What's the weather in Istanbul?"
  │
  ▼
Claude Desktop → calls list_services() via MCP
  │
  ▼
MCP Server → reads services.json → returns service list
  │
  ▼
Claude picks weather service → calls call_paid_api("/api/weather?city=Istanbul")
  │
  ▼
MCP Server → X402Client.request("http://localhost:4021/api/weather?city=Istanbul")
  │
  ▼
Demo API Server → x402 middleware returns HTTP 402
  │  Body: { accepts: [{ scheme: "exact", price: "$0.001", network: "stellar:testnet", payTo: "G..." }] }
  │
  ▼
X402Client → parses 402 → extracts payment instructions
  │
  ▼
X402Client → BudgetGuard.checkSpend(agent, "$0.001", payTo)
  │
  ▼
BudgetGuard → calls Soroban contract check_spend() → returns true
  │
  ▼
X402Client → wallet.sign(walletName, { type: "auth_entry", payload: authEntryXdr, context })
  │
  ▼
NativeStellarProvider:
  1. PolicyEngine.evaluate() → allowed ✓
  2. Keystore.decrypt("agent-default") → secret key (in scoped memory)
  3. Keypair.sign(authEntry) → signature
  4. [secret key out of scope → wiped]
  5. PolicyEngine.recordSpend()
  │
  ▼
X402Client → FacilitatorClient.settle(signature, authEntry)
  │
  ▼
OpenZeppelin Facilitator:
  1. Verifies signature
  2. Submits USDC transfer on Stellar testnet
  3. Returns { paymentHeader, txHash }
  │  ~5 seconds
  │
  ▼
X402Client → fetch(url, { headers: { "X-PAYMENT": paymentHeader } })
  │
  ▼
Demo API Server → middleware verifies payment → passes to handler
  │
  ▼
Weather handler → fetches from OpenWeatherMap → returns real data
  │  { city: "Istanbul", temperature: 22, condition: "sunny", ... }
  │
  ▼
MCP Server → returns data to Claude
  │
  ▼
Claude: "Istanbul'da 22°C ve güneşli. Hafif bir ceket yeterli olacaktır."

Security Model

  1. Key isolation: Private keys are AES-256-GCM encrypted at rest. Decryption happens inside a scoped block in the sign() method. The decrypted key never leaves that scope, is never logged, and is never returned.

  2. Policy-before-decrypt: The policy engine runs BEFORE any key material is touched. If the policy rejects (budget exceeded, service not allowed), the keystore is never accessed.

  3. On-chain enforcement: Soroban contract provides a second layer of budget checking. Even if the local policy engine is bypassed, the contract will reject over-budget transactions.

  4. Passphrase protection: The encryption passphrase comes from an environment variable, never hardcoded. In production, this would be a hardware security module or secure enclave.

  5. Audit trail: Every sign() call is logged to a local append-only JSON log with timestamp, service, amount, and tx hash. No key material in logs.