Skip to content

feat(liquidator): add one-shot liquidator package - #15

Merged
Meriem-BM merged 3 commits into
mainfrom
feat-interface-ring-ui
Mar 27, 2026
Merged

feat(liquidator): add one-shot liquidator package#15
Meriem-BM merged 3 commits into
mainfrom
feat-interface-ring-ui

Conversation

@Meriem-BM

@Meriem-BM Meriem-BM commented Mar 26, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added a HeartbeatRing liquidator CLI with dry-run mode, network selection (testnet/mainnet/both), and per-run transaction cap.
  • Documentation

    • Added overarching README and liquidator-specific README plus an environment template documenting setup, CLI options, and runtime behavior.
  • Tests

    • Added unit tests covering discovery, execution, caps, and end-to-end summaries.
  • Chores

    • Added project manifests, TypeScript config, and updated ignore rules for the new liquidator project.

@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3a01e790-27d2-43bb-9cb1-9ba9cd188527

📥 Commits

Reviewing files that changed from the base of the PR and between 6b8d08b and dede4cc.

📒 Files selected for processing (1)
  • README.md
✅ Files skipped from review due to trivial changes (1)
  • README.md

📝 Walkthrough

Walkthrough

Adds a new Bun-based liquidator/ CLI that scans HeartbeatRing contracts on Rootstock testnet/mainnet, detects delinquent members, and optionally submits liquidation transactions. Implements runtime config loading, Viem gateway, types, runner pipeline, CLI parsing, tests, and documentation.

Changes

Cohort / File(s) Summary
Project & manifests
/.gitignore, package.json, liquidator/package.json, liquidator/tsconfig.json
Repository and project-level manifests for the new Bun TypeScript project; .gitignore updated to ignore liquidator/node_modules and liquidator/.env.
Env & docs
liquidator/.env.example, liquidator/README.md, README.md
Adds environment template and README describing prerequisites, setup, CLI flags (--network, --max-tx, --dry-run, --help), workflows (test/dry-run/live), and runtime notes; top-level README updated with component overviews and suggested flows.
Type definitions
liquidator/src/types.ts
New domain and runtime types: CLI options, network/runtime configs, liquidation candidate/report shapes, TxStatus, and LiquidatorGateway interface.
CLI & parsing
liquidator/src/cli.ts, liquidator/src/cli-options.ts, liquidator/src/parse-utils.ts
Command-line entrypoint, usage/help text, argument parsing (supports --network and --network=<value>, --max-tx forms), positive-integer parsing helper, and top-level error handling.
Configuration loader
liquidator/src/config.ts
Loads and validates per-network env vars (RPC, factory address, optional private key), applies CLI max-tx override or env default, normalizes addresses, and aggregates validation errors into a single thrown Error when present.
Chains & ABIs
liquidator/src/chains.ts, liquidator/src/abi.ts
Defines Rootstock testnet/mainnet chain objects and exports factoryAbi and heartbeatRingAbi for contract interactions.
Viem gateway
liquidator/src/viem-gateway.ts
Creates a Viem-backed LiquidatorGateway that manages per-network public/wallet clients, performs contract reads, submits liquidate writes when a signer is configured, and waits for receipts; explicit throws for missing configs or signer.
Runner & orchestration
liquidator/src/runner.ts
Core scanning and execution pipeline: fetch rings, filter by phase, detect delinquent members, deterministic ordering, apply per-run tx cap, execute liquidations (or dry-run), isolate errors per candidate, and aggregate per-network and overall summaries.
Logger utility
liquidator/src/logger.ts
Defines Logger interface plus consoleLogger and silentLogger implementations.
Tests
liquidator/test/runner.test.ts
Unit tests mocking LiquidatorGateway to verify scanning, cap enforcement, dry-run behavior, error handling, and aggregated run summaries.
sequenceDiagram
    participant CLI as CLI Entry
    participant Config as Config Loader
    participant Runner as Runner
    participant Gateway as Viem Gateway
    participant Chain as Blockchain
    participant Logger as Logger

    CLI->>CLI: Parse CLI args
    CLI->>Config: loadRuntimeConfig(env, options)
    Config->>Config: Validate RPC, factory, privateKey
    Config-->>CLI: RuntimeConfig

    CLI->>Runner: runLiquidator(gateway, configs, options)

    rect rgba(100, 150, 200, 0.5)
        note over Runner,Gateway: Scan Phase
        Runner->>Gateway: getAllRings(network)
        Gateway->>Chain: Call factory.getAllRings()
        Chain-->>Gateway: rings[]
        Gateway-->>Runner: rings[]

        loop For each ring
            Runner->>Gateway: getPhase(network, ring)
            Gateway->>Chain: Read ring.phase
            Chain-->>Gateway: phase

            alt phase == Active
                Runner->>Gateway: getRingMembers(network, ring)
                Gateway->>Chain: Call ring.getRing()
                Chain-->>Gateway: members[]

                loop For each member
                    Runner->>Gateway: isDelinquent(network, ring, member)
                    Gateway->>Chain: Check delinquency
                    Chain-->>Gateway: bool

                    alt isDelinquent
                        Runner->>Runner: Add to candidates
                    end
                end
            end
        end
    end

    rect rgba(200, 150, 100, 0.5)
        note over Runner,Gateway: Execute Phase
        Runner->>Runner: Sort candidates (deterministic)
        Runner->>Runner: Apply maxTxPerRun cap

        loop For each candidate (within cap)
            alt dryRun
                Runner->>Logger: Log candidate
            else
                Runner->>Gateway: liquidate(network, ring, target)
                Gateway->>Chain: Submit liquidate txn
                Chain-->>Gateway: txHash
                Runner->>Gateway: waitForReceipt(txHash)
                Gateway->>Chain: Poll receipt
                Chain-->>Gateway: receipt
                Runner->>Runner: Update counters
            end
        end
    end

    Runner->>Logger: Log RunSummary
    Runner-->>CLI: RunSummary
    CLI-->>CLI: Print JSON summary + exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hop through rings on Rootstock night,
Sniffing delinquent marks by moonlight,
Bun scripts ready, viem in paw,
One-shot liquidations—orderly law,
Hooray, a tidy run without a fight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a complete one-shot liquidator package to the repository.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-interface-ring-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
liquidator/src/cli.ts (1)

50-53: Log stack traces on fatal failures to improve incident triage.

Line 50–53 currently emit only the message, which drops useful context when diagnosing production issues.

🛠️ Proposed improvement
 main().catch((error) => {
-  const message = error instanceof Error ? error.message : String(error);
-  console.error(`[liquidator] Fatal: ${message}`);
+  if (error instanceof Error) {
+    console.error(`[liquidator] Fatal: ${error.message}`);
+    if (error.stack) {
+      console.error(error.stack);
+    }
+  } else {
+    console.error(`[liquidator] Fatal: ${String(error)}`);
+  }
   process.exit(1);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@liquidator/src/cli.ts` around lines 50 - 53, The fatal error handler for
main() currently logs only error.message; update the catch to log the full stack
(or the Error object) to preserve diagnostic context: inside
main().catch((error) => { ... }) build a log that includes error instanceof
Error ? error.stack || error.message : String(error) (or simply
console.error(error)) before calling process.exit(1), so the stack trace is
emitted for triage while keeping the existing exit behavior.
liquidator/.env.example (1)

2-9: Optional: reorder env keys to satisfy dotenv-linter warnings.

Line 2–9 ordering triggers the current linter warnings. Reordering keeps CI/lint output clean.

🧹 Proposed reorder
 # Testnet runtime
-LIQUIDATOR_TESTNET_RPC_URL=https://public-node.testnet.rsk.co
 LIQUIDATOR_TESTNET_FACTORY_ADDRESS=0x52C37e8364290F3A5f293D6D4ef9852B2d7D0542
 LIQUIDATOR_TESTNET_PRIVATE_KEY=REPLACE_WITH_TESTNET_PRIVATE_KEY
+LIQUIDATOR_TESTNET_RPC_URL=https://public-node.testnet.rsk.co

 # Mainnet runtime
-LIQUIDATOR_MAINNET_RPC_URL=https://public-node.rsk.co
 LIQUIDATOR_MAINNET_FACTORY_ADDRESS=0x0000000000000000000000000000000000000000
 LIQUIDATOR_MAINNET_PRIVATE_KEY=REPLACE_WITH_MAINNET_PRIVATE_KEY
+LIQUIDATOR_MAINNET_RPC_URL=https://public-node.rsk.co
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@liquidator/.env.example` around lines 2 - 9, Reorder the environment variable
entries so they satisfy dotenv-linter ordering rules: within each section
(Testnet and Mainnet) sort the keys alphabetically (e.g.,
LIQUIDATOR_TESTNET_FACTORY_ADDRESS, LIQUIDATOR_TESTNET_PRIVATE_KEY,
LIQUIDATOR_TESTNET_RPC_URL for Testnet; LIQUIDATOR_MAINNET_FACTORY_ADDRESS,
LIQUIDATOR_MAINNET_PRIVATE_KEY, LIQUIDATOR_MAINNET_RPC_URL for Mainnet) and keep
the separating comment lines intact; update the block containing
LIQUIDATOR_TESTNET_* and LIQUIDATOR_MAINNET_* keys accordingly so the linter
warnings disappear.
liquidator/src/types.ts (1)

61-74: Extract RunSummary.perNetwork inline object into a named type.

Line 67–74 uses an inline structural type that’s likely reused in tests/formatters. Naming it improves readability and reduces drift risk.

♻️ Proposed refactor
 export type RunSummary = {
+  // consider placing near other report types
+  // export type RunSummaryPerNetwork = { ... }
   activeRings: number;
   delinquentTargetsFound: number;
   dryRun: boolean;
   maxTxPerRun: number;
   networks: NetworkKey[];
-  perNetwork: Array<{
-    activeRings: number;
-    delinquentTargetsFound: number;
-    network: NetworkKey;
-    ringsScanned: number;
-    txFailed: number;
-    txSucceeded: number;
-  }>;
+  perNetwork: RunSummaryPerNetwork[];
   ringsScanned: number;
   skippedByCap: number;
   txAttempted: number;
   txFailed: number;
   txSucceeded: number;
 };
+
+export type RunSummaryPerNetwork = {
+  activeRings: number;
+  delinquentTargetsFound: number;
+  network: NetworkKey;
+  ringsScanned: number;
+  txFailed: number;
+  txSucceeded: number;
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@liquidator/src/types.ts` around lines 61 - 74, Extract the inline object used
in RunSummary.perNetwork into a new exported named type (e.g., PerNetworkSummary
or NetworkRunSummary), replace the inline definition in RunSummary with
perNetwork: PerNetworkSummary[] and export the new type so tests/formatters can
import it; update any local references to the inline shape (including
tests/formatters) to use the new type name and ensure the new type includes the
same fields: activeRings, delinquentTargetsFound, network, ringsScanned,
txFailed, txSucceeded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@liquidator/.env.example`:
- Around line 4-9: The example env contains syntactically valid-looking private
keys (LIQUIDATOR_TESTNET_PRIVATE_KEY and LIQUIDATOR_MAINNET_PRIVATE_KEY);
replace their values with clearly non-usable placeholders (e.g.,
<YOUR_PRIVATE_KEY_HERE> or PRIVATE_KEY_PLACEHOLDER) so they cannot be
accidentally used, and keep the existing variable names
(LIQUIDATOR_TESTNET_PRIVATE_KEY, LIQUIDATOR_MAINNET_PRIVATE_KEY) so consumers
know where to insert real keys.

In `@liquidator/README.md`:
- Around line 20-29: The README currently misstates the config requirements and
env var names; update the listed environment variables and explanatory text to
match loadRuntimeConfig() behavior: make LIQUIDATOR_*_PRIVATE_KEY optional for
non-live/test runs and required only for live runs, and correct the cap variable
name to LIQUIDATOR_MAX_TX_PER_RUN (not MAX_TX_PER_RUN). Locate the sections
around the bullet list and lines 86-90 and change the wording to indicate which
keys are required for live runs only and that LIQUIDATOR_MAX_TX_PER_RUN (default
5) is the optional cap.

In `@liquidator/src/config.ts`:
- Line 1: isValidPrivateKey currently only checks hex format/length and lets
invalid secp256k1 scalars (e.g. 0x00...00) pass; move real validation earlier by
attempting to derive a signer/account from the key and rejecting on failure.
Update isValidPrivateKey to convert the hex key and call the same
account-derivation used by createViemGateway/privateKeyToAccount (or call
privateKeyToAccount directly) inside a try/catch and return false on any error
(and true only if account creation succeeds), so invalid scalars are rejected
before runtime.

In `@liquidator/src/runner.ts`:
- Around line 141-145: The flattened candidate list is biased because
scanNetwork/scanNetworks appends full per-network candidate arrays then
applyTxCap slices the combined list; change the logic to enforce the
transaction-cap per network before flattening (or interleave candidates across
networks). Concretely, inside the loop that calls scanNetwork (referencing
scanNetwork and the perNetwork/report.candidates variables) trim
report.candidates to the per-network cap computed from applyTxCap or refactor
applyTxCap to accept and enforce caps per-network (and consider
selectedNetworks("both") ordering). Ensure candidates.push only receives the
already-capped arrays so a backlog on one network cannot consume all global
slots.
- Around line 49-59: The call to gateway.getAllRings(network) is outside the
scan's error isolation so any RPC failure can abort the whole scan; wrap the
getAllRings(...) call and subsequent getAddress mapping in a try/catch (or move
it inside the existing try block used for the scan) so failures are caught
per-network, log the error (with context including the network) and set rings =
[] (and ringsScanned = 0) so the NetworkScanReport can still be returned;
reference the gateway.getAllRings, getAddress and the NetworkScanReport creation
so you modify that code path only.

---

Nitpick comments:
In `@liquidator/.env.example`:
- Around line 2-9: Reorder the environment variable entries so they satisfy
dotenv-linter ordering rules: within each section (Testnet and Mainnet) sort the
keys alphabetically (e.g., LIQUIDATOR_TESTNET_FACTORY_ADDRESS,
LIQUIDATOR_TESTNET_PRIVATE_KEY, LIQUIDATOR_TESTNET_RPC_URL for Testnet;
LIQUIDATOR_MAINNET_FACTORY_ADDRESS, LIQUIDATOR_MAINNET_PRIVATE_KEY,
LIQUIDATOR_MAINNET_RPC_URL for Mainnet) and keep the separating comment lines
intact; update the block containing LIQUIDATOR_TESTNET_* and
LIQUIDATOR_MAINNET_* keys accordingly so the linter warnings disappear.

In `@liquidator/src/cli.ts`:
- Around line 50-53: The fatal error handler for main() currently logs only
error.message; update the catch to log the full stack (or the Error object) to
preserve diagnostic context: inside main().catch((error) => { ... }) build a log
that includes error instanceof Error ? error.stack || error.message :
String(error) (or simply console.error(error)) before calling process.exit(1),
so the stack trace is emitted for triage while keeping the existing exit
behavior.

In `@liquidator/src/types.ts`:
- Around line 61-74: Extract the inline object used in RunSummary.perNetwork
into a new exported named type (e.g., PerNetworkSummary or NetworkRunSummary),
replace the inline definition in RunSummary with perNetwork: PerNetworkSummary[]
and export the new type so tests/formatters can import it; update any local
references to the inline shape (including tests/formatters) to use the new type
name and ensure the new type includes the same fields: activeRings,
delinquentTargetsFound, network, ringsScanned, txFailed, txSucceeded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ec41fb71-13ae-40d7-b354-6bf470a92e09

📥 Commits

Reviewing files that changed from the base of the PR and between 9d222fb and e87288f.

⛔ Files ignored due to path filters (1)
  • liquidator/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • .gitignore
  • liquidator/.env.example
  • liquidator/README.md
  • liquidator/package.json
  • liquidator/src/abi.ts
  • liquidator/src/chains.ts
  • liquidator/src/cli-options.ts
  • liquidator/src/cli.ts
  • liquidator/src/config.ts
  • liquidator/src/logger.ts
  • liquidator/src/parse-utils.ts
  • liquidator/src/runner.ts
  • liquidator/src/types.ts
  • liquidator/src/viem-gateway.ts
  • liquidator/test/runner.test.ts
  • liquidator/tsconfig.json
  • package.json

Comment thread liquidator/.env.example
Comment thread liquidator/README.md Outdated
Comment thread liquidator/src/config.ts
Comment thread liquidator/src/runner.ts
Comment thread liquidator/src/runner.ts
@Meriem-BM
Meriem-BM merged commit b177a37 into main Mar 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant