Start Here

Read telegram-connector-complete for the master overview (status, contracts, configuration, deployment).

The telegram_connector is the pmv2 fleet’s single Telegram interface — a bidirectional Rust microservice that owns the bot token and all Telegram I/O, consuming notifications from NATS and relaying operator commands back into the fleet.

What It Is

pmv2 is a set of independent algo-trading Rust services on a NATS JetStream spine (cohort_algo, position_manager, crypto_algo, etc.). telegram_connector is the human I/O plane:

  • Outbound relay — durable NATS pull consumer on telegram.outbound; sends each message to the Telegram Bot API. Only service that holds the bot token and touches Telegram rate limits. LIVE in production.
  • Inbound commands — long-poll getUpdates, authorize against TELEGRAM_OWNER_IDS, parse operator commands, publish to ops.commands, relay ops.results confirmations back to chat (makes the kill-switch confirmable). Deployed & activated.

It holds NO trading logic and never touches another service’s DB.

Repository Layout

/Users/levander/coding/pmv2/telegram_connector-outbound/
├── crates/
│   └── telegram_connector/
│       └── src/
│           └── consumer.rs   ← NATS JetStream MessageSource impl
├── docs/
│   └── superpowers/
├── Cargo.toml                ← workspace; async-nats 0.49 + nkeys, futures, tokio, etc.
├── Cargo.lock
└── HANDOFF.md

Repo telegram_connector (inbound, under design): /Users/levander/coding/pmv2/telegram_connector

NATS Wire Contracts

Canonical source

pmv2-contracts crate (pending publish by @positionmanager). Until then the shapes below are the agreed design. Coordinate with @positionmanager before finalizing inbound vocab.

telegram.outbound — CONSUME (JetStream, durable, dedup on Nats-Msg-Id)

{
  "msg_id": "string",           // idempotency key = Nats-Msg-Id
  "chat": "string | null",      // null => default TELEGRAM_CHAT_ID
  "text": "string",             // already-formatted body
  "parse_mode": "string | null", // "HTML" | "MarkdownV2" | null
  "kind": "string | null"       // "info" | "alert" | "trade"
}

Reference publisher: cohort_algo/.../src/telegram_outbound.rs (TelegramOutbound, subject telegram.outbound).

ops.commands — PUBLISH (operator → service)

{
  "id": "string",
  "target": "string",           // exact service name e.g. "position_manager"
  "command": "string",          // "set_mode" | "halt" | "status"
  "args": {},
  "issued_by": "string"
}

ops.results — CONSUME (service → operator confirmation)

{
  "reply_to": "string",         // command id
  "target": "string",
  "ok": true,
  "detail": "string"
}

Tech Stack

ConcernChoice
Runtimetokio (async)
NATS clientasync-nats 0.49 (workspace dep)
NATS authnkeys feature, ConnectOptions::nkey(seed)
HTTP (Telegram API)reqwest + rustls
Serdeserde + serde_json
Error handlinganyhow + thiserror
Loggingtracing
Testingembedded unit tests + tests/ integration (wiremock for Telegram API)
Edition2024, resolver “3”, Rust 1.85

Build Conventions

  • unsafe_code = "forbid" workspace lint
  • anyhow::Result throughout
  • No code comments
  • Dormant-safe: if TELEGRAM_BOT_TOKEN or NATS_URL unset → log and exit cleanly, no panic (mirror cohort_algo’s dormant guards)

Env Matrix

VarRequiredSecretNote
TELEGRAM_BOT_TOKENyesyessole holder — this service only
TELEGRAM_CHAT_IDyesnodefault chat for chat: null messages
TELEGRAM_OWNER_IDSyesnocomma-sep authorized operator user IDs
NATS_URLyesnofleet NATS spine
NATS_*_NKEY_SEEDyesyesNATS auth seed
SUPABASE_DB_URLoptionalyesonly if persisting ops state; v1 stateless

Build Status

  • Outbound relay: complete (Task 7 done). lib/bin split, full relay loop with graceful shutdown, 3 wiremock integration tests. 23 tests passing, 1 ignored (nats_smoke skeleton). Clippy clean. Commit 8f0d776.
  • Inbound commands: design-gated on ops.commands/ops.results vocab co-design with @positionmanager.