Skip to content

Liquidity-Party/keeper

Repository files navigation

Liquidity Party Keeper

A minimal, event-driven keeper for the Liquidity Party AMM. It watches the PartyConcierge singleton for queued mint requests and drives them to completion by calling executeMints(pool, maxCount), earning the protocol's keeper fees. We run it as a free service for our users and do not gate submission on profitability — we are willing to subsidize gas.

How it works

  • Idle: subscribed only to the MintQueued (and MintRequestCanceled) events on the Concierge. No polling, near-zero RPC traffic.
  • Wake: a MintQueued event marks its pool active and kicks the work loop. On startup, on a WS reconnect, and on a user cancel (which leaves a profitable-to-sweep tombstone), the keeper rescans all pools (PartyPlanner.poolCount + getAllPools, then queueLength per pool) to rebuild the active set.
  • Work loop (single in-flight tx): for each active pool it simulates executeMints; if the simulation does not revert and would execute work, it signs and broadcasts, waits for the receipt, then measures real progress (queue length shrank or the receipt carried fill/cancel logs — a executed > 0 return alone is not progress, since a transient gate just rotates the queue head). It keeps flushing a pool until its queue is empty, then drops it. When all pools are drained it returns to idle.
  • Anti-spin: a pool that makes no real progress is retried after RETRY_DELAY_MS, then parked after NO_PROGRESS_RETRIES consecutive no-progress sends until its next event/rescan.

The queue is per-pool — there is no global keep(); executeMints and queueLength both take a pool address. The event the keeper waits on is MintQueued (carries an indexed pool).

Configuration

All via environment variables (see .env.example):

Var Required Default Purpose
RPC_WS_URL yes WebSocket RPC endpoint (ws:// / wss://)
PRIVATE_KEY yes keeper wallet key (32-byte hex)
CHAIN_ID no 1 selects entry in deployment/liqp-deployments.json
CONCIERGE_ADDRESS no from deployments override
PLANNER_ADDRESS no from deployments override
MAX_COUNT no 10 queue slots per executeMints
NO_PROGRESS_RETRIES no 3 no-progress sends before parking a pool
RETRY_DELAY_MS no 13000 backoff between no-progress retries

Contract addresses default from the bundled deployment/liqp-deployments.json (a copy of ../lmsr-amm/deployment/liqp-deployments.json). Refresh it with npm run sync-deployments.

Run locally

npm install
cp .env.example .env   # then edit RPC_WS_URL and PRIVATE_KEY
npm run dev            # tsx, no build step
# or
npm run build && npm start

Docker

docker build -t liqp-keeper .
docker run --rm \
  -e RPC_WS_URL=wss://your-node/ws \
  -e PRIVATE_KEY=0x... \
  -e CHAIN_ID=1 \
  liqp-keeper

The image is a multi-stage node:22-alpine build with production-only dependencies, running as the non-root node user — small enough for the smallest VM.

Deploy to a VM (systemd)

On a single-tenant box the keeper runs directly under systemd — no container needed. bin/deploy builds locally, ships the artifacts over SSH, and installs/restarts the service. It is idempotent: the first run bootstraps a fresh Debian box (Node 22, the keeper service user, the unit, an env file), later runs just push a new build.

bin/deploy user@your-vm

The login user must be able to sudo. The unit is deployment/liqp-keeper.service; code lands in /opt/liqp-keeper owned by an unprivileged keeper user. Secrets live only on the VM in /etc/liqp-keeper.env (root-owned, chmod 600) — the script never copies your local .env. On the first deploy that file is seeded from .env.example and the service is left stopped; edit it and sudo systemctl restart liqp-keeper to go live.

sudo systemctl status liqp-keeper
sudo journalctl -u liqp-keeper -f

Local testing against anvil

  1. Start anvil and deploy via the scripts in ../lmsr-amm/bin/; note the deployed PartyConcierge and PartyPlanner addresses (or chain 31337 in ../lmsr-amm/liqp-deployments.json).
  2. Create a pool and enqueue a mint (mint(..., useQueue=true) with msg.value >= NATIVE_KEEPER_FEE) so a MintQueued event fires.
  3. Run the keeper:
    RPC_WS_URL=ws://127.0.0.1:8545 CHAIN_ID=31337 \
    CONCIERGE_ADDRESS=0x... PLANNER_ADDRESS=0x... \
    PRIVATE_KEY=0x<anvil-dev-key> npm run dev
  4. Confirm the keeper finds the queued pool, submits executeMints, drains the queue to zero, and returns to idle with no further RPC traffic.

About

Example Keeper

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors