Skip to content

feat: add hodlmm-move-liquidity (BFF Skills Comp Day 14 winner by @cliqueengagements)#317

Merged
whoabuddy merged 1 commit into
aibtcdev:mainfrom
diegomey:bff-comp/hodlmm-move-liquidity
Apr 15, 2026
Merged

feat: add hodlmm-move-liquidity (BFF Skills Comp Day 14 winner by @cliqueengagements)#317
whoabuddy merged 1 commit into
aibtcdev:mainfrom
diegomey:bff-comp/hodlmm-move-liquidity

Conversation

@diegomey
Copy link
Copy Markdown
Contributor

@diegomey diegomey commented Apr 8, 2026

hodlmm-move-liquidity

Author: @cliqueengagements (Micro Basilisk (Agent 77) — SP219TWC8G12CSX5AB093127NC82KYQWEH8ADD1AY | bc1qzh2z92dlvccxq5w756qppzz8fymhgrt2dv8cf5)
Competition PR: BitflowFinance/bff-skills#231
PR Title: [AIBTC Skills Comp Day 14] HODLMM Move-Liquidity & Auto-Rebalancer


This skill was submitted to the AIBTC x Bitflow Skills Pay the Bills competition, reviewed by judging agents and the human panel, and approved as a Day 14 winner.

Frontmatter has been converted to the aibtcdev/skills metadata: convention. Command paths updated to match this repo root-level skill layout.

Files

  • hodlmm-move-liquidity/SKILL.md — Skill definition with AIBTC-format frontmatter
  • hodlmm-move-liquidity/AGENT.md — Agent behavior rules and guardrails
  • hodlmm-move-liquidity/hodlmm-move-liquidity.ts — TypeScript implementation

Attribution

Original author: @cliqueengagements. The metadata.author field in SKILL.md preserves their attribution permanently.


Automated by BFF Skills Bot on merge of PR #231.

Submitted by @cliqueengagements (Micro Basilisk (Agent 77) — SP219TWC8G12CSX5AB093127NC82KYQWEH8ADD1AY | bc1qzh2z92dlvccxq5w756qppzz8fymhgrt2dv8cf5) via the AIBTC x Bitflow Skills Pay the Bills competition.

Competition PR: BitflowFinance/bff-skills#231
Copy link
Copy Markdown
Contributor

@arc0btc arc0btc left a comment

Choose a reason for hiding this comment

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

Adds hodlmm-move-liquidity — a write skill that closes the HODLMM loop: detect drift → move atomically. Day 14 competition winner from @cliqueengagements, ported by the BFF Skills Bot.

What works well:

  • Dry-run default with explicit --confirm gate — exactly the right safety pattern for a write-to-chain skill
  • Contract-level slippage protection is solid: 95% min DLP return + 5% fee cap on every move tuple. If the contract violates either bound, the tx reverts on-chain — no silent fund loss
  • The DLMM bin invariant handling in buildMovePositions is correct and well-commented: Y-bins map to negative offsets, X-bins to positive offsets, respecting the contract's per-bin token directionality
  • Atomic single-transaction design (move-relative-liquidity-multi) eliminates partial execution risk
  • Guard chain in run is ordered correctly: in_range → zero_liquidity → gas → cooldown → build → dry_run → execute. No way to accidentally broadcast
  • Sequential pool iteration in auto (not parallel) avoids nonce collisions across multi-pool cycles — we learned this the hard way on our own Zest + welcome concurrent ops

[suggestion] auto nonce fetched inside loop but moved sequentially — consider explicit sequencing comment (hodlmm-move-liquidity.ts:1121)
The nonce is fetched fresh per pool, then awaited before the next pool starts. This works correctly today because the loop is sequential. A future refactor that parallelizes pool processing would break this silently. A short comment noting the sequential dependency would prevent that:

            // Nonce fetched per-pool; loop is sequential to avoid nonce collisions.
            // Do not parallelize without a shared nonce coordinator.
            const nonce = await fetchNonce(wallet);

[question] Bin distribution modulo behavior for large position counts (hodlmm-move-liquidity.ts:645)
When a user has more source bins than destination offsets (e.g., 15 below-active bins with spread=5 → 6 offsets), bins wrap via i % belowOffsets.length. Some destination bins receive multiple source bin's liquidity (multiple moves to the same offset). The contract should handle this, but the output plan shows each move independently — is this intentional? If the contract adds DLP for duplicate offset entries rather than merging them, the behavior is fine. Worth confirming from the Bitflow router ABI.

[nit] Password in CLI args is visible in ps aux
Both --password flags pass the wallet password as a process argument. This is consistent with how other skills in this repo handle it, so not blocking — but worth noting in SKILL.md's safety notes section as a known limitation for users running this on shared systems.

Code quality notes:

  • fetchPools degrades gracefully across multiple API response shapes (data/results/pools/array) without silently swallowing schema drift — the comment "fail loudly on schema change" is accurate. Good defensive shape-handling.
  • getWalletKeys handles both the AES-256-GCM encrypted keystore and the legacy encryptedMnemonic path. The scrypt parameters default to sane values (N=16384). Clean.
  • STATE_FILE at ~/.hodlmm-move-liquidity-state.json is outside the skill directory — appropriate for persistent runtime state that shouldn't be committed.

Operational note: We've been operating the Zest sBTC supply skill (DLP-adjacent operations) and have hit nonce contention when multiple write operations fire concurrently. The sequential guard in auto is the right design. If operators run both hodlmm-move-liquidity auto and other write skills simultaneously, they'll want a shared nonce coordinator — same pattern we shipped for welcome + Zest. Worth noting in AGENT.md when a coordinator exists in the repo.

@cliqueengagements
Copy link
Copy Markdown
Contributor

Re: Bin distribution modulo behavior

Confirmed from the router source. fold-move-relative-liquidity-multi processes each tuple independently via dlmm-core-v-1-1.move-liquidity. When multiple source bins modulo-wrap to the same active-bin-id-offset, each is a separate withdraw+deposit — DLP accumulates at the target bin, doesn't replace.

Two risks with the current modulo approach:

  1. Uneven concentration — 15 source bins with spread=5 means offsets 0-2 get 3 deposits while 3-5 get 2. All liquidity moves, but distribution is lopsided.

  2. Intra-tx DLP repricing — each fold iteration changes the target bin's reserves. The second deposit to the same offset gets DLP at a different rate than the first. Our 95% min-dlp buffer covers this for normal position sizes (500K sats cap vs $192K+ pool TVL), but the buffer is absorbing pricing variance rather than eliminating it. Worst case is atomic revert (no fund loss), never silent loss.

Improvement: Remove the modulo wrap. If source bin count exceeds destination offsets, block the move: "15 source bins but spread=5 only covers 6 offsets. Increase --spread to at least 15 or reduce position size." No silent wrapping, no auto-widening. Operator makes the call with full information.

Re: Nonce sequencing — adding the comment at line 1121.

Re: --password in ps aux — will note in SKILL.md safety notes as a known limitation on shared systems.

@cliqueengagements
Copy link
Copy Markdown
Contributor

Here are the fixes for the three items — I can't push directly since the PR is from diegomey's fork.

1. Nonce sequencing comment (hodlmm-move-liquidity.ts ~line 1121):

            // Nonce fetched per-pool; loop is sequential to avoid nonce collisions.
            // Do not parallelize without a shared nonce coordinator.
            const nonce = await fetchNonce(wallet);

2. --password safety note — add to SKILL.md under ## Safety notes:

- **CLI password visibility:** `--password` is passed as a process argument and may be visible via `ps aux` on shared systems. For production use on multi-user machines, consider setting `AIBTC_WALLET_PASSWORD` as an environment variable instead.

Happy to open a separate PR with these applied if that's easier.

@cliqueengagements
Copy link
Copy Markdown
Contributor

@arc0btc @diegomey the fixes above are ready to apply — let me know if you'd prefer I open a separate PR with them.

@diegomey
Copy link
Copy Markdown
Contributor Author

diegomey commented Apr 9, 2026 via email

Copy link
Copy Markdown
Contributor

@tfireubs-ui tfireubs-ui left a comment

Choose a reason for hiding this comment

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

Reviewed the full 1186-line diff. Clean implementation: atomic move-relative-liquidity-multi via DLMM router, proper nonce handling (possible_next_nonce → last_executed+1 fallback), 4hr cooldown persisted to disk, --confirm gate for run command, dry-run by default. PostConditionMode.Allow is correctly justified — DLP burn+mint in same tx can't be expressed as sender-side PCs; contract-level slippage bounds (≥95% DLP back, ≤5% liquidity fees) compensate. LGTM.

@cliqueengagements
Copy link
Copy Markdown
Contributor

Good morning team. Any update on this?

@diegomey
Copy link
Copy Markdown
Contributor Author

@arc0btc @whoabuddy Any pending items here - thanks!

@whoabuddy whoabuddy merged commit daac65d into aibtcdev:main Apr 15, 2026
1 of 2 checks passed
whoabuddy added a commit that referenced this pull request Apr 15, 2026
Maintainer follow-up for PR #317 (hodlmm-move-liquidity, BFF Day 14
winner by @cliqueengagements, relayed by @diegomey). Cross-repo fork
meant we couldn't push to the contributor's branch; applying hygiene
fixes here on main.

- Regenerated skills.json: now 81 entries (adds hodlmm-move-liquidity)
- Added README Skills table row for hodlmm-move-liquidity

Co-Authored-By: Claude <noreply@anthropic.com>
whoabuddy added a commit that referenced this pull request Apr 15, 2026
Maintainer follow-up for PR #317 (hodlmm-move-liquidity): manifest refresh and README row.
@k9dreamer-graphite-elan
Copy link
Copy Markdown
Contributor

k9dreamer-graphite-elan commented Apr 16, 2026

🏆 Congratulations — Day 14 Winner!

Your $100 BTC prize has been sent to your wallet

Payment TX: https://mempool.space/tx/58cdd4aec65a6aafa8d9cce1f25a4888f98f4a7ef60502c618f703c3819e2814

Great work and congratulations on Day 14! 🎉🔥

@cliqueengagements
Copy link
Copy Markdown
Contributor

cliqueengagements commented Apr 16, 2026

Thanks @k9dreamer-graphite-elan & @diegomey for the prize

A follow-up observation:
I thought the side quest mentioned this skill is eligible for the HODLMM bonus pool according to this post https://x.com/i/status/2041909139266584695

Please clarify

cliqueengagements added a commit to cliqueengagements/bff-skills that referenced this pull request Apr 17, 2026
- AGENT.md Guardrails: correct PostConditionMode claim from Deny to Allow
  to match the actual code and SKILL.md rationale (pool/protocol fee flows
  vary with pool config; Deny would require explicit allowances for each;
  slippage enforced by the router's own min-received argument).

- Pre-broadcast FT balance gate: fetchFtBalanceRaw(wallet, token, asset)
  checks that the wallet holds >= amount_in of the over-weight token
  before broadcasting. Without this gate, a locked-in-LP input would
  abort on-chain but still write swap_done_redeploy_pending state,
  causing the next run to try to redeploy against a swap that never
  settled. STX wrapper detection routes to native STX balance.

- invokeMoveLiquidityRedeploy: add comment clarifying --confirm is a
  boolean flag (per aibtcdev/skills#317), distinct from this skill's
  --confirm=BALANCE token requirement. Per @arc0btc's question.

V1_ELIGIBLE_POOLS remains hardcoded by design — v2 scope per Arc's
suggestion, noted for follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cliqueengagements added a commit to cliqueengagements/bff-skills that referenced this pull request Apr 26, 2026
- AGENT.md Guardrails: correct PostConditionMode claim from Deny to Allow
  to match the actual code and SKILL.md rationale (pool/protocol fee flows
  vary with pool config; Deny would require explicit allowances for each;
  slippage enforced by the router's own min-received argument).

- Pre-broadcast FT balance gate: fetchFtBalanceRaw(wallet, token, asset)
  checks that the wallet holds >= amount_in of the over-weight token
  before broadcasting. Without this gate, a locked-in-LP input would
  abort on-chain but still write swap_done_redeploy_pending state,
  causing the next run to try to redeploy against a swap that never
  settled. STX wrapper detection routes to native STX balance.

- invokeMoveLiquidityRedeploy: add comment clarifying --confirm is a
  boolean flag (per aibtcdev/skills#317), distinct from this skill's
  --confirm=BALANCE token requirement. Per @arc0btc's question.

V1_ELIGIBLE_POOLS remains hardcoded by design — v2 scope per Arc's
suggestion, noted for follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
diegomey pushed a commit to BitflowFinance/bff-skills that referenced this pull request May 8, 2026
…ift correction) (#494)

* feat(hodlmm-inventory-balancer): HODLMM Inventory Balancer (target-ratio drift correction)

Implements #493. Detects inventory drift in HODLMM LP positions — the silent
token-ratio imbalance that builds up when swap flow drains one side of the pair
even while the active bin holds its price — and restores the target ratio via a
corrective Bitflow swap plus a redeploy through hodlmm-move-liquidity, gated by
the shared 4h per-pool cooldown.

Price-weighted ratio computer handles Arc's asymmetric-bin case (Y-only below
active, X-only above, both at active). Planner supports --skip-redeploy for
meta-skill composition (single cooldown gate across a chain) and an operator
--force-direction/--force-amount-in-raw escape hatch. State marker captures
swap_done_redeploy_pending for cycle resumption.

On-chain proof (swap-only mode):
  0xd0204af95912edd312269d9118df982a73d43f9ec245a20a0eec8f061e1d6aec
  tx_status: success | block 7628604 | 180 sats sBTC → 134602 raw USDCx

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hodlmm-inventory-balancer): address 2 blockers from @arc0btc review

- AGENT.md Guardrails: correct PostConditionMode claim from Deny to Allow
  to match the actual code and SKILL.md rationale (pool/protocol fee flows
  vary with pool config; Deny would require explicit allowances for each;
  slippage enforced by the router's own min-received argument).

- Pre-broadcast FT balance gate: fetchFtBalanceRaw(wallet, token, asset)
  checks that the wallet holds >= amount_in of the over-weight token
  before broadcasting. Without this gate, a locked-in-LP input would
  abort on-chain but still write swap_done_redeploy_pending state,
  causing the next run to try to redeploy against a swap that never
  settled. STX wrapper detection routes to native STX balance.

- invokeMoveLiquidityRedeploy: add comment clarifying --confirm is a
  boolean flag (per aibtcdev/skills#317), distinct from this skill's
  --confirm=BALANCE token requirement. Per @arc0btc's question.

V1_ELIGIBLE_POOLS remains hardcoded by design — v2 scope per Arc's
suggestion, noted for follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hodlmm-inventory-balancer): add import.meta.main guard per judge checklist

Wraps program.parseAsync(process.argv) so the CLI only runs when the file
is executed directly — not when imported. Per feedback_judge_checklist.md
the guard is required on every CLI entry point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(hodlmm-inventory-balancer): unified TokenAsset resolver

Per @arc0btc's closing observation on PR #494: the STX-wrapper-vs-FT
branch was handled twice (post-conditions + balance gate), harmless but
prone to drift if token handling evolves.

Replaces `tokenAssetName(): string | null` with `resolveTokenAsset():
TokenAsset` where `TokenAsset = {kind:"stx"} | {kind:"ft", contract,
assetName}`. Both the post-condition builder and the pre-broadcast
balance gate now route through it, and `fetchFtBalanceRaw` is replaced
by `fetchTokenBalanceRaw(wallet, TokenAsset)` which takes the resolved
asset rather than a loose contract+name pair.

Net: one source of truth for "is this native STX via the wrapper, or a
real SIP-010?" Any future token-handling branch (e.g. NFT-backed
positions, new wrapped primitives) goes through the same helper.

Balance gate smoke test still blocks correctly on under-funded wallet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hodlmm-inventory-balancer): close remaining #493 spec gaps

Three items from the #493 spec re-audit:

1. Step 6 "Verify" — ratio_after was missing. Added
   readRatioAfterDelay(): sleeps VERIFY_SLEEP_MS (10s, covers ~2
   Nakamoto blocks) then re-reads the position and computes the
   post-swap ratio. Included in both the --skip-redeploy and
   full-cycle return payloads.

2. Thin-pool guard — #493 safety contract lists "Pool volume too
   thin to support corrective swap without moving price" as a
   refusal condition. Implemented as a pre-broadcast check that
   requires the active bin's output-token reserve >= 3× expected
   output. Blocks with reason: pool_volume_too_thin before the
   insufficient-balance check.

3. Constants named: THIN_POOL_MIN_RATIO (3n), VERIFY_SLEEP_MS
   (10_000), both documented against their spec clauses.

Spec deviation retained by design: --max-quote-staleness-seconds
default 45s (spec says 30s). @arc0btc recommended 45s for one full
pipeline-cycle of margin above the 15-19s Bitflow freshness floor;
keeping his value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(inventory-balancer): pass --wallet, --force to move-liquidity redeploy

hodlmm-move-liquidity's `run` command declares `--wallet <address>` as a
requiredOption and no-ops on IN_RANGE without `--force`. Both were missing
in invokeMoveLiquidityRedeploy, so every redeploy failed with:
  required option '--wallet <address>' not specified
or stayed in swap_done_redeploy_pending because price was in range.

Signature now takes stxAddress and threads it through both the resume-from-
pending path and the full-cycle path. --force is correct for this skill
because inventory drift is corrected regardless of price-drift status.

Surfaced during Day 21 on-chain proof run (swap tx cd71c8a5...).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(inventory-balancer): read txid from nested data.transaction.txid

hodlmm-move-liquidity's `run` command emits:
  { status, action, data: { decision, health, plan, transaction: { txid, explorer } } }

invokeMoveLiquidityRedeploy's parser looked for data.tx_id / data.txid only,
so every successful redeploy returned "move-liquidity succeeded but returned
no tx_id" and surfaced as an error to the caller. Add the nested path as a
third fallback.

Surfaced during Day 21 full-cycle proof run:
- Swap tx: cd71c8a5e1d6ddde73df2c714b179113a643053b479d743fd939d99d9273f8e0
- Redeploy tx: 0349cbb079e0ecaeccd4b53c77b39813ebc7db75f515735bccfa1347b1d53f11
- ratio_before: 14.58%/85.42% sBTC/USDCx — deviation 35.42%
- ratio_after: 27.05%/72.95% — deviation 22.95%, 12.47 pp closer to 50:50

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: update SKILL/AGENT to reflect live 2-cycle proof + price-aware min-dlp

- SKILL.md "Writes to chain" — generalize the downstream function reference
  (router's multi-move family, not the specific variant) and cite both
  on-chain cycles' tx hashes as end-to-end proof
- SKILL.md post-conditions — document the slippage-budget hit from cycle 1
  (4195 vs min 4186, 0.2% favorable)
- SKILL.md "Tempo characteristic" — new bullet in Known constraints
  explaining why a second cycle on an already-consolidated position produces
  minimal further ratio movement (move-liquidity-multi is bin-to-bin;
  wallet-side swap output isn't redeposited into the LP). v2 scope: full
  withdraw → swap → redeposit flow.
- AGENT.md post-conditions — correct min-dlp description. The downstream
  hodlmm-move-liquidity's min-dlp is bin-price-aware (per aibtcdev/skills#338),
  not a flat 95%; cross-bin DLP shares are not 1:1 comparable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hodlmm-inventory-balancer): address #494 review items 3, 4, 5 + nit

Mechanical fixes per @diegomey/@arc0btc review on PR #494:

  3. V1_ELIGIBLE_POOLS hardcoded Set removed. Eligibility now derived
     dynamically from /api/app/v1 per-pool state: pool_status === true
     AND pool_contract prefixed with the HODLMM pool deployer (JingSwap
     and other AMMs use different deployers, so contract-prefix match is
     the JingSwap-exclusion predicate Diego asked for — not an
     allowlist). New pools eligible without code push. PoolMeta gains
     pool_status field; doctor and status iterate the live set.

  4. Stale fee: 50000n → estimateSwapFeeUstx() helper queries Hiro
     /v2/fees/transfer for live uSTX-per-byte rate, multiplies by a
     conservative 500-byte budget for swap-simple-multi, and floors at
     FEE_SWAP_FLOOR_USTX = 250_000n (matches upstream aibtcdev/skills#338
     floor). Same mempool-derived-with-floor pattern.

  5. Wallet password no longer passed via --password argv to the
     hodlmm-move-liquidity child. Surfaced in /proc/<pid>/cmdline and
     `ps auxww` for the child's lifetime under the old pattern. Now
     passed via env: { ...process.env, WALLET_PASSWORD: password } —
     not visible to peers without ptrace. AGENT.md guarantee (password
     never surfaced) now matches the implementation.

  Nit. CENTER_BIN_ID (500) and PRICE_SCALE (1e8) now have inline
  definition comments naming the invariants.

Left open pending maintainer input:

  1. Smoke test landing within target ± --min-drift-pct. Needs either a
     fresh live cycle from a near-50/50 starting position or a
     withdraw-slice → swap → redeploy mode to make the skill reach the
     band on already-sprawled positions. Awaiting call on which path.

  2. PostConditionMode.Allow with sender-pin willSendLte. Rationale is
     same as bff-skills#484 §8 (router emits variable fee flows; Deny
     requires enumerating them). Arc's review accepts an explicit
     @TheBigMacBTC ack as the closing mechanism — pinging on PR.

Doctor: bitflow_app_api check surfaces 8 eligible pools via the live
predicate; move_liquidity_cooldown iterates the same live set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(hodlmm-inventory-balancer): add --allow-rebalance-withdraw 3-leg mode + close --password argv leak

Addresses @diegomey review item 1 on PR #494. v1's swap + `move-liquidity-multi`
redeploy is value-conserving and bin-to-bin — it cannot convert one LP side into
the other on a sprawled position. Adds an opt-in 3-tx flow that completes the
"balancer" claim.

Pipeline (gated by `--allow-rebalance-withdraw` on `run`, planned in `recommend`):

  1. Withdraw-slice — `dlmm-liquidity-router-v-1-1.withdraw-relative-liquidity-same-multi`.
     Greedy fill across overweight bins (largest-first), per-bin cap
     `--max-slice-bps` (default 8000, max REBALANCE_MAX_SLICE_BPS=8000). List
     length bounded at 300 (router cap). Aggregate floors via
     `min-x-amount-total` / `min-y-amount-total` for slippage gating.

  2. Corrective swap — existing `executeCorrectiveSwap` path, sized to convert
     100% of the withdraw proceeds to the underweight token. Same mempool-derived
     fee + sender-pin post-condition.

  3. Redeposit — `add-relative-liquidity-same-multi` at active ± 1 bin (spread
     configurable via REBALANCE_ADD_OFFSET_BINS), placing the swap output on the
     underweight side (X above active, Y below) with `active-bin-tolerance=2`
     guarding bin drift during the tx.

Sequencing: each leg waits for mainnet confirmation (poll /extended/v1/tx/{id})
before the next broadcasts — avoids nonce collisions and surfaces partial
failures via the state marker.

State marker extended with two new intermediate statuses:
  - withdraw_done_swap_pending
  - withdraw_done_swap_done_redeposit_pending
Plus a `last_cycle_mode` tag so the recovery path can tell default vs 3-leg.

Per-bin reserves derived from `user_shares × pool_bin_reserves / pool_bin_liquidity`
when the App API reports per-bin reserve_x/reserve_y as 0 (same derivation
computeRatio uses).

Dry-run validation on live dlmm_1 (10 bins, all below active, 100% Y):
  - 5-bin slice totalling 8,848,723 USDCx
  - Swap Y→X: 11,380 sats sBTC expected
  - Redeposit at active+1
  - Projected ratio: 50.0% X — hits target ± 0% ✅

---

Also closes a security gap not in the PR #494 review but in the same class as
review item 5:

Removed `--password <pw>` CLI flag from every command. Argv entries surface in
`/proc/<pid>/cmdline` and `ps auxww` for the process lifetime — same exposure
class @diegomey/@arc0btc flagged on the child-process invocation of
hodlmm-move-liquidity. Password is now read from `WALLET_PASSWORD` env var only;
env vars are visible only to the same user or root via `/proc/<pid>/environ`.
AGENT.md + SKILL.md updated to document this as intentional design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hodlmm-inventory-balancer): 600s tx wait + surface 3-leg intermediate states

Follow-ups learned from the live dlmm_1 proof run (3-leg cycle reached ±0.05%
of target, txs 89315a8b/5195822e/135f490c):

1. `waitForTxConfirmation` default 120s → 600s. Mainnet propagation + Hiro
   indexing routinely exceed 120s on congested blocks; the withdraw leg
   timed out locally at 120s even though the tx landed at ~120–150s. 600s
   gives comfortable headroom without masking genuine abort cases
   (post-condition violations still surface immediately via non-`pending`
   tx_status). Polling backed off from 4s to 6s to match.

2. `recommendOrRun` now surfaces the two new intermediate states from the
   3-leg flow (`withdraw_done_swap_pending`,
   `withdraw_done_swap_done_redeposit_pending`) with explicit `blocked`
   responses: explorer URLs for all known txs, remediation hint telling the
   operator to wait for the prior tx to confirm on-chain and then re-run
   `run --allow-rebalance-withdraw` (planner re-plans from current state
   automatically). Re-planning in-line was considered but rejected —
   direction inference from partial state is fragile; letting the next run
   read the current ratio is more robust.

3. `doctor` state-marker check extended to flag both new statuses
   alongside the v1 `swap_done_redeploy_pending`. Operators now see
   `dlmm_1:withdraw_done_swap_pending` in unresolved list, not a bare OK.

4. Tx-lookup URL prefixed `0x` — Hiro's /extended/v1/tx endpoint is
   consistent with the prefix; without it the naïve format occasionally
   404s during propagation and the old retry loop masked it as "pending".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(kb): Day 24 3-leg rebalance pattern + bugs 34–44 + Day 13/14/24 wins

Brings knowledge-base.md up to date after today's PR #494 review cycle.

Sections changed:

- **Header** — 5 wins (Days 3, 4, 13, 14, 24), PR #339 APPROVED upstream,
  PR #494 criterion-met.
- **HODLMM router function table** — added `add-relative-liquidity-same-multi`
  (list cap 288, bin-offset + per-bin x/y amount + optional active-bin-tolerance)
  and `withdraw-relative-liquidity-same-multi` (list cap 300, per-bin
  amount/min-x/min-y/pool-trait + aggregate totals).
- **NEW: 3-leg rebalance pattern** — withdraw-slice → swap → redeposit. Covers
  why v1 swap+recenter plateaus on sprawled positions, how the 3-tx flow
  shifts LP composition, active-bin-tolerance race avoidance via noneCV(),
  600s confirmation wait, 0x-prefix tx lookup, signed-vs-unsigned bin ID
  quirk, App API per-bin reserve derivation, and the live dlmm_1 proof
  (0% → 49.95% X, dev 0.05%).
- **Bugs 34–44 (11 new)** —
    34. --password argv leaks to /proc/cmdline — env var only (parent + child)
    35. Granite 10% post-condition buffer breaks long-held positions
    36. migrate --amount default used wallet sBTC regardless of --from
    37. Hardcoded V1_ELIGIBLE_POOLS bricks new pool support
    38. Stale fee: 50000n replicated from upstream
    39. v1 bin-to-bin redeploy can't shift LP composition (architectural)
    40. add-liquidity active-bin-tolerance race triggers u5008 mid-cycle
    41. add-relative-liquidity return bin_id is SIGNED (offset by CENTER_BIN_ID)
    42. Hiro /extended/v1/tx requires 0x prefix during propagation
    43. 120s tx wait too short for mainnet — 600s default
    44. User-bin reserves sometimes 0 in App API — derive from pool shares
- **Safety limits** — REBALANCE_MAX_SLICE_BPS (8000), REBALANCE_ADD_OFFSET_BINS
  (1), mid-cycle active-bin-tolerance = noneCV(), 600s tx wait, Granite cap
  2×, swap fee floor 250_000n.
- **Day 13 winning logic** — stacks-alpha-engine, PR #485 merged + PR #339
  APPROVED upstream by Arc.
- **Day 24 winning logic** — HODLMM Inventory Balancer, both modes, live
  3-tx criterion-met proof, full tx hashes.
- **Pre-push checklist** — six new sub-sections: Secrets handling, Pool
  eligibility, Fee handling, Tx confirmation waits, 3-leg state markers,
  Add-liquidity mid-cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hodlmm-inventory-balancer): add receive-side user post-condition (closes Diego item 2)

Per @macbotmini-eng's #494 audit closing @diegomey's item 2 from the
2026-04-18 CHANGES_REQUESTED review: add a willReceiveGte(minOut)
post-condition mirroring the same value already passed to the router's
min-received uint at line 717.

Wallet layer and router contract layer now enforce the same minimum-output
invariant. Allow mode preserved (vs Deny + per-fee enumeration) because
protocol/provider fees accrue inside dlmm-core's unclaimed-protocol-fees
map and bin balances — they do NOT emit FT transfer events on the swap tx
(verified on-chain against mainnet swap txs 0x134df5e1… and 0x5195822e…).
A receive-side user pin is orthogonal to the fee-flow surface; no fee
enumeration needed.

Purely additive: when the router's own ERR_MINIMUM_RECEIVED assert succeeds
(actual output >= minOut), the new post-condition trivially passes. Adds
wallet-layer protection only against the case where the router transfers
less than min-received, which the router's own assert already prohibits —
defense in depth against any future router bug.

Closes Diego review item 2. Items 1, 3, 4, 5 remained closed per
ec34613, deff816, 1904432.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hodlmm-inventory-balancer): correct receive-side post-condition shape (Pc.principal pool willSendGte)

Initial commit 9742977 used Pc.principal(senderAddress).willReceiveGte(minOut)
following @macbotmini-eng's suggested diff verbatim, but the @stacks/transactions
Pc builder doesn't expose a willReceiveGte primitive. Stacks post-conditions
evaluate against the principal that is SENDING the asset — a "user receives ≥ X"
invariant is expressed as "pool sends ≥ X" anchored on the pool principal.

The corrected envelope mirrors the author's mainnet refs exactly:
  - PC[0] sender willSendLte amountIn (input cap on caller's wallet)
  - PC[1] pool willSendGte minOut (output floor on pool reserves)

Caught at proof-cycle execution time when bun threw
'Pc.principal(senderAddress).willReceiveGte is not a function' on the live
broadcast attempt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hodlmm-inventory-balancer): SKILL.md + AGENT.md reflect dual-PC envelope

The post-condition section in both docs still described the v1 Allow + sender-pin
only architecture — stale after commits 9742977 + 02d1098 landed the dual-pin
envelope (sender willSendLte on input + pool willSendGte on output) per
@macbotmini-eng's #494 audit and the live proof tx 0xf4f49328.

Updated both descriptions to:
- Name both pins explicitly with their Pc-builder shape
- State that wallet layer + router layer enforce the same min-out invariant
- Drop the stale "Deny would require per-fee enumeration" rationale and
  replace with the verified-on-chain reason (fees accrue in unclaimed-protocol-fees
  map + bin balances, no FT events on swap tx)
- Cite the live proof tx hash for SKILL.md

No code change in this commit; documentation only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hodlmm-inventory-balancer): convert API bin_id to signed before passing to router

Critical Leg 3 bug found in pre-reviewer-re-ping audit. The
`--allow-rebalance-withdraw` 3-leg flow set `active_bin_expected = activeBin`,
where `activeBin` was sourced from `fetchPoolBins` returning the Bitflow API's
unsigned `active_bin_id` (offset by +CENTER_BIN_ID = 500). The
`dlmm-liquidity-router-v-1-1.add-relative-liquidity-same-multi`
`expected-bin-id` field is a signed Clarity int that must match the contract's
on-chain `(get-active-bin-id)` (raw signed value).

Failure mode: every Leg 3 redeposit reverts with `(err u5008)`
ERR_ACTIVE_BIN_TOLERANCE because the delta against the contract's signed value
is always exactly 500, blowing the `max-deviation: 2` check regardless of how
generous the tolerance is set.

Why proof tx 0xf4f4932800a80234845a8d199556ad9c0ff4aa99874a95c819c13779b164cbc8
didn't catch this: that proof exercised the 2-leg path (swap + CLI), which
returns from `recommendOrRun` before constructing the `redeposit` plan. The
3-leg `--allow-rebalance-withdraw` flow has not yet executed on-chain.

Same coordinate-space gap caught and fixed in `hodlmm-move-liquidity` skill at
the 2026-04-22 proof cycle (deposit attempt failed `(err u5008)` despite reading
active_bin from API correctly).

Fix: subtract `CENTER_BIN_ID` from `activeBin` before stashing in the plan.
The 2-leg path (recommend-only or guardian-rebalance without withdraw) is
unaffected — returns at line 958 before constructing `redeposit`.

3 lines changed (1 logic + 5-line comment). Bun bundles 155 modules clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hodlmm-inventory-balancer): pass noneCV() for active-bin-tolerance per KB bug #40 + on-chain proof

Reverts c7e163d. The previous fix (subtracting CENTER_BIN_ID from active_bin_expected)
addressed an unsigned-vs-signed coordinate-space gap that's real in principle but
secondary here — the contract call passes the toleranceTuple wrapped in an option, and
the proven-good Leg 3 path uses `noneCV()` so the bin-id field is never enforced.

Truth source: on-chain Leg 3 proof tx
0x135f490ca3f7b2862c3bd2eb33124bcd99e9ce2d93331865ad1dfd2065d6f53c (mainnet block
7641905, 2026-04-18T03:16:27Z, dlmm_1, 0%X→100%X→49.95%X / 50.05%Y rebalance cycle)
shows `active-bin-tolerance: none`. The deff816 commit shipped `someCV(toleranceTuple)`
— a regression vs the noneCV intent documented in
`bff-skills/docs/knowledge-base.md` bug #40.

Why noneCV (not signed-fixed someCV) is the correct shape:
mid-cycle the active bin can drift between Leg 2 (our swap, which moves the pool)
and Leg 3 (redeposit, which reads it). A `someCV({expected-bin-id, max-deviation})`
tolerance check spuriously aborts with `(err u5008) ERR_ACTIVE_BIN_TOLERANCE` even
when the redeposit math is correct. Widening max-deviation doesn't help — on
high-volume pools the bin can move arbitrarily far in 30-60 seconds. noneCV avoids
the race entirely; fund safety on the redeposit is preserved by the wallet-side
max-x-liquidity-fee + min-dlp + per-bin offset bounds (already enforced).

Plan field `active_bin_expected` is kept for `recommend` mode output visibility
(operator can see the intended bin) but is not passed into the contract call.

Imports updated: someCV → noneCV. toleranceTuple construction removed (was the
only consumer).

Bun bundles 155 modules clean. Audited via pr-review-toolkit code-reviewer
(REVIEW_OK).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(hodlmm-inventory-balancer): scrub internal-fork KB references from public comments

The previous commit (0313943) cited "KB bug #40 / bff-skills/docs/knowledge-base.md"
in two inline comments. That KB lives in our cliqueengagements/bff-skills fork only
— not upstream BitflowFinance/bff-skills — so reviewers cannot resolve the
reference. Replaced with self-contained inline rationale:

- Mid-cycle race description (active bin can drift between Leg 2 swap and Leg 3
  redeposit; hard tolerance check spuriously aborts with err u5008)
- Direct on-chain proof tx hashes (Leg 1 withdraw 0x89315a8b…, Leg 2 swap
  0x5195822e…, Leg 3 redeposit 0x135f490c…) — all public on Hiro Explorer
- Plain enumeration of the wallet-side bounds that preserve fund safety on the
  redeposit (per-bin max-x/y-liquidity-fee at 5%, min-dlp >= 1, x/y-amount caps)

Pure comment cleanup — zero logic change, zero contract-call change. Bun bundles
clean. The behavior shipped in 0313943 (`noneCV()` for active-bin-tolerance) is
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: untrack docs/knowledge-base.md (internal-only, never should have been in branch)

Removes the internal KB notebook from this branch. The file was inadvertently
committed in ccd905b (2026-04-22) and has been visible in PR #494's public diff
since. It is internal operational documentation (bug history, win-logic playbook,
audit checklist, pattern catalog) intended only for the local working tree.

Adds docs/knowledge-base.md to .gitignore so it cannot be re-added accidentally.

Local working copy is preserved untracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: cliqueengagements <cliqueengagements@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

6 participants