Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion hodlmm-bin-guardian/AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ Always return strict JSON:
```json
{
"status": "success | error",
"action": "HOLD | REBALANCE | CHECK | <error description>",
"action": "HOLD | REBALANCE | PINNED | CHECK | <error description>",
"data": {
"in_range": "boolean | null",
"active_bin": "number",
"at_grid_edge": "boolean — active bin at unsigned grid edge (0 or 1000), only when the read verifiably carried it",
"pinned": "boolean — at_grid_edge AND slippage gate failing; pool price is frozen by contract design and divergence is structural",
"user_bin_range": "{ min, max, count, bins } | null",
"can_rebalance": "boolean",
"refusal_reasons": "string[] | null",
Expand All @@ -70,3 +72,7 @@ Always return strict JSON:
"error": "null | { code, message, next }"
}
```

## PINNED (added 2026-07-15)

`PINNED` fires when the pool's active bin sits at the grid edge AND the slippage gate is failing — the divergence is structural (pool price frozen by contract design), so the slippage gate can never clear on its own. Do **not** rebalance or blind-swap while pinned; route to the exit/withdraw path (withdraw minimums remain enforceable) or escalate to a human. Guarded against degraded API reads: a bins response missing the active bin or its price cannot produce PINNED.
29 changes: 28 additions & 1 deletion hodlmm-bin-guardian/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,11 @@ All outputs are strict JSON to stdout.
| Field | Type | Description |
|---|---|---|
| `status` | `"success" \| "error"` | Overall result |
| `action` | `string` | `HOLD`, `REBALANCE`, or `CHECK` with reason |
| `action` | `string` | `HOLD`, `REBALANCE`, `PINNED`, or `CHECK` with reason |
| `data.in_range` | `boolean \| null` | `null` if no wallet provided |
| `data.active_bin` | `number` | Pool's current active bin ID |
| `data.at_grid_edge` | `boolean` | Active bin at the grid edge (unsigned 0 or 1000); only true when the bins read verifiably carried the active bin |
| `data.pinned` | `boolean` | At grid edge AND slippage gate failing — structural divergence; see PINNED |
| `data.user_bin_range` | `{min,max,count,bins} \| null` | User's liquidity bin range |
| `data.can_rebalance` | `boolean` | Whether all safety gates pass |
| `data.refusal_reasons` | `string[] \| null` | Why REBALANCE is blocked |
Expand Down Expand Up @@ -232,3 +234,28 @@ All outputs are strict JSON to stdout.
Winner of AIBTC x Bitflow Skills Pay the Bills competition.
Original author: @cliqueengagements
Competition PR: https://github.com/BitflowFinance/bff-skills/pull/39

## PINNED detection (2026-07-15 field audit F-5)

When the pool's active bin is at the grid edge (unsigned 0 or 1000) AND pool-vs-market divergence exceeds the slippage cap, the guardian emits a distinct `PINNED` action (plus `at_grid_edge` / `pinned` data fields) instead of an unpassable slippage HOLD. While pinned, pool price is frozen by contract design and the divergence is structural — do not rebalance or blind-swap; route to the exit/withdraw path (withdraw minimums remain enforceable) or escalate. There is no official pinned signal in the Bitflow API; this is derived from contract behavior. Observed live on dlmm_3 (2026-07-13): 1.4–4.8% structural divergence for ~36h that deadlocked slippage-gated automation.

### Example PINNED output (fields added 2026-07-15; both `run` examples above predate `at_grid_edge`/`pinned`)

```json
{
"status": "success",
"action": "PINNED — pool is at the grid edge (active bin 0) with structural pool-vs-market divergence 2.74%. The slippage gate cannot clear while pinned. Do NOT rebalance or blind-swap; route to the exit/withdraw path (withdraw minimums remain enforceable while pinned) or escalate to a human.",
"data": {
"in_range": false,
"active_bin": 0,
"at_grid_edge": true,
"pinned": true,
"can_rebalance": false,
"refusal_reasons": ["price slippage 2.74% > 0.5% cap"],
"slippage_ok": false,
"slippage_pct": 2.74
}
}
```

Normal (non-pinned) `run` output now also carries `"at_grid_edge": false, "pinned": false`.
25 changes: 24 additions & 1 deletion hodlmm-bin-guardian/hodlmm-bin-guardian.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,30 @@ async function runGuardian(wallet?: string, poolId?: string): Promise<{

const canRebalance = refusals.length === 0;

// ── Grid-edge / pinning detection (2026-07-15 field audit F-5) ──────────────
// The DLMM active bin cannot move past the grid edge (dlmm-core: it only
// decrements while bin-id > MIN_BIN_ID, mirrored at MAX). At the edge the
// pool price freezes while the market keeps moving, so pool-vs-market
// "slippage" becomes STRUCTURAL — the slippage gate can never clear
// (observed live: 1.4–4.8% divergence for ~36h on dlmm_3, 2026-07-13, while
// the pool sat at unsigned bin 0). There is no official pinned signal
// anywhere in the Bitflow API surface; this detection is derived.
// Unsigned grid: 0 = raw −500 floor, 1000 = raw +500 ceiling.
// Degraded-read guard (reviewer catch): active_bin_id defaults to 0 when the
// bins response is missing the field, and a missing bin price makes pool
// price 0 => 100% fake "divergence" — which would manufacture a confident
// false PINNED (an action recommendation) out of a bad read. Only trust the
// edge test when the response actually carries the active bin and a price
// for it.
const activeBinReadOk = priceByBinId.has(active_bin_id) && (priceByBinId.get(active_bin_id) ?? 0) > 0;
const atGridEdge = activeBinReadOk && (active_bin_id <= 0 || active_bin_id >= 1000);
const pinned = atGridEdge && !slippageResult.ok;

let action: string;
if (inRange === null) {
action = `CHECK — ${positionNote}`;
} else if (pinned && !inRange) {
action = `PINNED — pool is at the grid edge (active bin ${active_bin_id}) with structural pool-vs-market divergence ${slippageResult.pct.toFixed(2)}%. The slippage gate cannot clear while pinned. Do NOT rebalance or blind-swap; route to the exit/withdraw path (withdraw minimums remain enforceable while pinned) or escalate to a human.`;
} else if (inRange) {
action = `HOLD — position in range at active bin ${active_bin_id}. APR (24h): ${apr24h.toFixed(2)}%.`;
} else if (!canRebalance) {
Expand All @@ -378,6 +399,8 @@ async function runGuardian(wallet?: string, poolId?: string): Promise<{
data: {
in_range: inRange,
active_bin: active_bin_id,
at_grid_edge: atGridEdge,
pinned: pinned,
user_bin_range: userBinRange,
can_rebalance: canRebalance,
refusal_reasons: refusals.length > 0 ? refusals : null,
Expand Down Expand Up @@ -411,7 +434,7 @@ const program = new Command();
program
.name("hodlmm-bin-guardian")
.description("Monitor Bitflow HODLMM bins and output LP health status")
.version("2.1.0");
.version("2.2.0");

program
.command("doctor")
Expand Down
Loading