Panoptes
A webhook notification system for the Cardano blockchain. Monitor addresses, track native assets, and receive real-time HTTP callbacks with enriched transaction data.
Install / Use
/learn @We-Are-Triji/PanoptesREADME
Features
Core Capabilities
- Real-Time Blockchain Sync - Powered by Argus.Sync and UtxoRPC (Demeter)
- Flexible Filtering - Monitor specific addresses or policy IDs
- HTTP Webhooks - Push notifications to any endpoint
- Automatic Retries - Smart retry logic with exponential backoff
- Rate Limiting - Per-subscription limits (60/min, 1000/hour configurable)
- Rich Payload - Enhanced transaction data with proper Bech32 addresses
- Delivery Logs - Full audit trail of webhook attempts
Developer Experience
- Modern Dashboard - React + Tailwind CSS control panel
- API Key Auth - Secure your webhook endpoints
- Comprehensive Metadata - Self-documenting payloads with data quality flags
- Built-in Diagnostics - Automatic detection of parsing issues
- Zero Data Loss Detection - Alerts when outputs are filtered
Quick Start
Prerequisites
- .NET 9.0 SDK (9.0.308+)
- Node.js 18+ (v24.11.0 recommended)
- Docker (for PostgreSQL)
- Demeter.run API Key (UtxoRPC access)
Verify Prerequisites
# Check .NET SDK version
dotnet --version
# Expected: 9.0.308 or higher
# Check Node.js and npm versions
node --version && npm --version
# Expected: v18+ (v24.11.0 recommended) and npm 8+ (11.6.1 recommended)
# Check Docker
docker --version
# Expected: Docker 20+ with Compose support
Installation
# Clone the repository
git clone https://github.com/gauciv/panoptes.git
cd panoptes
# Start PostgreSQL database (required)
docker compose up -d
# Verify database is running
docker ps
# Expected: panoptes_db container running on port 5432
# Build the backend
dotnet build
# Install frontend dependencies
cd Panoptes.Client
npm install
Configuration
GUI-Based Setup (Recommended)
Panoptes features a built-in setup wizard for easy configuration:
- Start the backend:
dotnet run --project Panoptes.Api - Start the frontend:
cd Panoptes.Client && npm run dev - Open http://localhost:5173 in your browser
- Complete the Setup Wizard with your Demeter credentials
The wizard will:
- Validate your API key by connecting to Demeter
- 🔒 Encrypt credentials using ASP.NET Core Data Protection
- Store configuration securely in PostgreSQL database
- Auto-start the blockchain sync worker
Running the Application
Backend (Terminal 1):
dotnet run --project Panoptes.Api
# API runs on http://localhost:5033
Frontend (Terminal 2):
cd Panoptes.Client
npm run dev
# Dashboard runs on http://localhost:5173
First Run:
- Open http://localhost:5173
- Complete the Setup Wizard with your Demeter.run API key
- ArgusWorker will start automatically once configured
Usage
Creating a Subscription
- Open the dashboard at
http://localhost:5173 - Click "New Subscription"
- Configure:
- Name: Descriptive label
- Target URL: Your webhook endpoint (e.g.,
https://webhook.site/unique-id) - Event Type:
transaction,block, orrollback - Filters (optional):
- Target Address: Monitor specific Cardano address
- Policy ID: Track native assets by policy
Webhook Payload Structure
{
"Event": "transaction",
"TxHash": "3eb8f9...",
"Metadata": {
"MatchReason": "Address: addr_test1...",
"InputCount": 2,
"OutputCount": 3,
"OutputsIncluded": 3,
"InputAmountsHydrated": false,
"TotalOutputAda": 10.5,
"DataLossWarning": null
},
"TotalReceived": {
"addr_test1wzn...": "8.00 ADA",
"addr_test1vry...": "2.00 ADA"
},
"Fees": {
"Lovelace": 170000,
"Ada": 0.17
},
"Inputs": [
{
"TxHash": "5a3c2d...",
"OutputIndex": 1
}
],
"Outputs": [
{
"Address": "addr_test1wzn...",
"AddressHex": "01abc...",
"Amount": {
"Lovelace": 3000000,
"Ada": 3.0
},
"Assets": [
{
"PolicyId": "362e...",
"NameHex": "546f6b656e",
"NameUTF8": "Token",
"Quantity": 100
}
],
"IsChange": null
}
],
"Block": {
"Slot": 109341540,
"Hash": "42fba...",
"Height": 2850123
},
"Timestamp": "2025-12-06T10:30:00.000Z"
}
Key Payload Features
Honest Data Quality Flags
InputAmountsHydrated: false- Inputs don't include amounts (requires querying previous transactions)IsChange: null- Without input hydration, we can't determine if an output is change or paymentDataLossWarning- Alerts when outputs are filtered due to parsing errors
TotalReceived vs Balance
TotalReceived: Shows ADA received per address in this transaction (OUTPUT ONLY)- NOT a net balance calculation (would require input amounts)
- For self-transfers, this doesn't mean the address "gained" money
Asset Names
NameHex: Source of truth (always present)NameUTF8: Human-readable (only if valid UTF-8)- Handles binary/special character asset names safely
Architecture
Project Structure
panoptes/
├── Panoptes.Api/ # ASP.NET Core Web API
│ ├── Controllers/ # REST API endpoints
│ │ ├── HealthController.cs # System health & metrics
│ │ ├── SetupController.cs # Demeter configuration
│ │ └── SubscriptionsController.cs # Webhook CRUD operations
│ ├── Workers/ # Background services
│ │ ├── ArgusWorker.cs # Blockchain sync worker
│ │ └── WebhookRetryWorker.cs # Failed delivery retry
│ ├── DTOs/ # Data transfer objects
│ ├── Auth/ # API authentication
│ └── Program.cs # Application entry point
│
├── Panoptes.Core/ # Domain layer (entities, interfaces)
│ ├── Entities/ # Database models
│ │ ├── WebhookSubscription.cs
│ │ ├── DeliveryLog.cs
│ │ ├── DemeterConfig.cs
│ │ └── SystemState.cs
│ ├── Interfaces/ # Contracts
│ └── External/ # External type definitions
│
├── Panoptes.Infrastructure/ # Data access & services
│ ├── Persistence/ # EF Core DbContext
│ ├── Services/ # Business logic
│ │ ├── PanoptesReducer.cs # Transaction processor
│ │ └── WebhookDispatcher.cs # HTTP delivery
│ ├── Providers/ # External service integrations
│ ├── Configurations/ # App configuration models
│ └── Migrations/ # EF Core migrations
│
├── Panoptes.Client/ # React frontend
│ ├── src/
│ │ ├── pages/ # Route components
│ │ │ ├── Dashboard.tsx
│ │ │ ├── Landing.tsx
│ │ │ ├── Analytics.tsx
│ │ │ ├── Health.tsx
│ │ │ ├── Settings.tsx
│ │ │ └── SubscriptionDetail.tsx
│ │ ├── components/ # Reusable UI components (40+)
│ │ ├── services/ # API client (axios)
│ │ ├── context/ # React context (Auth)
│ │ ├── hooks/ # Custom hooks
│ │ ├── layouts/ # Page layouts
│ │ └── types/ # TypeScript definitions
│ ├── vite.config.ts # Vite configuration
│ └── package.json
│
├── Panoptes.Tests/ # Unit tests
├── terraform/ # AWS infrastructure as code
├── docker-compose.yml # PostgreSQL container
└── docker-compose.prod.yml # Production deployment
Components
| Component | Description | Technology | |-----------|-------------|------------| | ArgusWorker | Blockchain sync service using Argus.Sync | .NET Background Service | | PanoptesReducer | Transaction processing, address matching, rate limiting | Argus IReducer | | WebhookDispatcher | HTTP delivery with HMAC signing | HttpClient | | WebhookRetryWorker | Background retry for failed deliveries | .NET Background Service | | AppDbContext | Entity Framework persistence | Npgsql + PostgreSQL | | Dashboard | React-based management UI | React 18 + Tailwind CSS |
Configuration
Rate Limiting
Configure per-subscription limits:
{
"MaxWebhooksPerMinute": 60,
"MaxWebhooksPerHour": 1000,
"EnableBatching": false,
"BatchWindowSeconds": 10
}
Retry Policy
Automatic retries with exponential backoff:
- Max Attempts: 5
- Backoff: 30s, 1m, 5m, 15m, 1h
- Status Tracking:
Pending,Success,Failed,Retrying
Debugging
Suspicious Zero-Value Outputs
If you see DataLossWarning in metadata:
{
"Metadata": {
"OutputCount": 3,
"OutputsIncluded": 2,
"DataLossWarning": "CRITICAL: Outputs were filtered
