Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Monitoring Stellar Testnet [RPC]

Your first monitor walks the EVM Sepolia example end to end. This page is the same kind of pass for examples/source-stellar-rpc-monitor/: every seed file, annotated, as a reference for what each line does. Run that directory’s README.md first if you have not started the pipeline yet; read this one when you want to know exactly what you are looking at.

source-stellar-rpc-monitor/
├── .env.example
├── blockwatcher.toml
├── setup.sh
└── resources/
    ├── networks/stellar-testnet.json
    ├── specs/classic.json
    ├── specs/native-xlm.json
    ├── sinks/log-sink.json
    ├── monitors/xlm-payments.json
    ├── monitors/xlm-transfers.json
    └── monitors/xlm-transfer-calls.json

The seed watches three things on one stellar-rpc network: classic native XLM Payment operations, native SAC transfer events, and native SAC transfer invocations. A classic Payment and a CAP-67 transfer event on the same ledger are two decoded occurrences; the decoder does not merge them. Selector keys and spec payloads for this family are on Stellar.

.env.example

STELLAR_RPC_URL=https://soroban-testnet.stellar.org
BLOCKWATCHER_API_TOKEN=local-test-token

Two variables, both required. Nothing under resources/ ever holds STELLAR_RPC_URL’s actual value: resources/networks/stellar-testnet.json below names the variable itself, and blockwatcher resolves it from its own process environment the moment it needs it. The public SDF Testnet node needs no API key; swap the URL if you run your own. BLOCKWATCHER_API_TOKEN can be any string here: the example’s blockwatcher.toml binds the API to 127.0.0.1 only.

blockwatcher.toml

[api]
enabled = true
listen = "127.0.0.1:8081"

[[auth.tokens]]
label = "operator"
scope = "admin"
secret = "env:BLOCKWATCHER_API_TOKEN"

[metrics]
enabled = true
listen = "127.0.0.1:9091"

[storage]
module = "sqlite"
config = { path = "blockwatcher.db" }

[engine]
event_channel_capacity = 1000
sink_channel_capacity = 100
drain_deadline_ms = 5000
matcher = { module = "expr", config = {} }

Every section here is covered generally on the Configuration reference. This instance listens on 8081 / 9091 so it can run beside examples/source-evm-rpc-monitor on 8080 / 9090. [storage].config.path is a bare relative filename, blockwatcher.db, so the database lands next to the process’s current directory: inside examples/source-stellar-rpc-monitor/ if you cd there first.

setup.sh

start_ledger is absolute and required. The committed network JSON uses 1, which is a valid construct-time value (the floor the schema accepts) and the same number registry_examples/stellar_rpc.json constructs. A public node’s oldestLedger sits far above 1, so boot refuses that placeholder at the first getHealth. The script asks the endpoint for oldestLedger and latestLedger and stamps a ledger just behind the head, clamped into that window.

resources/networks/stellar-testnet.json

{
  "id": "stellar-testnet",
  "chain": "stellar",
  "source": {
    "module": "stellar-rpc",
    "config": {
      "start_ledger": 1,
      "endpoints": [
        {
          "name": "primary",
          "url_secret": "env:STELLAR_RPC_URL",
          "rate_limit": { "rps": 5 }
        }
      ],
      "poll_interval_ms": 2000,
      "request_timeout_ms": 10000
    }
  }
}

chain is stellar; source.module is stellar-rpc. There is no confirmations key: closed ledgers are not rewound. url_secret is an env:NAME reference, never the URL. After ./setup.sh, start_ledger is a ledger inside the node’s retention window rather than 1.

resources/specs/classic.json

{
  "id": "classic",
  "chain": "stellar",
  "payload": { "catalog": "classic" }
}

The built-in catalog of every classic OperationType. InvokeHostFunction becomes a function_call; every other classic type, including Payment, is an operation.

resources/specs/native-xlm.json

The payload is XDR-JSON of ScSpecEntry: a CAP-67 transfer event (from / to on the topic list, amount as scalar data) and a transfer(from, to, amount) function. The native SAC id is not in the spec; it lives on the monitors that select this spec.

resources/sinks/log-sink.json

One JSON line per match to stdout, identical to the Sepolia example’s log sink.

resources/monitors/

xlm-payments.json selects classic Payment with no address list and keeps native-asset payments (args.asset == "native"). Omitting addresses means “every address”; interest-union then blanks address filters for the whole pipeline, so getEvents is unfiltered and the decoder still drops everything that is not a selected transfer on the native SAC. Add a G-address to this monitor if you want event fetches restricted to the SAC id.

xlm-transfers.json selects events: ["transfer"] on CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC. xlm-transfer-calls.json selects functions: ["transfer"] on the same id. Operation or function interest spends getTransactions (512-ledger windows); event interest spends getEvents.

Addresses in the seed are StrKey. Decoded args.from / args.to on the match JSON are 0x hex of prefixed canonical bytes. amount.int is stroops (10,000,000 is 1 XLM).

Running

From examples/source-stellar-rpc-monitor/:

cp .env.example .env
./setup.sh
set -a; . ./.env; set +a
cargo run --bin blockwatcher -- check ./resources
cargo run --bin blockwatcher -- --config ./blockwatcher.toml --seed ./resources

check constructs every module the seed references, including resolving url_secret, so STELLAR_RPC_URL has to be set to a real http/https URL. The check inspects the scheme; the endpoint does not need to be reachable. After boot, GET /status on 127.0.0.1:8081 is the operator surface; see the example README.md for curl and jq.