Skip to main content

Operator Setup

This guide takes you from a fresh checkout to a running keeper that monitors the Blend pool, competes for liquidation auctions, and books profit back to the vault. Everything here targets the current testnet deployment — see Contract Addresses for the authoritative list.

:::info Tranche 2 reality The keeper is the off-chain daemon in keeper/ (Go). It self-registers on first run by calling KeeperRegistry.register, which pulls exactly min_stake USDC (100 USDC on testnet) from your keeper account into the registry contract. There is no separate CLI subcommand for registering or staking — running the binary does it. :::

Prerequisites

  • Go 1.24+ (install). The keeper module declares go 1.24.0.
  • A Stellar keypair (S... secret) dedicated to this keeper, funded with XLM for transaction fees.
  • At least 200 USDC on testnet in the keeper account: 100 USDC is locked as stake on registration, and you want margin left over to pay fees and act as the keeper's working float. On testnet this is Circle testnet USDC (from the Circle faucet); mainnet (Tranche 3) will use Circle's mainnet USDC.
  • An always-on machine or VPS — keepers race each other, so downtime means lost fills and, if a draw is left outstanding, slashing risk.

:::warning Use a dedicated keypair Use a fresh keypair for each keeper instance. The secret key (KEEPER_SECRET) signs every transaction and controls the staked USDC. Do not reuse a key that holds personal funds. :::

1. Clone and build the binary

The keeper is a single main package at the root of keeper/ — there is no cmd/ subdirectory.

git clone https://github.com/Nectar-Network/nectar.git
cd nectar/keeper
go build -o nectar-keeper .

You now have a nectar-keeper binary in keeper/. To sanity-check the toolchain before building, run the test suite (race detector on, as CI does):

go test -race ./...

2. Set the required environment variables

The keeper is configured entirely through environment variables — there is no config file. It loads a .env file from the working directory at startup (via godotenv, best-effort) and then reads the process environment. Create a .env in keeper/ or export the variables directly.

These four are required — a missing one prints missing required env: <KEY> to stderr and exits with status 1:

export KEEPER_SECRET="S..." # keeper Stellar secret key (signs all txs)
export KEEPER_NAME="my-keeper" # human-readable name, used at registration
export REGISTRY_CONTRACT="CD33A7IGNCOLVQ4EEINBVMVA7IHWXGN57R6YLE5AJEEKPA6VKC2E4IQD"
export VAULT_CONTRACT="CDOGQY7NAE3BP4Q7RWBCBLW23Z36RNWNDNXX5DWNIEVMFEWP3GVEPXLR"

:::note KEEPER_NAME default KEEPER_NAME is technically optional — it defaults to nectar-keeper-1. Set it to something distinctive so your operator is identifiable on the keeper leaderboard. :::

To actually monitor for liquidations you also want the Blend pool and the network endpoints. These have testnet defaults, so the minimum useful testnet config adds just the pool:

# Blend pool to monitor. Empty disables the liquidation cycle (vault-monitor-only).
export BLEND_POOL="CCEBVDYM32YNYCVNRXQKDFFPISJJCV557CDZEIRBEE4NCV4KHPQ44HGF"

# USDC token — required for collateral-swap proceeds and stale-draw recovery.
export USDC_CONTRACT="CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"

The endpoints below default to testnet and only need to be set if you are overriding them:

VariableDefault
SOROBAN_RPChttps://soroban-testnet.stellar.org:443
HORIZON_URLhttps://horizon-testnet.stellar.org
NETWORK_PASSPHRASETest SDF Network ; September 2015
POLL_INTERVAL10 (seconds; must be in [3, 300])
MIN_PROFIT1.02 (minimum lot/bid ratio to fill; must be > 0)
API_PORT8080 (HTTP API / SSE / metrics)

See Configuration for the complete variable list, including DEX (SOROSWAP_ROUTER, PHOENIX_ROUTER, SLIPPAGE_BPS) and DeFindex (DEFINDEX_VAULT, DEFINDEX_DRIFT_BPS) options.

:::tip USDC_CONTRACT is not optional in practice USDC_CONTRACT defaults to empty, but leaving it empty disables two safety behaviors: crediting USDC lots directly and stale-draw recovery (returning capital the keeper drew but never repaid). Always set it on a keeper that draws vault capital. :::

3. Fund the keeper account

Your keeper address needs both assets before it can register and operate.

  • XLM for transaction fees. Fund from Friendbot:

    curl "https://friendbot.stellar.org/?addr=<YOUR_KEEPER_G_ADDRESS>"
  • USDC — at least the 100 USDC stake plus working margin. On testnet this is Circle testnet USDC, which is faucet-issued (not admin-minted). Request it for your keeper's G… address from the Circle testnet faucet (select the Stellar network). Once each keeper holds its stake, the repo's helper script registers them:

    ./scripts/register-keepers-testnet.sh

    On-chain, amounts are in 7-decimal stroops (200 USDC = 2000000000). Confirm the faucet balance lands before registering.

:::warning Insufficient stake fails hard Registration transfers exactly min_stake (100 USDC) from your account into the registry. If your balance is below that, the token transfer panics inside the contract — it surfaces as a host error, not a typed ContractError, and your keeper is not registered. Confirm the balance lands before running. :::

4. Start the daemon

./nectar-keeper

On startup the keeper, in order:

  1. Loads .env and parses KEEPER_SECRET.
  2. Calls KeeperRegistry.register(operator, name). The contract pulls min_stake USDC and creates your KeeperInfo. Registration is idempotent — if you are already registered, the AlreadyRegistered error is swallowed and logged as a warning ("registration skipped (may already be registered)"); it is never fatal.
  3. Builds its adapters — always the Blend adapter, plus a DeFindex adapter if DEFINDEX_VAULT is set. A DEX swap client is wired in only when SOROSWAP_ROUTER or PHOENIX_ROUTER is set.
  4. Starts the HTTP API on API_PORT (/api/state, /api/events SSE, /api/performance, /metrics, /healthz).
  5. Begins the poll loop on a POLL_INTERVAL-second ticker, handling SIGTERM/SIGINT for clean shutdown.

Each cycle the keeper first runs stale-draw recovery (returns any capital drawn but unreturned in a prior cycle, capped at what it owes), then for each adapter scans for tasks, sorts them highest-priority first, and executes them. For Blend: it loads pool positions, flags any with health factor < 1.0, creates and fills the user-liquidation auction when the lot/bid ratio clears MIN_PROFIT, swaps seized collateral to USDC, and returns drawn capital plus profit via return_proceeds.

:::info No coordinator Multiple keepers race the same auction. The first confirmed fill wins; losers receive ErrAlreadyFilled, return their unspent draw unchanged (no profit, no loss), and move on. There is no central coordinator and no single point of failure. :::

5. Verify it is monitoring

Liveness — the health endpoint returns 200 OK once the API is up:

curl -i http://localhost:8080/healthz

Live state/api/state returns the keeper's current view: registered keepers, pool positions with health factors, recent event log lines, and vault state:

curl http://localhost:8080/api/state

Metrics/metrics exposes Prometheus counters, including cycle and liquidation totals and vault TVL (7-decimal):

curl http://localhost:8080/metrics
# HELP nectar_cycles_total Number of keeper poll cycles
nectar_cycles_total 12
# HELP nectar_liquidations_total Number of successful auction fills
nectar_liquidations_total 0
# HELP nectar_vault_tvl Vault total USDC (7 decimals)
nectar_vault_tvl 10100000000
# HELP nectar_sse_active Active SSE connections
nectar_sse_active 0

On-chain registration — confirm the registry recorded your stake by reading get_keeper. A registered keeper returns its KeeperInfo (with stake = 1000000000, i.e. 100 USDC in stroops); an unregistered one returns NotRegistered:

stellar contract invoke \
--id CD33A7IGNCOLVQ4EEINBVMVA7IHWXGN57R6YLE5AJEEKPA6VKC2E4IQD \
--source <YOUR_KEEPER_SECRET> \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015" \
-- get_keeper --operator <YOUR_KEEPER_G_ADDRESS>

Dashboard — your operator appears on the keeper leaderboard once the registry read picks it up. Execution count, win rate, average response time, and total profit populate as you complete fills.

:::tip Watch the live log Tail the event stream to see scans and fills as they happen:

curl -N http://localhost:8080/api/events

:::

Running as a service

For a durable deployment, run the binary under a process manager that restarts on exit. A minimal systemd unit:

[Unit]
Description=Nectar Keeper
After=network.target

[Service]
Type=simple
User=nectar
EnvironmentFile=/etc/nectar/keeper.env
ExecStart=/opt/nectar/nectar-keeper
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

The keeper is stateless — all state is read from chain each cycle — so it restarts safely. Stale-draw recovery means a crash mid-fill is repaired on the next start, provided USDC_CONTRACT is set. For containerized and Railway deployments, see Docker.

:::danger Keep XLM on hand Running out of XLM mid-fill can leave a draw outstanding. If return_proceeds is never called within slash_timeout (1 hour on testnet) of the draw, anyone can permissionlessly call slash and take slash_rate_bps (10%) of your stake. See Staking & Slashing. :::

What's next