A comprehensive guide to understanding how data flows through Open-Audit
This document provides a detailed architectural overview of Open-Audit, the "Google Translate for Soroban" — an open-source transparency tool that transforms cryptic blockchain events into human-readable sentences.
- System Overview
- Architecture Diagram
- Component Deep Dive
- Data Flow
- Key Technologies
- Development Guide
Open-Audit is a full-stack application that bridges the gap between raw blockchain data and human understanding. The system consists of five major components that work together to fetch, translate, store, and display Soroban smart contract events.
┌─────────────────┐
│ Stellar Network│ Raw XDR events emitted by smart contracts
└────────┬────────┘
│
▼
┌─────────────────┐
│ Event Indexer │ Polls RPC with rate limit handling
└────────┬────────┘
│
▼
┌─────────────────┐
│ Translation │ Converts hex data to human-readable text
│ Registry │
└────────┬────────┘
│
▼
┌─────────────────┐
│ WebSocket │ Real-time event streaming
│ Server │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Frontend │ Interactive dashboard with search & filters
│ Dashboard │
└─────────────────┘
flowchart TB
subgraph StellarNetwork["🌐 Stellar Network"]
RPC["Soroban RPC Server<br/>soroban-testnet.stellar.org"]
Contracts["Smart Contracts<br/>(SAC, Soroswap, etc.)"]
Contracts -->|emit events| RPC
end
subgraph Backend["⚙️ Backend Services (Node.js)"]
subgraph Indexer["Event Indexer (lib/stellar/indexer.ts)"]
Poller["Polling Loop<br/>(every 5s)"]
RetryLogic["Exponential Backoff<br/>Retry Logic"]
Cursor["Cursor Manager<br/>(tracks last ledger)"]
Poller --> RetryLogic
RetryLogic --> Cursor
end
subgraph Translator["Translation Engine (lib/translator/)"]
Registry["Translation Registry<br/>(registry.ts)"]
Blueprints["Contract Blueprints<br/>(blueprints/)"]
Decoder["XDR Decoder<br/>(decode.ts)"]
Registry --> Blueprints
Blueprints --> Decoder
end
WSServer["WebSocket Server<br/>(server.ts)<br/>Port: 3000<br/>Path: /ws/events"]
Indexer -->|RawEvent[]| Translator
Translator -->|TranslatedEvent[]| WSServer
end
subgraph Frontend["🎨 Frontend (Next.js 14 + React)"]
subgraph UI["Dashboard UI (app/dashboard/)"]
EventTable["Event Feed Table<br/>(EventFeedTable.tsx)"]
SearchBar["Search & Filters<br/>(SearchBar.tsx)"]
StatsBar["Statistics Bar<br/>(StatsBar.tsx)"]
RawDialog["Raw Data Viewer<br/>(RawDataDialog.tsx)"]
end
subgraph Hooks["React Hooks (lib/hooks/)"]
LiveFeed["useLiveFeed<br/>(WebSocket client)"]
end
LiveFeed -->|new events| EventTable
EventTable --> SearchBar
EventTable --> StatsBar
EventTable --> RawDialog
end
RPC -->|"getEvents()<br/>(HTTP 429 handled)"| Poller
WSServer -->|"WebSocket<br/>(real-time)"| LiveFeed
style StellarNetwork fill:#7B2FBE,color:#fff
style Backend fill:#2E86AB,color:#fff
style Frontend fill:#06A77D,color:#fff
style Indexer fill:#3BACB6,color:#fff
style Translator fill:#3BACB6,color:#fff
style UI fill:#0E9594,color:#fff
style Hooks fill:#0E9594,color:#fff
Location: External (https://soroban-testnet.stellar.org)
The Stellar Network is where smart contracts live and emit events. When a contract executes (e.g., a token transfer), it emits events encoded in XDR (External Data Representation) — a binary format that's cryptic to humans.
Key Concepts:
- Contracts: Smart contracts deployed on Stellar (e.g., SAC tokens, DEX protocols)
- Events: Emitted by contracts during execution, containing topics and data
- Ledgers: Blocks on Stellar, each containing transactions and events
- RPC Endpoint: The Soroban RPC server provides the
getEventsAPI
Example Raw Event:
{
"id": "0000123456-0001",
"contractId": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
"topics": ["0x00000000000000000000000000000000000000000000000000000000000000000000000000"],
"data": "0x000000140000000200000000000000000000000000000000...",
"ledger": 123456,
"timestamp": 1713456789,
"txHash": "abc123..."
}Location: lib/stellar/indexer.ts, lib/stellar/client.ts
The Event Indexer is responsible for continuously fetching new events from the Stellar RPC endpoint. It implements robust error handling to deal with rate limits (HTTP 429 errors) using exponential backoff.
Key Features:
When the RPC server returns a 429 error, the indexer doesn't give up. Instead:
- Wait 1 second
- Retry → If fails, wait 2 seconds
- Retry → If fails, wait 4 seconds
- Retry → If fails, wait 8 seconds
- Continue up to 32 seconds max
This prevents hammering the server and ensures no events are lost.
The indexer maintains a cursor (last successfully indexed ledger) to ensure continuity:
- Before fetch: Cursor = Ledger 1000
- Fetch fails: Cursor stays at 1000 (retry from same point)
- Fetch succeeds: Cursor updates to Ledger 1005
- Next poll: Starts from Ledger 1005
This guarantees no events are skipped even during failures.
setInterval(() => {
// 1. Fetch events from RPC (with retry logic)
const events = await fetchEventsWithRetry(server, contractIds, cursor);
// 2. Update cursor only on success
cursor = events.latestLedger;
// 3. Pass to translation engine
translateAndBroadcast(events);
}, 5000); // Poll every 5 secondsConfiguration:
{
pollIntervalMs: 5000, // Poll every 5 seconds
initialDelayMs: 1000, // Start with 1s retry delay
maxDelayMs: 32000, // Cap at 32s
maxRetries: 10, // Try up to 10 times
backoffMultiplier: 2 // Double delay each retry
}Location: lib/translator/registry.ts, lib/translator/blueprints/, lib/translator/decode.ts
The Translation Engine is the heart of Open-Audit. It takes raw XDR events and converts them into plain English.
Architecture:
The central lookup table that maps contract IDs to their blueprints:
Map<ContractID, TranslationBlueprint>When an event arrives:
- Look up contract ID in registry
- If found, call the blueprint's
translate()function - Return translated event with human-readable description
- If not found, mark as "cryptic"
Each contract gets its own blueprint — a file that knows how to decode that contract's events.
Example: SAC Transfer Blueprint
export function createSacTransferBlueprint(contractId: string): TranslationBlueprint {
return {
contractId,
contractName: "Stellar Asset Contract",
translate: (event: RawEvent) => {
// Decode topics[1] = from address
const from = decodeAddress(event.topics[1]);
// Decode topics[2] = to address
const to = decodeAddress(event.topics[2]);
// Decode data = amount
const amount = decodeAmount(event.data);
return {
eventType: "Transfer",
description: `${from.short} transferred ${amount.formatted} to ${to.short}`
};
}
};
}Low-level utilities for decoding XDR binary data:
decodeAddress()— Converts hex to Stellar public key (G...)decodeAmount()— Converts raw stroops to decimal amountsdecodeScVal()— Generic XDR value decoder
Translation Flow:
Raw Event
↓
Registry Lookup
↓
Blueprint Match?
├─ Yes → decode.ts → Human Text
└─ No → Mark as "Cryptic"
Location: server.ts
The WebSocket server runs alongside the Next.js app, providing real-time event streaming to connected clients.
Architecture:
// Custom Next.js server with WebSocket support
const httpServer = createServer((req, res) => {
handle(req, res); // Next.js request handler
});
const wss = new WebSocketServer({
server: httpServer,
path: "/ws/events"
});
// Broadcast to all connected clients
function broadcast(event: TranslatedEvent) {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(event));
}
});
}Message Format:
{
"raw": { /* RawEvent */ },
"description": "GABC...1234 transferred 100.00 USDC to GXYZ...5678",
"status": "translated",
"blueprintName": "Stellar Asset Contract",
"eventType": "Transfer"
}Connection Details:
- Protocol: WebSocket (ws://)
- Port: 3000 (same as Next.js)
- Path:
/ws/events - Message Format: JSON
- Broadcast Rate: Every time a new event is translated (~5s interval)
Location: app/dashboard/, components/dashboard/, lib/hooks/
The frontend is a Next.js 14 application using the App Router, providing a clean, interactive dashboard for viewing translated events.
Key Components:
The main table displaying translated events with:
- Real-time updates via WebSocket
- Search and filtering
- Pagination
- Status badges (Translated / Cryptic)
- "View Raw" button to see original XDR
Columns:
- Event Type (Transfer, Mint, Swap, etc.)
- Description (Human-readable text)
- Contract Name (Which blueprint translated it)
- Timestamp
- Status (Translated / Cryptic)
- Actions (View Raw Data)
Filters events by:
- Contract ID
- Event type
- Description text
- Status (translated vs cryptic)
- Time range
Displays real-time statistics:
- Total events indexed
- Successfully translated
- Cryptic events (need blueprints)
- Live event rate
React hook managing WebSocket connection:
const { isLive, isPaused, toggleLive, togglePause } = useLiveFeed((event) => {
// Add new event to table
setEvents([event, ...events]);
});Features:
- Auto-reconnect on disconnect
- Pause/Resume live feed
- Event buffering when paused
- Highlight new events with animation
Here's how a single event flows through the entire system:
1. Smart Contract Execution
↓
Contract emits event with topics + data (XDR binary)
2. Event Available on Stellar Network
↓
RPC endpoint getEvents() returns raw event
3. Event Indexer Polls RPC
↓
fetchEventsWithRetry() fetches events
↓
Handle HTTP 429 with exponential backoff if needed
↓
Update cursor to latest ledger
4. Translation Engine Processes Event
↓
registry.translateEvent(rawEvent)
↓
Lookup contract ID in blueprint map
↓
Blueprint.translate() decodes XDR
↓
Returns TranslatedEvent with human text
5. WebSocket Server Broadcasts
↓
broadcast(translatedEvent) sends JSON to all clients
6. Frontend Receives Event
↓
useLiveFeed hook receives WebSocket message
↓
Parse JSON to TranslatedEvent
↓
Update React state with new event
↓
EventFeedTable re-renders with new row
↓
Highlight animation plays for 600ms
Start: Token Transfer on Stellar
// User executes: Transfer 100 USDC from Alice to Bob
contractId: "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"Step 1: RPC Returns Raw Event
{
"contractId": "CDLZFC...CYSC",
"topics": [
"0x0000000f", // Event name: "transfer"
"0x00000000...alice", // From: Alice's address
"0x00000000...bob" // To: Bob's address
],
"data": "0x0000000005f5e100" // Amount: 100000000 (100 USDC)
}Step 2: Translation Engine Decodes
// SAC Transfer Blueprint
const from = decodeAddress(topics[1]); // "GABC...1234"
const to = decodeAddress(topics[2]); // "GXYZ...5678"
const amount = decodeAmount(data); // "100.00 USDC"Step 3: Translated Event Created
{
"description": "GABC...1234 transferred 100.00 USDC to GXYZ...5678",
"status": "translated",
"blueprintName": "Stellar Asset Contract",
"eventType": "Transfer"
}Step 4: Frontend Displays
┌─────────────┬──────────────────────────────────────────────┬───────────────┐
│ Event Type │ Description │ Status │
├─────────────┼──────────────────────────────────────────────┼───────────────┤
│ Transfer │ GABC...1234 transferred 100.00 USDC to │ ✅ Translated │
│ │ GXYZ...5678 │ │
└─────────────┴──────────────────────────────────────────────┴───────────────┘
- stellar-sdk — Official Stellar SDK for JavaScript/TypeScript
SorobanRpc.Server— RPC client for fetching events- XDR decoding utilities
- ws — WebSocket library for real-time broadcasting
- next — Custom Next.js server with HTTP + WebSocket
- TypeScript — Type-safe implementation
- Next.js 14 — React framework with App Router
- React 18 — UI library with Server Components
- Tailwind CSS — Utility-first styling
- shadcn/ui — Accessible component library
- WebSocket API — Native browser WebSocket client
- Vitest — Unit testing framework
- @testing-library/react — Component testing
-
Clone the repository
git clone https://github.com/coderolisa/Open-Audit.git cd Open-Audit -
Install dependencies
npm install
-
Set up environment variables
cp .env.example .env.local
-
Start the development server (with WebSocket)
npm run dev:ws
-
Open the dashboard
http://localhost:3000/dashboard
open-audit/
├── app/
│ ├── dashboard/ # Dashboard page
│ │ └── page.tsx
│ ├── layout.tsx # Root layout
│ └── page.tsx # Landing page
│
├── components/
│ ├── dashboard/ # Dashboard components
│ │ ├── EventFeedTable.tsx # Main event table
│ │ ├── SearchBar.tsx # Search & filters
│ │ ├── StatsBar.tsx # Statistics display
│ │ └── RawDataDialog.tsx # Raw XDR viewer
│ ├── theme/ # Theme components
│ └── ui/ # shadcn/ui primitives
│
├── lib/
│ ├── stellar/ # Stellar integration
│ │ ├── client.ts # RPC client
│ │ ├── indexer.ts # Event indexer
│ │ └── __tests__/ # Indexer tests
│ ├── translator/ # Translation engine
│ │ ├── registry.ts # Central registry
│ │ ├── types.ts # Type definitions
│ │ ├── decode.ts # XDR decoder
│ │ └── blueprints/ # Contract blueprints
│ ├── hooks/ # React hooks
│ │ └── useLiveFeed.ts # WebSocket hook
│ └── utils.ts # Shared utilities
│
├── server.ts # Custom Next.js + WebSocket server
└── docs/
└── good-first-issues.json # Contribution opportunities
Want to add support for a new contract? Follow these steps:
-
Create a new blueprint file
// lib/translator/blueprints/my-contract.ts import type { RawEvent, TranslationBlueprint } from "../types"; import { decodeAddress, decodeAmount } from "../decode"; export function createMyContractBlueprint( contractId: string ): TranslationBlueprint { return { contractId, contractName: "My Contract", translate: (event: RawEvent) => { // Decode event data const user = decodeAddress(event.topics[1]); const amount = decodeAmount(event.data); return { eventType: "MyEvent", description: `${user.short} performed action with ${amount.formatted}`, }; }, }; }
-
Register the blueprint
// lib/translator/registry.ts import { createMyContractBlueprint } from "./blueprints/my-contract"; function buildRegistry() { const registry = new Map(); // Add your blueprint registry.set( "YOUR_CONTRACT_ID", createMyContractBlueprint("YOUR_CONTRACT_ID") ); return registry; }
-
Test your blueprint
// lib/translator/__tests__/my-contract.test.ts import { translateEvent } from "../registry"; it("translates my contract events", () => { const rawEvent = { contractId: "YOUR_CONTRACT_ID", topics: ["0x...", "0x..."], data: "0x...", // ... }; const translated = translateEvent(rawEvent); expect(translated.status).toBe("translated"); expect(translated.description).toContain("performed action"); });
# Run all tests
npm test
# Run specific test file
npm test lib/stellar/__tests__/indexer.test.ts
# Run with coverage
npm test -- --coverageEnable verbose logging:
// In indexer.ts or registry.ts
console.log("[debug] Current cursor:", cursor);
console.log("[debug] Translated event:", translatedEvent);Check WebSocket connection:
// In browser console
const ws = new WebSocket("ws://localhost:3000/ws/events");
ws.onmessage = (e) => console.log(JSON.parse(e.data));- Problem: Stellar RPC has rate limits (HTTP 429)
- Solution: Exponential backoff retry with cursor preservation
- Impact: Zero event loss during rate limiting
- Current: Single server broadcasts to all clients
- Future: Consider Redis pub/sub for multi-server deployments
- Optimization: Blueprint lookup is O(1) via Map
- Caching: Consider caching decoded addresses for repeat events
- Virtualization: For large event lists, use react-window
- Debouncing: Search input debounced to avoid excessive re-renders
-
Database Persistence
- Store events in PostgreSQL/MongoDB
- Enable historical queries
- Persist cursor for crash recovery
-
Multi-Contract Batching
- Fetch events for multiple contracts in one RPC call
- Reduce network overhead
-
Custom ABI Upload
- Let users upload Soroban ABIs for their own contracts
- Generate blueprints automatically from ABI JSON
-
Real-Time Notifications
- Alert users when specific events occur (e.g., large transfers)
- Email/Telegram integration
-
Analytics Dashboard
- Event volume charts
- Top contracts by activity
- Translation success rate
We welcome contributions! Here's how the architecture helps contributors:
- Start here:
lib/stellar/indexer.ts— Add features like multi-contract support - Rate limit optimization: Improve retry logic or add circuit breakers
- Database integration: Add persistence layer for events
- Start here:
lib/translator/blueprints/— Add new contract support - See:
docs/good-first-issues.jsonfor blueprint requests - Example: Add Soroswap Router, Blend Protocol, Phoenix DEX
- Start here:
components/dashboard/— Improve UI/UX - Add features: Advanced filters, charts, export functionality
- Optimize: Virtual scrolling, better search performance
- Issues: https://github.com/coderolisa/Open-Audit/issues
- Discussions: https://github.com/coderolisa/Open-Audit/discussions
- Contributing Guide: CONTRIBUTING.md
Built with ❤️ for the Stellar community