Human-readable PERO-J contract events on Stellar. Instead of raw XDR bytes, users see: "Address GABC… swapped 100 USDC → 98.7 XLM on StellarSwap at ledger #4521983."
PERO-J have excellent support for classic assets but poor support for Soroban smart contracts. When a user calls swap on a DEX, explorers show raw XDR bytes — unreadable to anyone. This "black box" experience dampens DeFi, NFT, and web3 growth on Stellar.
PERO-J decodes contract calls on the fly using an ABI-like metadata registry, turning opaque XDR into plain English.
| Before | After |
|---|---|
AAAAA9hZ...[Raw XDR]...== |
Address GABC… swapped 100 USDC → 98.7 XLM on StellarSwap at ledger #4521983 |
┌─────────────────────────────────────────────────────────┐
│ PERO-J RPC / Horizon │
│ (getEvents, getTransaction) │
└────────────────────┬────────────────────────────────────┘
│ poll every 5 s
┌────────────────────▼────────────────────────────────────┐
│ Indexer (Node.js) │
│ • Fetches raw events via PERO-JRpc.getEvents() │
│ • Decodes XDR → human text using ABI registry │
│ • Stores decoded events in PostgreSQL │
│ • Exposes REST API on :3001 │
└────────────────────┬────────────────────────────────────┘
│ REST /api/*
┌────────────────────▼────────────────────────────────────┐
│ React Frontend (Vite + TanStack Query) │
│ • Home: paginated event feed + function filter │
│ • /contract/:id — ABI metadata + event history │
│ • /wallet/:address — wallet transaction history │
│ • /event/:seq — full decoded event detail │
└─────────────────────────────────────────────────────────┘
▲
┌────────────────────┴────────────────────────────────────┐
│ PERO-J Contract (Rust) │
│ • ContractRegistry — stores ABI-like metadata │
│ • EventDecoder — persists decoded events on-chain │
└─────────────────────────────────────────────────────────┘
- Rust +
wasm32-unknown-unknowntarget - Stellar CLI
- Node.js ≥ 20
- PostgreSQL
git clone https://github.com/PERO-J
cd PERO-J
cp .env.example .env
# Edit .env with your RPC URL and DATABASE_URLmake build # compile to WASM
make test # run unit tests
make deploy # deploy to testnet, prints CONTRACT_IDCopy the printed contract ID into .env as EXPLORER_CONTRACT_ID.
make indexer-install
make indexermake frontend-install
make frontend
# Open http://localhost:5173Or run both together:
make install
make dev| Function | Description |
|---|---|
init(admin) |
Initialise contract with admin address |
transfer_admin(current_admin, new_admin) |
Transfer admin rights; both parties must sign |
register_contract(caller, contract_id, meta) |
Register ABI metadata for a contract |
update_contract(caller, contract_id, meta) |
Update metadata (admin or registrant) |
get_contract(contract_id) |
Fetch contract metadata |
submit_event(...) |
Persist a decoded event (admin only) |
get_event(seq) |
Fetch event by sequence number |
get_events(from, limit) |
Paginated event list |
event_count() |
Total stored events |
| Endpoint | Description |
|---|---|
GET /health |
Liveness + lag probe — returns lag_seconds, uptime_seconds, last_ledger. HTTP 200 when healthy, 503 when lag_seconds > 30. |
GET /api/events?contract=&fn=&page= |
Paginated event list |
GET /api/events/:seq |
Single event |
GET /api/contracts/:id |
Contract ABI metadata |
POST /api/contracts |
Register contract metadata |
GET /api/wallet/:address |
Wallet event history |
Configure an external monitor (UptimeRobot, Better Uptime, or similar) to call
GET /health every 60 seconds and alert when the response is HTTP 503 or
lag_seconds > 30. This satisfies ROADMAP Tranche 2 deliverable 2.7 (< 10 s
index lag under normal load, alert threshold 30 s).
Example healthy response:
{
"status": "ok",
"uptime_seconds": 3600,
"lag_seconds": 4,
"last_ledger": 5214892,
"last_indexed_at": "2026-07-25T21:00:00.000Z"
}Example degraded response (HTTP 503):
{
"status": "degraded",
"uptime_seconds": 7200,
"lag_seconds": 142,
"last_ledger": 5214750,
"last_indexed_at": "2026-07-25T20:57:38.000Z"
}Override the alert threshold via the LAG_ALERT_THRESHOLD_S environment variable
(default 30).
The decoder recognises SEP-41 token events (transfer, mint, burn) and formats amounts with the correct symbol, alongside classic Stellar assets fetched from Horizon.
- Confirmed gap: StellarExpert and Stellar.expert (the two primary Stellar explorers) show raw XDR bytes for all Soroban contract events as of May 2026 — no human-readable decoding exists.
- Community signal: Developers in
#soroban-devon Stellar Discord regularly ask how to inspect their own contract events in a readable form. No existing tool answers this. - Comparable success: Etherscan's ABI decoder is one of its most-used features. Solscan built the same for Solana and became the primary explorer for Solana DeFi. Stellar has no equivalent for Soroban.
- Target users: Soroban dApp developers, DeFi users, NFT traders, auditors — anyone who needs to understand what is happening on-chain.
┌──────────────────────────────────────────────────────────────┐
│ Stellar Network │
│ ┌─────────────────────┐ ┌──────────────────────────────┐ │
│ │ PERO-J RPC │ │ Horizon API │ │
│ │ getEvents() │ │ Classic asset metadata │ │
│ │ getTransaction() │ │ (asset codes, issuers) │ │
│ └──────────┬──────────┘ └──────────────┬───────────────┘ │
└─────────────┼────────────────────────────┼─────────────────┘
│ poll every 5 s │ on-demand
┌─────────────▼────────────────────────────▼─────────────────┐
│ Indexer (Node.js) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ decoder.js │ │
│ │ scValToNative(topic/data) → match ABI registry │ │
│ │ → "Address GA… swapped 100 USDC → 98.7 XLM" │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ ┌──────────────────────▼───────────────────────────────┐ │
│ │ db.js (PostgreSQL) │ │
│ │ events table · contracts table │ │
│ │ indexes on contract_id, function, ledger │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ ┌──────────────────────▼───────────────────────────────┐ │
│ │ api.js (Express REST) │ │
│ │ GET /api/events · GET /api/contracts/:id │ │
│ │ GET /api/wallet/:address · POST /api/contracts │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────┬───────────────────────────────┘
│ REST /api/*
┌─────────────────────────────▼───────────────────────────────┐
│ React Frontend (Vite + TanStack Query) │
│ / — paginated feed, function filter │
│ /contract/:id — ABI metadata + event history │
│ /wallet/:addr — all events for a Stellar address │
│ /event/:seq — full decoded event detail │
└─────────────────────────────────────────────────────────────┘
▲
┌─────────────────────────────┴───────────────────────────────┐
│ PERO-J Contract (Rust) — on-chain source of truth │
│ ContractRegistry register_contract / get_contract │
│ EventDecoder submit_event / get_events / event_count │
└─────────────────────────────────────────────────────────────┘
Data flow for a decoded event:
- PERO-J contract emits an event (e.g.,
swapon StellarSwap) - Indexer fetches it via
SorobanRpc.getEvents() decoder.jscallsscValToNative()on topics/data, looks up registered ABI- Produces human-readable string → stored in PostgreSQL + submitted to on-chain contract
- Frontend queries REST API and displays the decoded event
| Document | Description |
|---|---|
| CHANGELOG.md | Full release history — what changed, what broke, what was added |
| ROADMAP.md | 3-tranche milestone plan (MVP → Testnet → Mainnet) |
| BUDGET.md | Engineering hours and cost breakdown per tranche |
| TEAM.md | Team bios and qualification evidence |
| MANIFEST.md | Full project manifest |
| stellar.toml | SEP-1 compliant network info |
PRs welcome. Please open an issue first for large changes.