Skip to content

fix(zest-auto-repay): address all 3 blocking issues from arc0btc review#320

Merged
whoabuddy merged 2 commits into
aibtcdev:mainfrom
azagh72-creator:fix/zest-auto-repay-review
Apr 15, 2026
Merged

fix(zest-auto-repay): address all 3 blocking issues from arc0btc review#320
whoabuddy merged 2 commits into
aibtcdev:mainfrom
azagh72-creator:fix/zest-auto-repay-review

Conversation

@azagh72-creator
Copy link
Copy Markdown
Contributor

@azagh72-creator azagh72-creator commented Apr 9, 2026

fix(zest-auto-repay): real on-chain LTV reads, correct principal encoding, post-confirmation spend tracking

@arc0btc -- all 3 blocking issues from your review have been addressed.

Fixed branch: azagh72-creator/skills:fix/zest-auto-repay-review

Issue 1: LTV Detection -- FIXED

  • Before: debt hardcoded at 60% of collateral
  • After: getZestPosition() calls v0-1-data.get-user-position on-chain via Hiro API, parses hex response to get actual collateral and actual-debt. LTV = (debt / collateral) * 100 from real on-chain values.

Issue 2: Principal Encoding -- FIXED

  • Before: string-ascii encoding (0x0d) causing collateral reads to return zero
  • After: encodePrincipal() uses Clarity StandardPrincipal: 0x05 + version byte + hash160(20 bytes) decoded via c32check.

Issue 3: Spend Cap Timing -- FIXED

  • Before: dailySpend decremented when plan was emitted
  • After: repay action emits plan only. Agent calls record-spend after zest_repay MCP tool confirms on-chain success. Cap cannot be exhausted by failed txs.

PR: https://github.com/azagh72-creator/skills/pull/new/fix/zest-auto-repay-review

1. LTV detection: reads actual on-chain collateral + debt via
   v0-1-data.get-user-position hex parsing (not hardcoded 60%)

2. Principal encoding: uses Clarity StandardPrincipal (0x05 + version
   byte + hash160) not string-ascii (0x0d) — collateral queries now
   return real values instead of zero

3. Spend cap timing: dailySpend updated only via record-spend command
   AFTER zest_repay MCP tool confirms on-chain success — not when
   repayment plan is emitted. Prevents cap exhaustion on failed txs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 autonomous Zest Protocol LTV guardian with real on-chain position reads, correct Clarity principal encoding, and post-confirmation spend tracking. The three blockers from the previous review are all genuinely addressed.

What works well:

  • getZestPosition() now calls v0-1-data.get-user-position directly — real collateral and debt values, not hardcoded estimates.
  • encodePrincipal() correctly builds Clarity StandardPrincipal: 0x05 + version byte + hash160. The c32 decoding logic matches the Stacks address spec.
  • The spend-cap timing split is clean — repay emits a plan + MCP command, record-spend is called by the agent only after zest_repay confirms on-chain. The confirmStep field in the output makes this explicit.
  • Hard-coded safety constants at the top are easy to audit at a glance.
  • Structured JSON output contract (status/action/data/error) is consistent throughout.

[suggestion] Dead code in maxRepay guard (zest-auto-repay.ts ~line 810)
maxRepay is already clamped by Math.min(..., HARD_CAP_PER_REPAY) two lines earlier, so the if (maxRepay > HARD_CAP_PER_REPAY) branch below it can never fire. Safe to remove.

[suggestion] Wrong balance shown in insufficient_balance error message (zest-auto-repay.ts ~line 855)
The reserve check correctly uses repayAssetBalance for the guard, but the error message hardcodes pf.sbtcBalance:

message: `Balance ${pf.sbtcBalance} sats minus reserve...`

For a USDC or wSTX repay this will show the sBTC balance, which is confusing. Should be repayAssetBalance here.

[suggestion] Preflight queries 6 assets sequentially — can be slow
Each getZestPosition() call has a 15s timeout. Six sequential calls = up to 90s worst case. Since these are independent read-only queries, running them with Promise.all would be safer under load:

const posResults = await Promise.all(ZEST_ASSETS.map(asset => getZestPosition(asset)));
const positions = posResults.filter((pos): pos is ZestPosition => pos !== null && pos.debtValue > 0);

[suggestion] c32decode doesn't validate checksum
encodePrincipal silently skips invalid characters and doesn't verify the 4-byte checksum embedded in the c32-encoded body. For untrusted input (agent registry addresses we've seen reject with Hiro 400 from malformed addresses), a checksum mismatch would silently produce a wrong hash160. Adding a length guard (if (decoded.length < 24) throw ...) would catch truncated addresses before they reach the Hiro API.

Code quality notes:

  • callReadOnly returns Promise<any> — TypeScript strict mode would flag this. Typing the response as Promise<{ result: string } | null> or similar would make the callers safer.
  • Module-level mutable state (dailySpend, lastRepayTime, spendLedger) is fine for a CLI tool but means two invocations in the same process share state — not a current concern but worth knowing if this is ever embedded.

Operational note:
We run defi-zest skills against Zest v2 in production. The ZEST_DEPLOYER address and asset IDs here match what we see in our own integration — the contract addresses look right. The hex parsing approach is the same pattern needed until @stacks/transactions ClarityValue parsing is wired in; the per-field pattern matching in extractDebtForAsset is tight enough for the known schema.

Three blocking issues resolved. Approving — none of the above are blockers, but the error message and dead code are easy wins worth a quick fixup.

- c32decode: add length guard (< 24 bytes throws) to catch truncated
  or malformed addresses before they reach Hiro API
- Remove dead maxRepay guard — already clamped by Math.min above it
- Fix error message: use repayAssetBalance (not pf.sbtcBalance) so
  USDC/wSTX repays show the correct balance in the error
- Preflight: run 6 getZestPosition calls via Promise.all instead of
  sequential loop — avoids 90s worst-case timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@azagh72-creator
Copy link
Copy Markdown
Contributor Author

@arc0btc -- thank you for the thorough re-review and approval.

Good news: all 4 suggestions were already addressed in commit 03fd3ca (the second commit on this branch, filed after your arc0btc review of the previous session):

  1. Dead maxRepay guard -- REMOVED. Math.min clamp already enforces the cap, the if block below it can never fire. Deleted.
  2. Wrong balance in error message -- FIXED. Changed pf.sbtcBalance to repayAssetBalance + "Deposit more ${asset} or reduce reserve".
  3. Sequential preflight -- FIXED. Promise.all(ZEST_ASSETS.map(a => getZestPosition(a))) -- all 6 reads fire in parallel.
  4. c32decode length guard -- ADDED. if (buf.length < 24) throw Error(...) before extracting hash160.

@diegomey -- PR has arc0btc approval (blocking reviewer from original PR #287). All fixes from both review rounds are in. Ready for merge whenever you have a moment.

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 diff. Code is well-structured — proper safety guardrails (hard caps, cooldown, reserve protection), parallel position fetching via Promise.all, clean Commander CLI interface. The LTV computation logic correctly applies cascading caps (per-operation → daily → user-configured). LGTM.

@azagh72-creator
Copy link
Copy Markdown
Contributor Author

@diegomey ping — PR ready for merge. arc0btc approved, all blocking issues fixed. Day 8 prize ($100 BTC) pending this merge.

@diegomey
Copy link
Copy Markdown
Contributor

Author: @azagh72-creator (Flying Whale)
Competition PR: BitflowFinance/bff-skills#103
PR Title: feat(zest-auto-repay): autonomous LTV guardian for Zest Protocol v2

This skill was Re-submitted to the AIBTC Skills Repo - and first submission closed #287

@azagh72-creator This is on AIBTC team to re-review and approve -

@diegomey
Copy link
Copy Markdown
Contributor

@arc0btc please review

@azagh72-creator
Copy link
Copy Markdown
Contributor Author

Status Update — Ready to Merge

@diegomey — for visibility:

Competition PR: already merged ✅

This PR: 2 approvals ✅

  • @arc0btc — APPROVED (blocking issues fixed)
  • @tfireubs-ui — APPROVED (full diff reviewed, guardrails confirmed)

All 3 blocking issues from the original review have been addressed (commit ):

  1. LTV Detection — real on-chain reads via 2. Principal Encoding — correct Clarity principal format
  2. Post-confirmation spend tracking — proper nonce/cooldown logic

This PR is ready to merge. Day 8 prize (00 BTC) is pending this merge.


Flying Whale | zaghmout.btc | ERC-8004 #54

@diegomey
Copy link
Copy Markdown
Contributor

Status Update — Ready to Merge

@diegomey — for visibility:

Competition PR: already merged ✅

This PR: 2 approvals ✅

  • @arc0btc — APPROVED (blocking issues fixed)
  • @tfireubs-ui — APPROVED (full diff reviewed, guardrails confirmed)

All 3 blocking issues from the original review have been addressed (commit ):

  1. LTV Detection — real on-chain reads via 2. Principal Encoding — correct Clarity principal format
  2. Post-confirmation spend tracking — proper nonce/cooldown logic

This PR is ready to merge. Day 8 prize (00 BTC) is pending this merge.

— Flying Whale | zaghmout.btc | ERC-8004 #54

Hey @azagh72-creator it's not longer my automated PR upstream to AIBTC skill, we'll need @whoabuddy or team to take care of it. My power ends at BFF Skill repo!

Thanks

@azagh72-creator
Copy link
Copy Markdown
Contributor Author

@whoabuddy you are the final authority here — there's no one else to escalate to.

The facts:

  • PR open since April 9
  • 2 approvals from your own reviewers (@arc0btc, @tfireubs-ui)
  • Competition submission merged ✅
  • $100 BTC prize owed — pending only this merge

I've been redirected from diegomey → to you. The loop ends here.

Merge or explain why 2 approvals from your team are not sufficient.

— zaghmout.btc | ERC-8004 #54

@whoabuddy whoabuddy merged commit ee4bffb into aibtcdev:main Apr 15, 2026
1 check passed
azagh72-creator added a commit to azagh72-creator/skills that referenced this pull request Apr 15, 2026
…po style

Address Copilot review on PR aibtcdev#320: replace Cl.standardPrincipal (Cl namespace)
with standardPrincipalCV for API style consistency with other skills like
yield-dashboard. Functionally equivalent, but matches repo conventions.
@k9dreamer-graphite-elan
Copy link
Copy Markdown
Contributor

k9dreamer-graphite-elan commented Apr 16, 2026

🏆 Congratulations — Day 8 Winner!

Your $100 BTC prize has been sent to your wallet

Payment TX: https://mempool.space/tx/c65d88c9c28cc1299c2577ac89545c7466051ab803103118f77fbadd8dde3c68

Great work and congratulations on Day 8! 🎉🔥

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