From ebbe0cdef65bb5d324609c8aec931c807b24120a Mon Sep 17 00:00:00 2001 From: macbotmini-eng Date: Sat, 23 May 2026 17:16:30 -0600 Subject: [PATCH 1/3] fix(bitflow-hodlmm-deposit): allow first-time deposit by handling BFF user-bins 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `getUserBins` calls `fetchJson` with no error handling. BFF returns HTTP 404 with detail "Pool {pool} not found or user {addr} has no pool bins" when a wallet has zero existing LP positions in the pool. That is the normal first-time deposit case (explicitly supported per SKILL.md), not an error, but the throw bubbles up and crashes `doctor`, `status`, and `run` before any deposit logic executes. Wrap the fetch in try/catch. Catch ONLY the specific `HTTP 404 from ... has no pool bins` response shape and return an empty bins array. Let any other 404 or API failure propagate unchanged. Downstream code at line 498+ already handles an empty `bins` array correctly via `.filter(b => b.userLiquidity > 0n)` — empty array is the right shape for postcondition-plan adjustment to treat the wallet as a new LP. Companion fix to staging PR https://github.com/BitflowFinance/bff-skills/pull/583 which patches the same bug in the staging copy of this skill. Verified end-to-end on Stacks mainnet against three wallet shapes: - Fresh wallet `SP3YWTZQQ7ZYHG1ECEG6AJAT4KX36W2NA7Q6TCKBG` (was failing): doctor and status both now succeed; status preview generates full deposit plan (activeBin 648, 3-bin equal-split, slippage-protected minDlp, bounded postcondition). - Wallet with existing positions `SP2G6TM8JCRNK6WSPQE8S86FP2W3A4FEVGZCCCQT8`: doctor still works (no regression). - Wallet with positions in OTHER pools but not this one (`SP2G6TM8...` + `--pool-id dlmm_9`): correctly handled as first-time. Verified across dlmm_1, dlmm_3, dlmm_7 pools. `bun run typecheck` passes. --- .../bitflow-hodlmm-deposit.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts b/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts index df42db25..c1c9836b 100644 --- a/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts +++ b/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts @@ -492,9 +492,25 @@ async function getBins(poolId: string): Promise { } async function getUserBins(wallet: string, poolId: string): Promise> { - const response = await fetchJson( - `${BITFLOW_API}/api/app/v1/users/${wallet}/positions/${poolId}/bins?fresh=true` - ); + let response: UserBinsResponse; + try { + response = await fetchJson( + `${BITFLOW_API}/api/app/v1/users/${wallet}/positions/${poolId}/bins?fresh=true` + ); + } catch (error) { + // BFF returns HTTP 404 with detail "Pool {pool} not found or user {addr} has no pool bins" + // when a wallet has no existing LP positions in the pool. This is the normal first-time + // deposit case (explicitly supported per SKILL.md) — not an error. Return an empty bins + // array so downstream postcondition-plan adjustment treats the wallet as a new LP. + if ( + error instanceof Error && + error.message.startsWith("HTTP 404 from") && + /has no pool bins/i.test(error.message) + ) { + return []; + } + throw error; + } const bins = Array.isArray(response.bins) ? response.bins : []; return bins .map((bin) => ({ From 839314dba64cf330cc13dff8f6187131e2f0d872 Mon Sep 17 00:00:00 2001 From: macbotmini-eng Date: Sat, 23 May 2026 17:28:09 -0600 Subject: [PATCH 2/3] docs(bitflow-hodlmm-deposit): note fetchJson error-format coupling in 404 catch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a comment flagging that the empty-state catch is coupled to fetchJson's internal error message format ("HTTP 404 from ..."). If fetchJson's format ever changes, the catch silently fails — the 404 propagates instead of returning [], which is safe (no data loss) but the fix stops working with no diagnostic signal. The note tells a future fetchJson refactor where to look. Comment-only change; behavior identical to the previous commit. --- bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts b/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts index c1c9836b..173f25a5 100644 --- a/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts +++ b/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts @@ -502,6 +502,10 @@ async function getUserBins(wallet: string, poolId: string): Promise Date: Tue, 26 May 2026 08:55:29 +0545 Subject: [PATCH 3/3] fix(bitflow-hodlmm-deposit): clarify active-bin coordinate systems in status preview The status preview printed `observedActiveBinId` (BFF absolute, e.g. 646) next to `routerExpectedBinId` (on-chain relative, e.g. 146) with no labels, making the 500-bin center offset read as router-vs-BFF drift that would revert at maxDeviation 0. Verified against the live router (dlmm-liquidity-router-v-1-1 add-relative-liquidity-same-multi) and pool (dlmm-pool-sbtc-usdcx-v-1-bps-10): the contract's ERR_ACTIVE_BIN_TOLERANCE assert compares its own on-chain get-active-bin-id (returned 146) against the passed expected-bin-id (646-500=146), so a synced pool yields delta 0 and passes even at maxDeviation 0. The offset is by design (MIN_BIN_ID -500 / MAX_BIN_ID 500), not drift. Rename fields to bffActiveBinId / onChainExpectedBinId and document the coordinate systems so the broadcast path is not misdiagnosed as blocked. Co-Authored-By: Claude Opus 4.7 (1M context) --- bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts b/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts index 173f25a5..abf1fdb5 100644 --- a/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts +++ b/bitflow-hodlmm-deposit/bitflow-hodlmm-deposit.ts @@ -1126,8 +1126,14 @@ function contextData(context: Context): JsonMap { pendingDepth: context.pendingDepth, postConditionMode: "deny", activeBinTolerance: { - observedActiveBinId: context.bins.active_bin_id, - routerExpectedBinId: routerExpectedBinId(context.bins.active_bin_id), + // BFF reports the active bin in ABSOLUTE coordinates [0, 1000] (center = HODLMM_CENTER_BIN_ID). + // The on-chain pool/router use RELATIVE coordinates [MIN_BIN_ID -500, MAX_BIN_ID 500] (center = 0). + // The two always differ by HODLMM_CENTER_BIN_ID (500) BY DESIGN — this gap is the center + // offset, not active-bin drift. The router's ERR_ACTIVE_BIN_TOLERANCE assert compares its own + // on-chain get-active-bin-id against onChainExpectedBinId (both relative), so a synced pool + // yields delta 0 and passes even at maxDeviation 0. + bffActiveBinId: context.bins.active_bin_id, + onChainExpectedBinId: routerExpectedBinId(context.bins.active_bin_id), maxDeviation: context.selection.activeBinMaxDeviation, }, postconditions: [