A minimal TypeScript-based indexer that consumes events from Remitwise smart contracts and builds an off-chain queryable database.
This indexer monitors Soroban smart contracts on the Stellar network, processes emitted events, and stores normalized data in a SQLite database. It provides a simple query API for accessing indexed data.
- Event Monitoring: Continuously polls Stellar RPC for contract events
- Data Normalization: Parses and stores structured data from events
- SQLite Storage: Lightweight, file-based database for indexed data
- Query API: Simple interface for querying indexed entities
- Tag Support: Full support for tagging system across all entities
- Graceful Shutdown: Handles SIGINT/SIGTERM for clean shutdowns
┌─────────────────┐
│ Stellar Network │
│ (Testnet/Main) │
└────────┬────────┘
│ Events
▼
┌─────────────────┐
│ Event Indexer │
│ (TypeScript) │
└────────┬────────┘
│ Parsed Data
▼
┌─────────────────┐
│ SQLite Database │
│ (Normalized) │
└────────┬────────┘
│ Queries
▼
┌─────────────────┐
│ Query API │
│ (CLI/HTTP) │
└─────────────────┘
- Bill Payments: Tracks bills, payments, and schedules
- Savings Goals: Monitors goals, deposits, and withdrawals
- Insurance: Indexes policies and premium payments
- Remittance Split: Records split transactions
- Node.js 18+ and npm
- Access to Stellar RPC endpoint (testnet or mainnet)
- Deployed Remitwise contract addresses
- Install dependencies:
cd indexer
npm install- Configure environment:
cp .env.example .envEdit .env and set your configuration:
# Stellar Network
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
NETWORK_PASSPHRASE=Test SDF Network ; September 2015
# Contract Addresses (from your deployments)
BILL_PAYMENTS_CONTRACT=CXXXXXXXXX...
SAVINGS_GOALS_CONTRACT=CXXXXXXXXX...
INSURANCE_CONTRACT=CXXXXXXXXX...
REMITTANCE_SPLIT_CONTRACT=CXXXXXXXXX...
# Database
DB_PATH=./data/remitwise.db
# Indexer Settings
POLL_INTERVAL_MS=5000
START_LEDGER=0- Build the project:
npm run buildStart the indexer to begin monitoring and indexing events:
npm startThe indexer will:
- Initialize the SQLite database (if not exists)
- Connect to the Stellar RPC endpoint
- Start polling for events from configured contracts
- Process and store events in the database
- Continue running until stopped (Ctrl+C)
The indexer includes a CLI query interface:
View all data for a specific user:
npm start query dashboard GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOutput:
=== User Dashboard ===
Owner: GXXXXXXX...
Totals:
Savings Goals: 3 (Total: 15000)
Unpaid Bills: 2 (Total: 500)
Active Policies: 1 (Coverage: 100000)
Savings Goals:
[1] Emergency Fund: 5000/10000 [emergency, priority]
[2] Vacation: 3000/5000 [travel, leisure]
[3] New Car: 7000/20000 [vehicle]
Unpaid Bills:
[1] Electricity: 150 (Due: 2026-03-01) [utilities, monthly]
[2] Internet: 80 (Due: 2026-03-05) [utilities, monthly]
Active Policies:
[1] Health Insurance (Medical): 100000 [health, family]
List all overdue bills across all users:
npm start query overdueFind all entities with a specific tag:
npm start query tag utilitiesOutput:
=== Entities Tagged: utilities ===
Bills:
[1] Electricity: 150
[2] Internet: 80
[3] Water: 45
List all unique tags in the system:
npm start query tagsShow all active savings goals:
npm start query goalsCREATE TABLE savings_goals (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
name TEXT NOT NULL,
target_amount TEXT NOT NULL,
current_amount TEXT NOT NULL,
target_date INTEGER NOT NULL,
locked INTEGER NOT NULL,
unlock_date INTEGER,
tags TEXT NOT NULL DEFAULT '[]',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);CREATE TABLE bills (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
name TEXT NOT NULL,
amount TEXT NOT NULL,
due_date INTEGER NOT NULL,
recurring INTEGER NOT NULL,
frequency_days INTEGER NOT NULL,
paid INTEGER NOT NULL,
created_at INTEGER NOT NULL,
paid_at INTEGER,
schedule_id INTEGER,
tags TEXT NOT NULL DEFAULT '[]',
updated_at INTEGER NOT NULL
);CREATE TABLE insurance_policies (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
name TEXT NOT NULL,
coverage_type TEXT NOT NULL,
monthly_premium TEXT NOT NULL,
coverage_amount TEXT NOT NULL,
active INTEGER NOT NULL,
next_payment_date INTEGER NOT NULL,
schedule_id INTEGER,
tags TEXT NOT NULL DEFAULT '[]',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);CREATE TABLE remittance_splits (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
name TEXT NOT NULL,
total_amount TEXT NOT NULL,
recipients TEXT NOT NULL,
executed INTEGER NOT NULL,
created_at INTEGER NOT NULL,
executed_at INTEGER,
updated_at INTEGER NOT NULL
);Raw event storage for audit and debugging:
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ledger INTEGER NOT NULL,
tx_hash TEXT NOT NULL,
contract_address TEXT NOT NULL,
event_type TEXT NOT NULL,
topic TEXT NOT NULL,
data TEXT NOT NULL,
timestamp INTEGER NOT NULL
);| Event Type | Contract | Action |
|---|---|---|
goal_created |
Savings Goals | Create new goal record |
goal_deposit |
Savings Goals | Update current_amount |
goal_withdraw |
Savings Goals | Update current_amount |
bill_created |
Bill Payments | Create new bill record |
bill_paid |
Bill Payments | Mark bill as paid |
policy_created |
Insurance | Create new policy record |
split_created |
Remittance Split | Create new split record |
split_executed |
Remittance Split | Mark split as executed |
tags_add |
All Contracts | Add tags to entity |
tags_rem |
All Contracts | Remove tags from entity |
- Poll: Indexer polls Stellar RPC for new ledgers
- Fetch: Retrieves events from monitored contracts
- Parse: Converts Soroban ScVal format to JavaScript types
- Store: Saves raw event to
eventstable - Process: Updates normalized entity tables
- Checkpoint: Records last processed ledger
indexer/
├── src/
│ ├── db/
│ │ ├── schema.ts # Database schema and initialization
│ │ └── queries.ts # Query service with example queries
│ ├── types.ts # TypeScript type definitions
│ ├── eventProcessor.ts # Event parsing and processing logic
│ ├── indexer.ts # Main indexer loop
│ ├── api.ts # Query API service
│ └── index.ts # Entry point
├── package.json
├── tsconfig.json
├── .env.example
└── README.md
- Add event type to
eventProcessor.ts:
case 'new_event_type':
this.processNewEvent(data, timestamp);
break;- Implement processing function:
private processNewEvent(data: any, timestamp: number): void {
// Parse event data
// Update database
}- Add query methods to
queries.tsif needed
Use ts-node for development without building:
npm run dev- Start Stellar localnet:
stellar network start local- Deploy contracts to localnet:
cd ../
./scripts/deploy_local.sh- Update
.envwith localnet configuration:
STELLAR_RPC_URL=http://localhost:8000/soroban/rpc
NETWORK_PASSPHRASE=Standalone Network ; February 2017
START_LEDGER=1- Run indexer:
npm start- Generate test events by interacting with contracts:
# Create a savings goal
stellar contract invoke \
--id $SAVINGS_GOALS_CONTRACT \
--source alice \
-- create_goal \
--caller alice \
--name "Test Goal" \
--target_amount 10000 \
--target_date 1735689600
# Query indexed data
npm start query dashboard GXXXXXXX...- Deploy contracts to testnet:
./scripts/deploy_testnet.sh- Update
.envwith testnet configuration:
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
NETWORK_PASSPHRASE=Test SDF Network ; September 2015- Run indexer:
npm start- Poll Interval: Default 5 seconds. Adjust based on network activity
- Batch Processing: Processes all events in a ledger range atomically
- Database: SQLite with WAL mode for better concurrency
- Indexes: Created on frequently queried columns (owner, dates, status)
- No Real-time Updates: Polling-based, not push-based
- Single Instance: Not designed for horizontal scaling
- No Event Replay: Reprocessing requires database reset
- Basic Error Handling: Retries on next poll cycle
- HTTP REST API server
- GraphQL endpoint
- WebSocket support for real-time updates
- Event replay functionality
- Multi-instance coordination
- Prometheus metrics
- Advanced filtering and pagination
- Event subscription webhooks
- Verify contract addresses in
.env - Check
START_LEDGERis before contract deployment - Ensure RPC endpoint is accessible
- Verify network passphrase matches network
- Only run one indexer instance per database
- Check file permissions on
data/directory - Ensure WAL mode is enabled
- Check
indexer_statetable for last processed ledger - Verify events were emitted by contracts
- Review
eventstable for raw event data
MIT
For issues and questions:
- GitHub Issues: Remitwise-Contracts/issues
- Documentation: ARCHITECTURE.md