diff --git a/README.md b/README.md index 0ec8037c..9adf83cc 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,117 @@ -# Soroban Sentinel +

+

Soroban Sentinel

+

+ The missing operations layer for deployed Soroban smart contracts. +
+ Monitor TTLs. Get alerted before expiration. Auto-extend storage. Restore archived entries. +
+
+ Install + · + Quick Start + · + Commands + · + Alerting + · + Contributing +

+

+ +
+ +## Why This Exists + +Soroban's storage model is uncommon among major smart contract platforms: **state expires.** Every ledger entry — contract instances, persistent storage, WASM code — has a Time-To-Live (TTL). When it runs out, the entry is archived. If a contract's instance entry expires, the entire contract stops working. If persistent storage entries expire, user data becomes inaccessible until someone pays to restore it. + +This is by design — state archival keeps Stellar lean and scalable. But it means **you must actively manage the lifecycle of your contract's state, or it dies.** + +There is currently no dedicated open-source tool that combines TTL monitoring, alerting, auto-extension, cost tracking, and restoration for Soroban contracts. Developers either use manual CLI commands, build ad-hoc scripts, or embed TTL extension logic directly in their contracts. + +Sentinel is the unified operations layer that handles all of this. + +> Security auditors have started flagging TTL mismanagement as a risk area in Soroban contracts. [Veridise](https://veridise.com/audits/soroban/) includes TTL handling in their audit scope. The [LayerZero Stellar endpoint audit](https://code4rena.com/audits/2026-04-layerzero-stellar-endpoint) explicitly lists TTL expiration edge cases as a concern. [OpenZeppelin's Stellar contracts library](https://docs.openzeppelin.com/stellar-contracts) deliberately leaves instance storage TTL management to the application developer. + +## Features + +- **Watch** — Register contracts and automatically discover their instance, WASM, and storage entries +- **Monitor** — Continuous TTL polling with configurable intervals via a long-running daemon +- **Alert** — Webhook and Slack notifications with severity levels, HMAC signing, and retry logic +- **Auto-Extend** — Policy-based automatic TTL extension via `ExtendFootprintTTLOp` transactions +- **Restore** — Recover archived entries via `RestoreFootprintOp` transactions +- **Cost Tracking** — Per-contract extension history with XLM costs and 30-day projections +- **Discovery** — Footprint-based storage key discovery from on-chain transaction activity +- **Local-First** — All state stored in SQLite. No external services beyond a Stellar RPC endpoint. -The missing operations layer for deployed Soroban smart contracts. +## Install -Soroban Sentinel monitors the health of your deployed contracts — tracking state TTLs, alerting before expiration, and auto-extending storage lifetimes so your contracts don't die in their sleep. +**Requirements:** Node.js 22+ -## Why this exists +```bash +# From source +git clone https://github.com/AbdulmalikAlayande/soroban-sentinel.git +cd soroban-sentinel +npm install +npm run build -Soroban's storage model is uncommon among major smart contract platforms: **state expires.** Every ledger entry, contract instances, persistent storage, WASM code etc., all have a Time-To-Live (TTL). When it runs out, the entry is archived. If a contract's instance entry expires, the entire contract stops working. If persistent storage entries expire, user data becomes inaccessible until someone pays to restore it. +# Run directly +npx tsx src/index.ts --help -This is by design, state archival keeps Stellar lean and scalable. But it means **you must actively manage the lifecycle of your contract's state, or it dies.** +# Or link globally after building +npm link +sentinel --help +``` -There is currently no dedicated open-source tool that combines TTL monitoring, alerting, auto-extension, cost tracking, and restoration for Soroban contracts. Developers either use manual CLI commands, build ad-hoc scripts, or embed TTL extension logic directly in their contracts. Sentinel is the unified operations layer that handles all of this. + -Security auditors have started flagging TTL mismanagement as a risk area in Soroban contracts. [Veridise](https://veridise.com/audits/soroban/) includes TTL handling in their audit scope. The [LayerZero Stellar endpoint audit](https://code4rena.com/audits/2026-04-layerzero-stellar-endpoint) explicitly lists TTL expiration edge cases as a concern. [OpenZeppelin's Stellar contracts library](https://docs.openzeppelin.com/stellar-contracts) deliberately leaves instance storage TTL management to the application developer. +## Quick Start -## What's working right now +```bash +# 1. Register a contract for monitoring +sentinel watch CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC \ + --network testnet \ + --name "XLM Native Token" + +# 2. Check its current TTL health +sentinel status CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC + +# 3. Set up a webhook alert (fires when TTL drops below 20,000 ledgers) +sentinel alerts add \ + --contract CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC \ + --type webhook \ + --url https://your-server.com/webhook \ + --threshold 20000 + +# 4. Start the monitoring daemon +sentinel daemon --network testnet +``` + +The daemon will check TTLs every 5 minutes, fire alerts when thresholds are crossed, send resolution notifications when TTLs recover, and auto-extend entries if guard policies are configured. + +## Commands + +### `sentinel watch ` + +Register a contract for monitoring. Connects to the Stellar RPC, discovers the contract's instance and WASM code entries, reads their TTLs, and stores everything locally. + +```bash +sentinel watch [options] +``` -Sentinel is under active development. Here's what works today: +| Option | Description | Default | +|--------|-------------|---------| +| `-n, --name ` | Human-readable contract name | — | +| `--network ` | `testnet` or `mainnet` | `testnet` | +| `-r, --rpc-url ` | Custom Stellar RPC endpoint | Network default | +| `--storage-keys ` | Comma-separated base64 XDR storage keys to track | — | -**`sentinel watch `** — Register a contract for monitoring. Connects to Stellar testnet or mainnet, discovers the contract's instance entry and WASM code entry, reads their TTLs, and stores everything locally in SQLite. +**Example output:** ``` -$ sentinel watch CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC --network testnet --name "XLM Native Token" +$ sentinel watch CDLZFC3S...CYSC --network testnet --name "XLM Native Token" ✔ Contract XLM Native Token registered successfully. @@ -34,168 +124,464 @@ $ sentinel watch CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC --netw Run 'sentinel guard CDLZFC3S...CYSC' to enable auto-extension. ``` -**`sentinel status `** — Display current TTL health, live until ledger, and remaining TTL for a watched contract in a clean, human-readable terminal format. +Entry discovery happens in layers: -**`sentinel daemon`** — Run the long-running daemon process. Every polling cycle (default 5 minutes), it re-checks contract entries, updates database states, records alert threshold crossings, resolves alerts when TTLs recover, and dispatches pending alerts to external notification channels. +1. **Deterministic** (automatic) — Contract instance and WASM code entries, derived from the contract ID and WASM hash. Always tracked. +2. **Footprint-based** (daemon) — Discovered by scanning on-chain transaction events for storage keys your contract uses. +3. **Manual** (opt-in) — Specific storage keys declared via `--storage-keys`. -**`sentinel alerts`** — Manage notification configurations. Supports subcommands to `add`, `list`, and `remove` alert channels (webhooks, Slack channel targets) per contract with custom ledger thresholds. +--- -**Alert Delivery Dispatcher** — Background delivery engine for fired alerts: -- **Webhook**: Posts a detailed JSON payload containing the event context, complete with connection timeouts and HTTP status check. -- **Slack**: Sends rich messages using Slack Block Kit, with built-in token validation. +### `sentinel status ` -**Database layer** — SQLite schema for contracts, entries, extension policies, alert configs, fired alerts, and extension history. All with foreign key cascades, upsert support, and an in-memory mode for testing. +Display current TTL health for a watched contract. Reads from the local database — no RPC call. -## What's coming next +```bash +sentinel status +``` -These are concrete next steps, not a wish list: +Shows contract name, network, last checked ledger, and a table of all tracked entries with remaining TTL in ledgers and human-readable time, plus a status indicator (OK / Warning / Critical). -- **`sentinel guard`** — Auto-extend TTLs by submitting `ExtendFootprintTTLOp` transactions -- **`sentinel costs`** — Track rent spending per contract over time -- **`sentinel restore`** — Restore archived entries via `RestoreFootprintOp` +--- -Longer term: transaction footprint-based storage key discovery, web dashboard, npm library mode for CI/CD integration, and MCP server for AI-assisted development tools. +### `sentinel daemon` -## Install +Start the long-running monitoring process. + +```bash +sentinel daemon [options] +``` + +| Option | Description | Default | +|--------|-------------|---------| +| `--network ` | Network to monitor | `testnet` | +| `--interval ` | Polling interval in milliseconds (min: 10,000) | `300000` (5 min) | +| `-r, --rpc-url ` | Custom RPC endpoint | Network default | + +Each cycle performs three phases: -Requires Node.js 22+. +1. **Monitor** — Fetches fresh TTLs for all contracts, detects threshold crossings, resolves recovered alerts +2. **Deliver** — Dispatches pending alerts to configured webhook and Slack channels +3. **Auto-Extend** — Extends TTLs for contracts with active guard policies + +The daemon handles graceful shutdown on `SIGINT`/`SIGTERM` and includes a re-entrance guard to prevent overlapping cycles. + +--- + +### `sentinel alerts` + +Manage alert configurations. Supports five subcommands. + +#### `alerts add` — Create a new alert ```bash -# From source (current method) -git clone https://github.com/AbdulmalikAlayande/soroban-sentinel.git -cd soroban-sentinel -npm install -npx tsx src/index.ts --help +sentinel alerts add [options] +``` + +| Option | Description | +|--------|-------------| +| `--contract ` | Contract ID to alert on (required) | +| `--type ` | `webhook` or `slack` (required) | +| `--url ` | Webhook POST URL (required for webhook) | +| `--channel ` | Slack channel name or ID (required for slack) | +| `--threshold ` | Fire when remaining TTL drops below this (required) | +| `--secret ` | HMAC signing secret for webhooks (auto-generated if omitted) | + +For webhook alerts, an HMAC signing secret is auto-generated (32-byte hex) if you don't provide one. The secret is displayed once at creation time — save it to verify webhook signatures on your server. See [Webhook Signing](#webhook-signing) for details. + +#### `alerts list` — View configured alerts -# npm install coming soon -# npm install -g soroban-sentinel +```bash +sentinel alerts list --contract ``` -## Usage +#### `alerts remove` — Delete an alert configuration ```bash -# See all commands -sentinel --help +sentinel alerts remove --id +``` -# Watch a contract on testnet -sentinel watch --network testnet --name "My Contract" +#### `alerts test` — Send a test alert -# Watch a contract on mainnet -sentinel watch --network mainnet --name "Production Pool" +```bash +sentinel alerts test --id +``` + +Fires a synthetic `threshold_crossed` event through the real delivery pipeline. Useful for verifying that your webhook endpoint or Slack channel is correctly configured before going live. -# Use a custom RPC endpoint -sentinel watch --network testnet --rpc-url https://my-rpc.example.com +#### `alerts history` — View past alert activity + +```bash +sentinel alerts history --contract [--limit 20] ``` -## How it works +Shows a table of fired alerts: timestamp, entry label, TTL at fire, channel type, delivery status, retry count, and resolution time. -Sentinel is an off-chain monitoring tool. It reads data from the Stellar RPC, stores it locally in SQLite, and acts on it (alerts, auto-extension). It does not run on-chain and does not require you to modify your contracts. +--- -The core workflow: +### `sentinel guard` + +Configure auto-extension policies. When enabled, the daemon automatically extends TTLs by submitting `ExtendFootprintTTLOp` transactions using a funded Stellar keypair. + +```bash +sentinel guard [options] +``` + +| Option | Description | Default | +|--------|-------------|---------| +| `--target-ttl ` | TTL to extend entries to | `100000` | +| `--threshold ` | Extend when TTL drops below this | `20000` | +| `--keypair ` | Stellar secret key (for one-time extension) | — | +| `--keypair-env ` | Env var name containing the secret key | — | +| `--auto-extend` | Enable daemon auto-extension (requires `--keypair-env`) | — | +| `--dry-run` | Simulate extension and show estimated fee | — | +| `--disable` | Disable auto-extension for this contract | — | + +**Usage modes:** + +```bash +# Check current policy +sentinel guard + +# Dry run — see estimated fee without submitting +sentinel guard --keypair S... --dry-run + +# One-time immediate extension +sentinel guard --keypair S... + +# Enable auto-extension for the daemon +sentinel guard --keypair-env STELLAR_SECRET_KEY --auto-extend + +# Disable auto-extension +sentinel guard --disable +``` + +**Security:** Secret keys are never stored in the database. When using `--auto-extend`, only the public key and the environment variable name are persisted. The daemon resolves the actual secret key from the environment at runtime. + +--- + +### `sentinel costs` + +View extension history and rent spending for a contract. + +```bash +sentinel costs [options] +``` -1. **Register** a contract with `sentinel watch` — Sentinel discovers instance and WASM entries, reads their TTLs, stores everything in `~/.soroban-sentinel/sentinel.db` -2. **Monitor** with the daemon — every 5 minutes, Sentinel re-fetches TTLs for all registered contracts and checks them against your configured thresholds -3. **Alert** when TTLs drop below thresholds — webhook POST, Slack message, or email -4. **Auto-extend** if configured — Sentinel builds and submits `ExtendFootprintTTLOp` transactions using a funded keypair you provide -5. **Track costs** — every extension is recorded with the transaction hash and XLM cost +| Option | Description | Default | +|--------|-------------|---------| +| `--period ` | Show costs for the last N days | `30` | +| `--all` | Show all history | — | -### Entry discovery +**Output includes:** -Sentinel discovers contract entries in three layers: +- Total extensions and total cost in XLM +- Breakdown by entry type (instance, wasm, persistent) with count and cost +- 30-day cost projection extrapolated from the selected period +- Recent extensions table: timestamp, entry label, old TTL → new TTL, cost in XLM, transaction hash -- **Deterministic entries (automatic):** Contract instance and WASM code entries are constructed from the contract ID and WASM hash. These are always tracked. -- **Footprint-based discovery (planned):** By watching transactions that invoke your contract, Sentinel will learn which persistent storage keys exist and start tracking their TTLs too. -- **Developer hints (supported):** You can declare specific storage keys to track via CLI flags or config. +--- + +### `sentinel restore` + +Recover archived ledger entries via `RestoreFootprintOp` transactions. + +```bash +sentinel restore [options] +``` + +| Option | Description | +|--------|-------------| +| `--keypair ` | Stellar secret key | +| `--keypair-env ` | Env var containing secret key | +| `--entry ` | Specific entry key XDR to restore (repeatable) | +| `--all` | Restore all tracked entries for the contract | + +One of `--keypair` or `--keypair-env` is required. One of `--entry` or `--all` is required (mutually exclusive). + +```bash +# Restore a specific entry +sentinel restore --keypair-env STELLAR_SECRET_KEY --entry + +# Restore all tracked entries +sentinel restore --keypair-env STELLAR_SECRET_KEY --all +``` + +## Alerting + +Sentinel delivers alerts through two channels: **webhooks** and **Slack**. Each alert includes a severity level and rich context about the affected entry. + +### Alert Lifecycle + +1. **Threshold Crossed** — During each monitoring cycle, if an entry's remaining TTL drops below a configured threshold, Sentinel fires a `threshold_crossed` alert. +2. **Delivery** — The dispatcher routes the alert to the configured channel (webhook or Slack). Failed deliveries are retried on subsequent cycles, up to 5 attempts. +3. **Resolution** — When TTL recovers past the threshold (e.g., after an extension), Sentinel fires an `alert_resolved` notification to all configured channels. + +### Severity Levels + +Severity is computed automatically based on how much TTL remains relative to the configured threshold: + +| Severity | Condition | Description | +|----------|-----------|-------------| +| **critical** | Remaining TTL < 25% of threshold, or TTL = 0 | Entry is in immediate danger of archival | +| **warning** | Remaining TTL is below threshold but above 25% | Entry needs attention soon | +| **info** | Alert resolved (TTL recovered) | Entry is healthy again | + +### Webhook Delivery + +Webhook alerts are delivered as HTTP POST requests with a JSON body: + +```json +{ + "type": "threshold_crossed", + "severity": "warning", + "contractId": "CDLZFC3S...", + "contractName": "XLM Native Token", + "network": "testnet", + "entry": { + "keyXdr": "AAAA1234...", + "type": "instance", + "label": "Contract Instance" + }, + "threshold": { + "configuredLedgers": 20000, + "currentRemainingLedgers": 8500, + "approximateTimeRemaining": "~13h 0m" + }, + "firedAtLedger": 2500000, + "timestamp": "2026-06-13T12:00:00.000Z" +} +``` + +### Webhook Signing + +Webhook requests include an HMAC-SHA256 signature in the `X-Sentinel-Signature` header for payload verification: + +``` +X-Sentinel-Signature: sha256=a1b2c3d4e5f6... +``` + +To verify on your server: + +```javascript +import { createHmac } from "node:crypto"; + +function verifySignature(payload, signature, secret) { + const expected = "sha256=" + createHmac("sha256", secret) + .update(payload) + .digest("hex"); + return signature === expected; +} +``` + +The signing secret is auto-generated when you create a webhook alert (or you can provide your own with `--secret`). It is displayed once at creation time — store it securely. + +### Slack Delivery + +Slack alerts are sent via the [Slack Web API](https://api.slack.com/methods/chat.postMessage) using Block Kit for rich formatting. Messages include severity icons, contract details, remaining TTL, and actionable hints. + +**Setup:** + +1. Create a Slack app with `chat:write` scope at [api.slack.com/apps](https://api.slack.com/apps) +2. Install the app to your workspace and copy the Bot User OAuth Token (`xoxb-...`) +3. Provide the token via environment variable: + +```bash +export SENTINEL_SLACK_TOKEN=xoxb-your-bot-token +``` + +Alternatively, store the token in your config file at `~/.soroban-sentinel/config.yaml`: + +```yaml +slackToken: "xoxb-your-bot-token" +``` + +The environment variable takes precedence over the config file. + +### Retry Policy + +Failed alert deliveries are automatically retried on subsequent daemon cycles. After **5 consecutive failures**, the alert is abandoned and no further delivery attempts are made. You can view delivery status and retry counts with `sentinel alerts history`. + +## How It Works + +Sentinel is an off-chain monitoring tool. It reads data from the Stellar RPC, stores it locally in SQLite, and acts on it (alerts, auto-extension, restoration). It does not run on-chain and does not require you to modify your contracts. + +``` + ┌─────────────────────┐ + │ Stellar Network │ + │ (testnet / mainnet) │ + └──────────┬───────────┘ + │ RPC + ┌──────────▼───────────┐ + │ Soroban Sentinel │ + │ │ + │ ┌─────────────────┐ │ + │ │ Monitor Cycle │ │ + │ │ (fetch TTLs, │ │ + │ │ detect alerts, │ │ + │ │ resolve) │ │ + │ └────────┬────────┘ │ + │ │ │ + │ ┌────────▼────────┐ │ + │ │ Dispatcher │ │ + │ │ (webhook/slack) │ │ + │ └────────┬────────┘ │ + │ │ │ + │ ┌────────▼────────┐ │ + │ │ Auto-Extend │ │ + │ │ (guard policy) │ │ + │ └─────────────────┘ │ + │ │ + │ ┌─────────────────┐ │ + │ │ SQLite DB │ │ + │ │ ~/.soroban- │ │ + │ │ sentinel/ │ │ + │ │ sentinel.db │ │ + │ └─────────────────┘ │ + └───────────────────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌────────────┐ ┌────────────┐ + │ Webhooks │ │ Slack │ │ Terminal │ + └──────────┘ └────────────┘ └────────────┘ +``` + +### The Daemon Cycle + +Every polling interval (default: 5 minutes), the daemon runs three phases: + +1. **Monitor** — For each registered contract, fetches fresh TTLs from the RPC, updates the database, checks each entry against every configured alert threshold. Fires `threshold_crossed` when TTL drops below a threshold; fires `alert_resolved` when TTL recovers. + +2. **Deliver** — Processes all undelivered alerts. Routes each to its configured channel (webhook or Slack), marks successful deliveries, increments retry counters on failures, abandons after 5 retries. + +3. **Auto-Extend** — For contracts with an active guard policy, checks which entries have TTL below the policy threshold and submits `ExtendFootprintTTLOp` transactions to extend them. Records the extension cost in XLM. ### Storage -All state is local. Sentinel stores data in `~/.soroban-sentinel/sentinel.db` (SQLite). No external services required beyond a Stellar RPC endpoint. +All state is local. Sentinel stores data in `~/.soroban-sentinel/sentinel.db` (SQLite with WAL mode). No external services required beyond a Stellar RPC endpoint. + +**Database tables:** + +| Table | Purpose | +|-------|---------| +| `contracts` | Registered contracts with network, name, WASM hash | +| `contract_entries` | Tracked ledger entries with TTLs and discovery source | +| `extension_policies` | Auto-extension rules per contract (threshold, target, keypair reference) | +| `alert_configs` | Alert channels, thresholds, and webhook secrets | +| `alerts_fired` | Fired alert records with delivery status, retry count, and resolution tracking | +| `extension_history` | Every TTL extension with transaction hash and XLM cost | + +### Configuration -Database tables: +Sentinel stores user configuration in `~/.soroban-sentinel/config.yaml`: -- `contracts` — registered contracts with network, name, WASM hash -- `contract_entries` — tracked ledger entries with TTLs and discovery source -- `extension_policies` — auto-extension rules per contract -- `alert_configs` — alert channels and thresholds -- `alerts_fired` — deduplication and resolution tracking -- `extension_history` — every TTL extension with tx hash and cost +```yaml +network: testnet +pollingIntervalSeconds: 300 +slackToken: "xoxb-..." # Optional — can also use SENTINEL_SLACK_TOKEN env var +rpcUrl: "https://..." # Optional — overrides network default +``` + +The config file is created with `0600` permissions (owner read/write only) to protect sensitive values like the Slack token. -## Project structure +## Project Structure ``` soroban-sentinel/ ├── src/ -│ ├── index.ts # CLI entry point (Commander.js) -│ ├── commands/ # CLI command handlers (thin presentation layer) -│ │ └── watch.ts -│ ├── core/ # Business logic (no CLI dependencies) -│ │ ├── watch.ts # Contract registration and discovery -│ │ └── monitor.ts # Polling cycle, threshold detection, resolution -│ ├── rpc/ # Stellar RPC client wrapper -│ │ └── client.ts -│ ├── db/ # SQLite database and repositories -│ │ ├── database.ts -│ │ ├── repositories.ts -│ │ └── schema.sql -│ ├── alerts/ # Alert dispatcher (webhook, Slack) -│ ├── daemon/ # Daemon loop and lifecycle -│ ├── logging/ # Structured logging (pino) -│ └── utils/ # Formatting, config helpers -├── tests/ # Mirrors src/ structure -│ ├── core/ -│ │ ├── watch.test.ts -│ │ └── monitor.test.ts -│ ├── db/ -│ │ └── database.test.ts -│ ├── rpc/ -│ │ └── client.test.ts -│ └── utils/ -│ └── formatting.test.ts +│ ├── index.ts # CLI entry point (Commander.js) +│ ├── commands/ # CLI command handlers (thin presentation layer) +│ │ ├── watch.ts # Contract registration +│ │ ├── status.ts # TTL health display +│ │ ├── daemon.ts # Long-running monitor +│ │ ├── alerts.ts # Alert CRUD + test + history +│ │ ├── guard.ts # Auto-extension policies +│ │ ├── costs.ts # Extension cost reporting +│ │ └── restore.ts # Archived entry recovery +│ ├── core/ # Business logic (no CLI dependencies) +│ │ ├── watch.ts # Contract registration and discovery +│ │ ├── monitor.ts # Polling cycle, threshold detection, resolution +│ │ ├── extension.ts # TTL extend, auto-extend, restore, cost recording +│ │ └── discovery.ts # Footprint-based storage key discovery +│ ├── alerts/ # Alert delivery pipeline +│ │ ├── types.ts # AlertEvent, AlertSeverity, buildAlertEvent +│ │ ├── dispatcher.ts # Routing, retry logic, delivery orchestration +│ │ ├── webhook.ts # HTTP POST with HMAC-SHA256 signing +│ │ └── slack.ts # Slack Web API + Block Kit formatting +│ ├── daemon/ # Daemon lifecycle +│ │ └── loop.ts # Start/stop, re-entrance guard, cycle orchestration +│ ├── rpc/ # Stellar RPC client wrapper +│ │ └── client.ts # Instance/WASM fetch, batch TTLs, extend, restore +│ ├── db/ # Database layer +│ │ ├── schema.sql # Full SQLite schema +│ │ ├── database.ts # Init, WAL mode, live migrations +│ │ └── repositories.ts # All query functions +│ ├── logging/ # Structured logging (pino) +│ └── utils/ # Config loader, TTL formatting +├── tests/ # Mirrors src/ structure — 238 tests across 14 files +├── .github/workflows/ # CI (test + type-check) and publish ├── package.json ├── tsconfig.json -└── vitest.config.ts +├── vitest.config.ts +├── LICENSE +└── CONTRIBUTING.md ``` -The architecture separates concerns into three layers: +**Architecture layers:** -- **Core** (`src/core/`) — pure business logic, testable without network or CLI. The daemon reuses the same core functions. -- **RPC** (`src/rpc/`) — Stellar SDK wrapper. All network calls go through here. -- **Commands** (`src/commands/`) — thin CLI layer. Parses args, calls core, formats output. +- **Commands** (`src/commands/`) — Thin CLI layer. Parses arguments, calls core, formats terminal output. No business logic. +- **Core** (`src/core/`) — Pure business logic. Testable without network or CLI. The daemon reuses the same functions. +- **RPC** (`src/rpc/`) — Stellar SDK wrapper. All network calls go through here. Handles transaction building, simulation, signing, and submission. +- **Alerts** (`src/alerts/`) — Delivery pipeline. Channel-specific formatting and transport, routing, retry management. +- **DB** (`src/db/`) — SQLite repositories. All queries centralized here. In-memory mode for tests. -## Tech stack +## Tech Stack -- **TypeScript / Node.js** — the application runtime -- **@stellar/stellar-sdk** — Stellar and Soroban RPC interactions -- **better-sqlite3** — local database (synchronous, zero external dependencies) -- **Commander.js** — CLI framework -- **pino** — structured logging -- **chalk / ora** — terminal formatting -- **Vitest** — test framework +| Package | Purpose | +|---------|---------| +| [TypeScript](https://www.typescriptlang.org/) | Application language (ESM) | +| [@stellar/stellar-sdk](https://github.com/nicktomlin/js-stellar-sdk) | Stellar and Soroban RPC interactions | +| [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) | Local database (synchronous, zero external deps) | +| [Commander.js](https://github.com/tj/commander.js) | CLI framework | +| [pino](https://github.com/pinojs/pino) | Structured JSON logging | +| [chalk](https://github.com/chalk/chalk) / [ora](https://github.com/sindresorhus/ora) | Terminal formatting and spinners | +| [yaml](https://github.com/eemeli/yaml) | Config file parsing | +| [Vitest](https://vitest.dev/) | Test framework | -## Tests +## Testing ```bash # Run all tests -npx vitest run +npm test -# Run specific test file +# Run a specific test file npx vitest run tests/core/monitor.test.ts # Watch mode npx vitest ``` -201 tests covering: +**238 tests** across **14 test files** covering: + +- **Formatting** — TTL conversion, status classification, human-readable time +- **Database** — CRUD, cascades, upserts, deduplication, alert delivery queries +- **RPC Client** — Contract instance, WASM code, batch TTL queries +- **Watch** — Registration, re-watch, SAC contracts, error handling, network isolation +- **Monitor Cycle** — TTL refresh, threshold detection, alert deduplication, resolution, fault isolation, multi-threshold escalation, partial RPC responses +- **Extension** — TTL extension, auto-extension policy evaluation, restore, cost recording +- **Alert Dispatcher** — Channel routing, retry logic, max retry cap, abandoned alerts +- **Webhook** — HMAC signing, timeout handling, HTTP error responses +- **Slack** — Token resolution, Block Kit structure, `body.ok` validation +- **CLI Commands** — Alerts add/list/remove/test/history, email type blocking +- **Config** — Load/save, defaults, parse failure handling, file permissions +- **Daemon** — Start/stop, re-entrance guard, cycle error isolation + +All tests use in-memory SQLite databases and mocked RPC responses — no network calls, no filesystem side effects. -- Formatting utilities (TTL conversion, status classification) -- Database operations (CRUD, cascades, upserts, deduplication, alert delivery, and query joining) -- RPC client (contract instance, WASM code, batch TTL queries) -- Watch command (registration, re-watch, SAC contracts, error handling, network isolation) -- Monitor cycle (TTL refresh, threshold detection, alert deduplication, resolution, fault isolation, multi-threshold escalation, partial RPC responses) -- Alert Delivery system (webhook integration, Slack Block Kit messages, dispatcher routing and retry logic) -- CLI status, daemon, and alerts commands +## FAQ -## Why TypeScript, not Rust? +### Why TypeScript, not Rust? Sentinel is an off-chain operational tool, not a smart contract. TypeScript was chosen because: @@ -203,15 +589,43 @@ Sentinel is an off-chain operational tool, not a smart contract. TypeScript was 2. Soroban developers already have Node.js in their toolchain 3. npm distribution means zero-friction installation 4. The performance requirements (periodic RPC polling) are well within Node.js capabilities -5. It maximizes the contributor pool +5. It maximizes the contributor pool — most Soroban developers know TypeScript + +### Is my secret key stored anywhere? + +No. When you configure auto-extension with `--keypair-env`, Sentinel stores only the **public key** and the **environment variable name** in the database. The actual secret key is resolved from your environment at runtime. If you use `--keypair` for a one-time operation, the key is used in-memory and never persisted. + +### What happens if the daemon crashes mid-cycle? + +Each phase (monitor, deliver, auto-extend) is wrapped in isolated error handling. A failure in one phase doesn't prevent the others from running. Alert deliveries are idempotent — if a delivery was marked successful, it won't be re-sent. If the daemon restarts, undelivered alerts will be picked up on the next cycle. + +### What networks are supported? + +Testnet (`https://soroban-testnet.stellar.org`) and Mainnet (`https://mainnet.sorobanrpc.com`). You can also point Sentinel at any custom RPC endpoint with `--rpc-url`. + +### What about email alerts? + +Email is not yet implemented. The CLI will reject `--type email` with a clear error message. Webhook and Slack are the supported channels today. + +## Roadmap + +- Web dashboard for visual TTL monitoring +- npm library mode for CI/CD integration +- MCP server for AI-assisted development tools +- Email alert channel +- Multi-contract batch operations + +## Contributing + +Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. ## License -MIT +[MIT](LICENSE) ## Author **Abdulmalik Alayande** -- GitHub: [AbdulmalikAlayande](https://github.com/AbdulmalikAlayande) +- GitHub: [@AbdulmalikAlayande](https://github.com/AbdulmalikAlayande) - X: [@The_good_man02](https://twitter.com/The_good_man02)