🛠️ SKILL: windleg-zestlend-hermeticastake-yield-rotator-sBTC-USDCx-sUSDh#604
Conversation
…skill Adds SKILL.md and AGENT.md scaffolding for a new composed-controller skill that supplies sBTC on Zest, borrows USDC/USDCx, swaps to USDh via Bitflow, and stakes USDh on Hermetica. Implementation deferred pending the open questions documented in the companion research note on bf_k-wiki-v1@claude/zest-hermetica-yield-research-RavKo. Marked research-stage and explicitly distinct from the existing hermetica-yield-rotator (which rotates already-held USDh between staking and HODLMM).
…alize scaffold Adds the required `<name>.ts` Commander.js entry to satisfy Issue #484 §4 (CLI pattern + doctor) and §9 (validate-frontmatter / generate- manifest / doctor). Controller mirrors the bitflow-zest-sbtc-leverage- cycle composition template: - Shells out to zest-asset-deposit-primitive, zest-borrow-asset- primitive, and bitflow-swap-aggregator only — never builds tx itself. - Hermetica stake/unstake legs delegated to a future hermetica-stake- primitive (separate PR per Issue #483); doctor surfaces the gap. - Strict single-JSON-object output contract with status/blocked/error envelopes plus registry-minimum-compatible error.message field. - Checkpoint state under ~/.aibtc/state/zest-hermetica-yield-rotator/. - Confirmation tokens: ROTATE (forward), UNWIND (reverse). - LTV cap 0.50, soft warn 0.40 per safety notes. Also updates SKILL.md / AGENT.md to remove placeholder author identifiers (TODO marker), tighten the dependency list, and link to the companion research doc on bf_k-wiki-v1.
…ITL monitor
Adds the strategic decision layer the controller was missing. Six new
moving parts, all read-only except the gate logic:
- `score` subcommand: composite 0-100 from BTC regime, perp funding,
carry spread, realized vol, USDh peg. Per-component sub-scores +
raw inputs + dropped-component handling so missing feeds don't
silently distort the composite.
- Multi-source BTC price (CoinGecko, CoinPaprika, Kraken, Binance
spot) with median selection + max-min dispersion gate. Refuses
--min-score gating if dispersion > 2% (sources disagree -> at
least one is wrong).
- Binance perp funding rate (BTCUSDT premium index), annualized
for the funding score.
- Hermetica sUSDh exchange-rate sampling via Hiro read-only call,
persisted at ~/.aibtc/state/.../exchange-rate-history.json.
Annualized over a 24h+ window to produce sUSDh APY estimate.
- Zest USDC borrow APR via the existing borrow primitive's status
output (defensive scan, no fabricated function names).
- USDh AMM peg via the swap aggregator's quote subcommand
(probe USDh -> USDC, observed price = amountOut/amountIn).
- `monitor` subcommand with two modes:
- hitl (default): polls every N seconds, emits one JSON per poll,
never broadcasts.
- autonomous: same poll, plus broadcasts run/unwind-init when score
crosses --min-score / --exit-score-below thresholds. Hard-capped
at one auto-action per 24h per wallet, intent logged before
broadcast, requires --mode=autonomous --confirm=AUTONOMOUS.
- `--min-score` gate on run, `--exit-score-below` gate on unwind-init.
Both default to sensible thresholds (55 / 35); both accept 0 to
disable. unwind-init also accepts --force-unwind to override the
"score still healthy" refusal.
Identifier discipline (Issue #484 §6): no new contract addresses or
function names fabricated. Hermetica staking-v1-1.get-usdh-per-susdh
is read-only and the contract ID is cited from canonical docs +
existing peer skill. All other reads are public HTTP endpoints or
delegated to primitive CLIs. 5s HTTP timeouts, per-feed failures
are swallowed not retried.
SKILL.md/AGENT.md updated to document strategy, modes, gates,
external sources, and Tenero-vs-DIA-vs-Pyth oracle clarification.
…elf-impact, reserve warn)
Significant strategy-layer upgrade in response to the dynamics review.
Six concrete improvements; scope locked to the four-leg pipeline
(sBTC lend -> borrow USDC -> swap -> USDh stake). LP routing
explicitly out of scope; HODLMM touched only at the swap leg.
1. Bitflow Quote Engine integration (authoritative peg + slippage)
- POST /api/quotes/v1/quote replaces the speculative `quote`
primitive probe. USDh peg is a real round-trip price.
- GET /v1/tokens resolves USDh / USDCx / USDC / sUSDh contract
addresses dynamically (no fabrication per Issue #484 §6).
- Pre-trade slippage estimate at my actual projected borrow size
enters the carry math via `price_impact_bps` instead of an
assumed swap drag.
2. Binance funding 7d MA + instantaneous alarm
- 7d MA from fundingRate history (limit=21 prints) is the entry
signal; raw spot funding becomes the exit alarm only.
- `instantaneousAlarm` flag fires when spot turns negative even
while 7d MA is positive ("momentum rolling over").
3. Post-borrow-impact carry spread
- Linear-below-kink projection of USDC borrow APR at my projected
debt size against current Zest utilization.
- Carry spread uses projected APR, not spot. `selfImpactBps`
reports how much my own borrow moves the rate.
- Surfaced as `projection_method` so the conservative estimate
is auditable.
4. Carry trend sub-score
- Δ(sUSDh APY) over the persisted 7d window of exchange-rate
samples. +2pp/7d -> 100; -2pp -> 0.
- 5% weight in the composite; isolates the observable component
from the broader carry-spread number.
5. Composite weight retune for the 7-day-cooldown risk surface
- realizedVol 15% -> 25% (vol is the dominant determinant of
liquidation-during-cooldown risk)
- peg 10% (unchanged) but now sourced from authoritative quote
- btcRegime 30% -> 25%, funding 25% -> 20% (room for carry trend)
6. Self-impact bounded sizing + wallet reserve soft-warn
- `selfImpactBoundedSbtcSats` = largest sBTC amount keeping my
projected position under --pool-share-cap-pct (default 5%) of
BOTH Zest USDC pool AND Hermetica sUSDh supply. Phrased as a
calculation result from operator-supplied caps, not a
recommendation, to avoid implying financial advice.
- sUSDh total supply read via Hiro from susdh-token-v1.get-total-
supply (verified canonical contract from Hermetica docs).
- Wallet USDC + USDCx balance read via Hiro
/extended/v1/address/{addr}/balances.
- `walletReserve.warning = RESERVE_BELOW_THRESHOLD` surfaces when
external USDC reserve < --emergency-reserve-pct (default 30%)
of projected debt; soft-warn only, never refuses entry.
SKILL.md / AGENT.md updated to document every behavior, scope-lock
the pipeline (no LP routing), and frame `selfImpactBoundedSbtcSats`
as a calculation result rather than advice. Updated weights table,
data-source table including Bitflow Quote Engine and BFF App API.
Pre-submission scan that grep-checks SKILL.md, AGENT.md, and the .ts for patterns indicating leaked user wallets, private keys, mnemonics, or personal filesystem paths. Allowlist contains only canonical public protocol contracts (Hermetica deployer, Zest V2 market). Exits non-zero on any blocking finding; warnings print but do not block. Run before opening the PR; intended as a hygiene gate, not a runtime behavior. Per pre-submission directive: any future edit to files in this skill directory must re-pass this script with zero blockers before submission.
…small files) Wind-only rename from zest-hermetica-yield-rotator. Adds preflight-screen.sh (allowlist now includes verified Bitflow token and DLMM pool deployers beyond Hermetica + Zest), per-skill package.json with @stacks/transactions + @stacks/network deps for inline broadcast. Companion commit will add SKILL.md, AGENT.md, and the .ts entry. Old zest-hermetica-yield-rotator/ directory will be deleted in a follow-up.
…SKILL.md + AGENT.md) Wind-only SKILL.md and AGENT.md. Companion next-step is the .ts entry, then deletion of the old zest-hermetica-yield-rotator/ directory.
…th inline stake The skill's entry. ~88KB. Wind-only four-leg pipeline (sBTC supply on Zest -> USDCx borrow on Zest -> swap USDCx->USDh on Bitflow Quote Engine -> stake USDh inline on Hermetica staking-v1-1). Key changes from the prior zest-hermetica-yield-rotator skill: - Renamed everything to windleg-zestlend-hermeticastake-yield-rotator - Dropped the unwind path (companion skill territory, separate PR) - Removed the hermetica-stake-primitive shell-out placeholder - Added inline `inlineStake()` against verified staking-v1-1.stake bytecode at block 3,567,258 (Hiro contract source) - Pre-checks: staking-state-v1.get-staking-enabled, wallet USDh balance - PostConditionMode.Deny + StandardFungiblePostCondition for exact USDh transfer - Signer: process.env.AIBTC_WALLET_PRIVATE_KEY (set by operator after `wallet unlock`); SIGNER_UNAVAILABLE blocker if missing - Default --borrow-asset changed from USDC to USDCx (canonical Zest V2 post-migration stable; no plain "USDC" exists in Bitflow registry) - Added --max-price-impact-bps gate (default 50 bps) wired into the swap leg via the Bitflow Quote Engine's price_impact_bps response - Score module unchanged structurally; same six components, same multi-source BTC price + dispersion gate, same self-impact-bounded sizing, same wallet-reserve soft-warn - Score recommendation surfaces UNWIND for the partner unwinder skill but this skill never broadcasts unwind - Autonomous monitor only broadcasts wind (`run`); unwind intent is logged + signaled but not broadcast Strict-typecheck passes (only missing-deps errors which the per-skill package.json resolves at runtime).
…ed to windleg-zestlend-hermeticastake-yield-rotator/)
… pattern Per user directive: - metadata.author: TODO-set-github-handle -> IamHarrie-Labs - metadata.author-agent: TODO-set-author-agent -> Serene Spring Per src/lib/services/x402.service.ts and wallet-manager.ts (read after installing the wallet skill via `npx -y skills add aibtcdev/skills/wallet`): - Signer pattern: try wallet-manager.restoreSessionFromDisk() (registry env, after `wallet unlock`) -> fall back to CLIENT_MNEMONIC env var (canonical fallback per x402.service.getAccount line 894). Drops the wrong AIBTC_WALLET_PRIVATE_KEY name I had previously. - Signer documented as the AIBTC canonical pipeline; mnemonic-based, not raw-private-key. .ts code refactor to match this pattern lands in the next commit.
…l AIBTC signer pipeline After `npx -y skills add aibtcdev/skills/wallet` and reading src/lib/services/wallet-manager.ts + x402.service.ts, refactored the inline-stake signer-loading to match the canonical x402.service.getAccount() pattern: 1. Path 1 (registry env): dynamic-import getWalletManager() from ../src/lib/services/wallet-manager.js. Try getActiveAccount(), then restoreSessionFromDisk(walletId) to pick up cross-process unlock state written by `wallet unlock`. Use Account.privateKey as senderKey. 2. Path 2 (bff-skills env): read process.env.CLIENT_MNEMONIC (the env var x402.service falls back to at line 894). Derive a signer via @stacks/wallet-sdk.generateWallet() and use accounts[0].stxPrivateKey. Drops the wrong AIBTC_WALLET_PRIVATE_KEY env var name entirely. Skill is now consistent with the canonical AIBTC pattern documented in the infrastructure code, and works in both bff-skills (env-var) and registry (wallet-manager) environments via dynamic imports + graceful fallback. The only other change is the deferred-stake reason text in runPlan, updated to reflect the canonical pipeline (not the wrong env-var name).
…h canonical CLIENT_MNEMONIC pattern Three lingering references to the old wrong env-var name updated to match the canonical AIBTC pattern (wallet-manager primary, CLIENT_MNEMONIC fallback) documented in SKILL.md and implemented in the .ts.
…1/2) Per user directive: rename skill to encode the asset journey explicitly. sBTC (collateral) -> USDCx (debt) -> sUSDh (final staked output). USDh is the transient swap intermediate (omitted from the name). This commit pushes the 4 small files (SKILL.md, AGENT.md, package.json, preflight-screen.sh) at the new path. Companion commit will push the .ts. Old path files will be deleted after both pushes land. SKILL.md additions over the previous version: - New "Asset journey" table making the sBTC->USDCx->USDh->sUSDh path explicit, including the on-chain effect at each step. - Inline-stake section now quotes the Clarity bytecode showing that staking-v1-1.stake calls susdh-token-v1.mint-for-protocol — the wallet receives sUSDh as the direct on-chain result. - State machine end-state label clarified: "complete (= sUSDh in wallet)". preflight-screen.sh adds the Hermetica hBTC deployer (SP1S1HSFH0SQQGWKB69EYFNY0B1MHRMGXR3J1FH4D) to the allowlist — referenced in the research note even though this skill does not call hBTC contracts; prevents the scan from flagging it as a non-allowlisted address.
Same .ts as commit e481eeb (canonical signer pipeline + viability gate + self-impact sizing + strategy score), with the only two internal identifier changes required for the new directory name: - stateDir() path: "windleg-zestlend-hermeticastake-yield-rotator" -> "windleg-zestlend-hermeticastake-yield-rotator-sBTC-USDCx-sUSDh" - program.name(): same rename All other code is byte-for-byte identical to the previous .ts. After this commit lands, the old skills/windleg-zestlend-hermeticastake-yield-rotator/ directory becomes redundant and will be deleted in follow-up commits.
✅ Validation PassedSkill: All checks passed. This submission is ready for review. |
Plan/run was failing against zest-asset-deposit-primitive with "unknown option '--mempool-depth-limit'" because the primitive only supports --min-gas-reserve-ustx and --wait-seconds; --mempool-depth-limit is only supported by bitflow-swap-aggregator. Verified each primitive's actual CLI surface via --help: - zest-asset-deposit-primitive plan: --wallet, --deposit-asset, --amount, --min-shares, --min-gas-reserve-ustx - zest-asset-deposit-primitive run: + --confirm, --fee-ustx, --wait-seconds - zest-borrow-asset-primitive plan: --wallet, --collateral-asset, --borrow-asset, --amount, --min-gas-reserve-ustx - zest-borrow-asset-primitive run: + --confirm, --fee-ustx, --wait-seconds - bitflow-swap-aggregator run: --wallet, --token-in, --token-out, --amount-in, --slippage-bps, --fee-ustx, --min-gas-reserve-ustx, --mempool-depth-limit, --wait-seconds, --confirm Fix: replace the combined `commonGasArgs` helper (which emitted both --min-gas-reserve-ustx AND --mempool-depth-limit) with two per-flag helpers: - gasReserveArg(opts) -> --min-gas-reserve-ustx (universal) - mempoolDepthArg(opts) -> --mempool-depth-limit (swap-only) Updated all call sites in runPlan / runForward / runMonitor to use just gasReserveArg for supply/borrow primitive calls, and the swap primitive keeps the full commonSwapArgs (which now uses gasReserveArg + mempoolDepthArg + commonWaitArgs). Verified locally: bun run ... plan --wallet <addr> --sbtc-amount-sats 55000 --target-ltv 0.20 now returns status:success with the swap-slippage projection populated (the supply step correctly surfaces INSUFFICIENT_ASSET_BALANCE because the wallet doesn't hold 55,000 sats sBTC yet, but the controller envelope itself is success).
|
Status: rotation broadcast pending — transaction hashes incoming. The 5-leg mainnet rotation is queued and ready to broadcast (STX→sBTC top-up swap → sBTC supply on Zest → USDCx borrow on Zest → USDCx→USDh swap on Bitflow The five Reviewing the structural / validator side of the submission is unblocked — the Will edit the body when the broadcast confirms. Generated by Claude Code |
… draft Companion to PR #604 (the wind skill). Reverses a position created by the wind leg through a 5-tx sequence separated by the on-chain 7-day Hermetica cooldown: Phase 1 (Day 0): 1. Initiate Hermetica unstake (staking-v1-1.initiate-cooldown) - burns sUSDh, starts 7d timer. Cooldown (no broadcast, checkpointed at cooldown_pending). Phase 2 (Day 7+): 2. Complete unstake (staking-v1-1.complete-unstake) -> USDh back. 3. Swap USDh -> USDCx on Bitflow dlmm_8 via bitflow-swap-aggregator. 4. Repay Zest USDCx debt (zest-auto-repay or inline market.repay). 5. Withdraw sBTC collateral (inline market.collateral-remove). This commit ships only the structural draft: - SKILL.md with valid frontmatter + the registry-required sections - AGENT.md with valid frontmatter + decision/guardrail rules - A placeholder .ts entry point that returns a CODE_IN_PROGRESS envelope on every subcommand. The .ts conforms to the JSON output contract so the validator's structural checks pass. The full implementation (mirroring the wind skill's score / plan / run / resume / monitor surface) will land in subsequent commits. Author + author-agent match #604 (IamHarrie-Labs / Serene Spring). HODLMM integration declared honestly as No (same reason as #604).
Per directive: leave the author + author-agent fields as TODO placeholders on this unwind PR. They'll be populated later. The wind PR (#604) keeps its IamHarrie-Labs / Serene Spring values; only this PR is being left blank. author: "TODO-set-github-handle" author-agent: "TODO-set-author-agent" user-invocable: "false" (unchanged - validator requires string "false")
macbotmini-eng
left a comment
There was a problem hiding this comment.
Re-tested at HEAD d33e71c173ecd8b7bf598fc3e264134324d00604. All findings from prior reviews are closed:
| Source | Resolution |
|---|---|
| Original 6 items at #604 (review) | 63c2100 + 9dd9031 |
hexToBig regression + fetchBitflowTokens silent-timeout from #604 (review) |
3d20803 |
Autonomous-resume gap + fetchSusdhTotalSupply precision + 9 fetcher silent-catches (proactive) |
d33e71c |
Bool-inversion fix is independently verifiable against the on-chain stake at https://explorer.hiro.so/txid/0x42637576627d58b0f7f1136a3cfcc69d762973e3b0aaaabd85a6c11f043e2f8e?chain=mainnet (2026-05-11T18:57:30Z).
…ecoder + carry HTTP_TIMEOUT_REGISTRY from #604 Agent's mainnet smoke test caught a hard blocker that made every Phase 1 broadcast fail with `BadFunctionArgument` pre-mempool. ABI verified live via Hiro `/v2/contracts/source/.../staking-v1-1`: (define-public (stake (amount uint) (affiliate (optional (buff 64))))) (define-public (unstake (amount uint))) The skill conflated the two signatures and passed `[uintCV(amount), noneCV()]` to unstake — chain rejects 2 args when ABI expects 1. 1. CRITICAL: drop the `noneCV()` arg from `inlineUnstake`'s functionArgs. Skill will not work end-to-end on Phase 1 until this lands. No broadcast / no on-chain effect under the buggy code path — wallet stays at pre-run state, no funds at risk, but the rotation cannot start. 2. SKILL.md doc drift paired with #1: "Verified contracts" table line for unstake now reads `unstake(uint)` matching the actual ABI; sister `stake` (with the affiliate arg) is noted explicitly so a future maintainer doesn't conflate. 3. `fetchSiloClaim` was decoding the silo claim tuple via a regex heuristic that picked "first uint = amount, last uint = unlock-ts." Worked by accident because the actual tuple has exactly 2 uints. The real contract surface (verified via Hiro source): (define-map claims { claim-id: uint } { recipient: principal, amount: uint, ts: uint }) 3 value fields, field name is `ts` (not `unlock-ts`), no `claim-id` inside the value record. Any Hermetica upgrade that added a uint anywhere in the response would have silently shifted the heuristic and the cooldown gate would have read the wrong timestamp. Replaced with proper `cvToJSON` decoding keyed on field name. JS-side return field stays `unlockTs` for caller-stability — explained in the docstring as a rename from the on-chain `ts` field for semantic clarity. 4. Carried the `HTTP_TIMEOUT_REGISTRY_MS = 20000` constant + stderr- logging pattern from PR #604's most recent commit: - Constant defined in this skill. - `httpJson` accepts an optional `timeoutMs` override. - `fetchBitflowTokens` uses the longer timeout and logs failures / 0-token responses via stderr. - `callReadHiro` adds stderr logging on its catch path. - New `logFetchFailure(fnName, error)` helper near the output() helpers; mirrors the #604 pattern. JSON-on-stdout contract preserved (stderr is separate). bun --target=bun --no-bundle transpile clean. Deferred (per agent's "design gap" item #6): pre-broadcast static ABI arg-count + Clarity-type check in `doctor`. Worth doing but out of scope for this fix — would catch the #1-class of regression going forward. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Hey @TheBigMacBTC, genuinely appreciate you picking up #506, tracking down all those bugs (the Clarity bool inversion + SDK drift were nasty ones), and pushing this through the full review cycle. That is a lot of work on a skill you did not have to touch. @diegomey, all four of your follow-ups from the re-review were addressed in comment #5, and macbotmini-eng has since approved at HEAD d33e71c. Would love to get your formal sign-off if everything checks out on your end. The tx hashes are all verifiable on Hiro and the provenance caveat on tx 5 is now disclosed in the PR body. |
@IamHarrie-Labs Thank you for participating in the comp. Keep making waves! 🌊 |
diegomey
left a comment
There was a problem hiding this comment.
Final review at HEAD d33e71c1 — APPROVED
All 4 follow-ups from my prior re-review at 9dd9031a are closed, plus the regressions surfaced by macbotmini-eng's smoke test, plus a proactive audit of related code paths. CI green, independent reviewer approval at HEAD, body accurately reflects code state.
✅ My prior follow-ups — all addressed
| Item | Status |
|---|---|
| 1. Tx 5 provenance | Honestly disclosed in body: "this tx was broadcast manually via the call_contract MCP, not the skill's inlineStake — the pre-fix inlineStake was unrunnable due to the Clarity decoder + SDK API bugs subsequently fixed in 63c2100 + 9dd9031. It demonstrates that Hermetica accepts stakes from the wallet, but the skill's orchestrated stake leg at HEAD 9dd9031 has not yet been broadcast-tested end-to-end; a follow-up real-broadcast run on the post-fix code is needed to fully close §14 for leg 5." |
| 2. Re-score with corrected Hermetica reads | Both score and monitor --mode hitl JSON snippets carry explicit "captured pre-63c21009" annotations. Refresh deferred to a fresh score invocation. |
| 3. Pre-submission checklist box | Flipped to [x] with pointer to tx 5 provenance caveat. |
| 4. Date the JSON snippets | Same annotation as #2. |
✅ Macbotmini-eng's smoke-test regressions — all fixed
The 9dd9031a smoke test surfaced two regressions from the 63c21009 rename pass:
hexToBig is not definedatfetchHermeticaExchangeRate+fetchSusdhTotalSupply— fixed in3d208034(both call sites updated todecodeClarityUint)fetchBitflowTokenssilent timeout at 5s — fixed in3d208034(newHTTP_TIMEOUT_REGISTRY_MS = 20000, optionaltimeoutMsoverride onhttpJson, stderr logging on fetch failure / 0-token response)
✅ Proactive audit at d33e71c1 — three additional bugs caught before they shipped
| Class | Fix |
|---|---|
| Autonomous monitor stranded partial rotations | runMonitor --mode autonomous previously had no resume path; now fires intendedAction = "resume" when checkpoint sits at supply_confirmed | borrow_confirmed | swap_confirmed with no strategy blockers, gated on the 24h rate limit + autonomous confirmation token |
fetchSusdhTotalSupply precision loss |
Was Number(big) (lossy at 2^53 atomic units ≈ 90M sUSDh); now returns supplyBase: bigint, self-impact pool-share-cap calc consumes the bigint directly |
| 9 fetcher silent-catches | New logFetchFailure(fnName, error) writes a single stderr line per failed fetch. Applied to callReadHiro, fetchBtcHistory30d, fetchBinanceFundingInstant, fetchBinanceFunding7dMA, fetchWalletUsdcBalance, fetchWalletUsdhBalance, fetchBitflowQuote, fetchZestUsdcPoolStats, checkHermeticaStakingEnabled |
Two findings from the audit were discarded after primary-source verification (sUSDh receive-PC under Deny mode not required; swap primitive does emit both balances and balancesAfter) — that's the right discipline.
✅ Independent reviewer approval
macbotmini-eng (the contributor who ran the mainnet smoke test that surfaced 6 of the original blockers) approved at HEAD d33e71c1 (review #4267959139), with all findings tracked closed.
On the leg 5 provenance caveat
Tx 5 (0x42637576627d58b0…) was broadcast manually via call_contract MCP, not the skill's inlineStake. This is honestly disclosed in the body and the §14 checkbox carries an explicit pointer to the caveat. The fixes that made inlineStake runnable (Clarity bool inversion + v6/v7 SDK adaptation) are in the code at HEAD — what's missing is a re-broadcast through the skill itself to demonstrate end-to-end.
Closing this gap definitively would require either:
- Another full rotation (~$45 sBTC collateral + gas), or
- Waiting 7+ days for the existing sUSDh to clear the Hermetica cooldown and re-staking
Neither is a quick action. The honest framing in the body is the right call — better than pretending the manual stake is skill-driven proof. The competition rules (#484 §5) require "tx_status: success, sender matches, contract/function matches claim" — leg 5 satisfies success + sender + contract/function on the underlying Hermetica protocol, but not the claim "this tx was orchestrated by inlineStake." The disclosure makes that gap explicit rather than papering over it.
I'm comfortable approving with this caveat documented because:
- Legs 1-4 are real skill-driven proofs
- The disclosure is transparent, not buried
- The code path that would produce tx 5 via the skill is now functional (Clarity decoder + SDK adaptation fixes verified)
- macbotmini-eng's review chain caught and closed every functional bug in the inline-stake path
- The skill is testable end-to-end against fresh USDh in any follow-up cycle
If you want to gate strictly, the path is: top up the wallet with fresh USDh (via the wind path or external transfer), run --confirm=ROTATE to broadcast leg 5 via inlineStake, replace the manual-stake tx in the body with the new one. Worth doing as a follow-up cycle, not a merge gate.
Code quality summary
This is genuinely good engineering work end-to-end:
- 30 commits of progressive improvements with detailed messages
- Two critical Clarity decoder bugs caught + fixed in
63c21009(type-tag overshoot + bool-byte inversion) — would have produced silently-wrong reads in production - v6/v7 SDK runtime adaptation in
9dd9031a— anticipates the bff-skills convention of dynamic dep imports - Rename-miss regression caught + fixed in
3d208034—hexToBig→decodeClarityUintat 2 call sites - Silent-catch audit + fix across 9 fetchers in
d33e71c1 - Honest HODLMM declaration — declines the bonus despite using
dlmm_8for swap, per the LP/destination-only criterion - Honest leg 5 provenance — discloses the manual diagnostic stake rather than claiming end-to-end skill proof
The "engineering between reviews" pattern this PR demonstrates is exactly the discipline competition winners should be evaluated on. Worth flagging as a positive precedent for future Claude-Code-authored submissions.
Verdict
Approved at HEAD d33e71c1. Merge when ready.
Optional follow-up worth tracking (not merge-blocking):
- Re-broadcast leg 5 via the skill's
inlineStakeonce the wallet has fresh USDh, replace tx 5 in the body with the skill-driven version - Re-run
scoreandmonitor --mode hitlagainst the post-fix code, refresh the snippets in the body
Both are operator-driven and don't change the code state.
Approval generated by Claude Code on behalf of @diegomey.
Generated by Claude Code
|
Re: #604 (review) Thanks for the approval. On the leg 5 provenance caveat — agree the honest disclosure was the right framing rather than papering over the manual Two optional follow-ups will land in the next cycle:
Neither gates the current approval. |
…Enabled The pre-flight check that gates `inlineStake` does a single fetch against Hiro `/v2/contracts/call-read/.../staking-state-v1/get-staking-enabled` with a 5s timeout. When the per-minute /v2 bucket is exhausted on the caller's IP (common after operator orchestration bursts), the fetch returns HTTP 429 and the function returns `null` — which `inlineStake` raises as `HERMETICA_STATE_UNREADABLE`, blocking the leg even though the on-chain state is healthy and the bucket would refresh in seconds. Wraps the fetch in a 4-attempt loop with linear backoff (5s/15s/30s between retries; max ~50s total). Triggers on `res.status === 429` or a thrown fetch error (timeout / network). The inner JSON parsing and 0x03/0x04 Clarity bool decode are unchanged. `logFetchFailure` still records the failure on stderr after the last attempt. Observed during the leg-5 skill-driven smoke test: a fresh `inlineStake` invocation hit the 429 path on two consecutive attempts, then completed cleanly on the third once the bucket refreshed naturally. With this patch the rotation survives normal bucket churn without operator intervention. Skill-driven stake proof produced via `resume --confirm=ROTATE` on a hand-primed `swap_confirmed` checkpoint (200000000 atoms USDh, ~2 USDh staked): https://explorer.hiro.so/txid/0ee4446b3f7e81597e05414ccaeb486892a19020b4eeb9e5e8b33883a1b425ad?chain=mainnet — sender SP2G6TM8...QCCCQT8, contract SPN5AKG35QZSK2M8GAMR4AFX45659RJHDW353HSG.staking-v1-1, function stake, args [u200000000, none], post_condition_mode deny + sent_equal_to 200000000 usdh, tx_status success, (ok true), block 7942648. bun --target=bun --no-bundle transpile clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Follow-up after commit Leg-5 skill-driven proof: Architectural note for reviewers (so the testing pattern below doesn't read as a workaround): This PR's windleg is wind-only by design; PR #605 unwindleg is its companion; a future third skill will orchestrate both. The per-leg So the leg-5 proof was produced the way an orchestrator would invoke it:
Checkpoint at finish carries
|
`fetchJson` is the shared fetch helper used by doctor's zest+hodlmm checks and the loop's price/quote reads. On a single 429 from Zest's `/v1/pools` per-minute bucket, doctor returned `zestApi: false` and surfaced "Zest API unreachable" — but the bucket would refresh in seconds and the API is actually healthy. False-negative blocked the loop's entry path. Wraps the inner fetch in a 4-attempt loop with linear backoff (5s/15s/30s; max ~50s total). Triggers on `res.status === 429` or a thrown error (timeout / network). Existing return shape unchanged — all callers (zest pool fetch, hodlmm APY, price reads, etc.) get the retry for free. Same defensive pattern landed in PR #604's `checkHermeticaStakingEnabled` (commit 581cec7). Cross-referenced in PR #604 architectural note #604 (comment) bun --target=bun --no-bundle transpile clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… inline broadcasts Brings the skill to the same inline-end-to-end bar as #605 (unwindleg) — every write leg now broadcasts directly via @stacks/transactions instead of dispatching to primitive subprocesses, with explicit tx_status: success polling between legs to serialize nonces and avoid ConflictingNonceInMempool rejections on back-to-back rotations. What's inline now (was: runPrimitive(zest-asset-deposit-primitive, zest-borrow-asset-primitive, bitflow-swap-aggregator)): - inlineSupply(wallet, sbtcAmountSats) — v0-4-market.supply-collateral-add with 3 wallet/market/vault willSendLte post-conditions. Mirrors skills/zest-asset-deposit-primitive/ exactly. - inlineBorrow(wallet, amountAtomic) — v0-4-market.borrow with Pyth Hermes update bytes (live fetch for sBTC + USDCx feeds) and 2 vault/Pyth-fee post-conditions. Mirrors skills/zest-borrow-asset-primitive/ exactly. - inlineSwap(wallet, usdcxAmountBase, slippageBps) — dlmm-swap-router-v-1-2.swap-y-for-x-simple-range-multi for the USDCx → USDh wind direction (pool token-x = USDh, token-y = USDCx; inverts the unwindleg's swap-x-for-y for the same pool). min-dy fetched live from Bitflow /quote, enforced on chain. Polling between legs (waitForTxConfirmation + requireTxSuccess) mirrors the pattern at line 256 of the unwindleg skill. parseWaitSeconds parses the --wait-seconds option. The 4 write call sites updated: runForward leg 1 supply, continueForward leg 2 borrow, continueForward leg 3 swap, autonomous cmdRun leg 1 supply. Read-side runPrimitive calls (doctor/status/plan) intentionally retained — the inline-end-to-end bar is about write broadcasts, not state reads, and read composition adds no broadcast risk. Post-swap observedUsdh derived via wallet balance delta (fetchWalletUsdhBalance) since the DLMM router does not emit a caller-friendly observed-out in tx_result. 5 inline-end-to-end markers now present at skill level: @stacks/transactions (21), makeContractCall (6), broadcastTransaction (15), tx_status (2), checkpoint (42). Validator passes, manifest builds, Bun --no-bundle transpile clean. Diego's approval at d33e71c is invalidated by the new HEAD — expected for a refactor of this scope; re-review will be requested.
… broadcast path Smoke test of HEAD 91083c6's inline 4-leg architecture surfaced three blockers that each prevented the wind path from completing on chain. This patch lands the minimal fixes — supply + borrow legs now confirm end-to-end on mainnet via the skill; leg 3 (swap) broadcasts cleanly but aborts on chain via a separate post-condition bug (filed below under "Known remaining issue"). 1. Hiro /v2/* 429 cascade makeContractCall auto-fetches fee estimation from /v2/fees/transaction and the next nonce from /v2/accounts/<addr>. Plus the skill polls /extended/v1/tx/<txid> for tx_status. Under Hiro's per-IP per-minute /v2 quota, any one of these would 429 mid- rotation and abort the entire run with no retry. Fix is two parts: a) Explicit `fee: 30000n` in all 4 inline txParams (lines 1626 / 1733 / 1864 / 1949 for supply / borrow / swap / stake). This bypasses fee-estimation entirely — makeContractCall sees an explicit fee and never calls Hiro for it. 30,000 microSTX (0.03 STX) per leg is comfortably above typical mainnet floor fees in quiet windows; well below sponsor-pattern fees (70,000) and direct dust fees (~2,250-5,000) — picked to guarantee inclusion without overpaying. b) globalThis.fetch wrapper at module top with linear 5s/15s/30s backoff on res.status === 429. Catches Hiro 429s from any downstream call by @stacks/transactions (nonce, broadcast, tx_status polling). Single 4-attempt loop, total max ~50s wait per fetch. Matches the retry shape in checkHermeticaStakingEnabled (commit 581cec7). Pass-through for all non-429 responses — zero behavioral impact on healthy bucket conditions. Without (a), every single run attempt 429'd on the very first broadcast attempt within ~15s. Without (b), the run survived fee estimation but 429'd on nonce fetch. With both, legs 1+2 landed cleanly on chain. 2. Bitflow /quote response shape — `min_amount_out` Inline-swap parser at lines 1829-1831 typed the Bitflow /quote body as `{ min_received?; minReceived?; min_out?; minOut? }` and chained those four for the min-dx field. Bitflow's response now returns `min_amount_out` (verified empirically — full response shape captured below). Result: BITFLOW_QUOTE_SHAPE_UNEXPECTED on every inline-swap leg. Curiously, the skill's other quote-fetch site (`fetchBitflowQuote` at line 704) already handles `min_amount_out` correctly. The inconsistency between the two parse sites in the same file is itself worth a comment — likely missed during the refactor from primitive dispatch to inline broadcasts. This patch brings the inline parser into line with `fetchBitflowQuote`. Empirical Bitflow response shape (from skill output at HEAD 91083c6 pre-fix): { "success": true, "amount_out": "1785147200", "min_amount_out": "1758369992", "slippage_tolerance": 1.5, "route_path": [usdcx, usdh-token-v1], "execution_path": [{ pool_id: "dlmm_8", ... }], ... } On-chain evidence (from smoke test with this patch applied): - Supply: 0x86a52cd513b60ff7ff16762b1b78d1582a4a7cf95aa003c13b1f46510975948d (ok u54972) at block 7943842 - Borrow: 0x528b6bccfb37bfb57010e4610d7dc86f292157eb0b176d1ad5b34461405ebcbf (ok true) at block 7943847 — 17.860402 USDCx borrowed - Swap: 0x4b7fa7b309e9be410c71739a3c1c7b110178e5c7caa6c7a0b262f26aa6eb06a7 broadcast cleanly with explicit fee 30000 and min-dx u1758369992 (= the patched min_amount_out field). Contract returned (ok (tuple (in u17860402) (out u1785147200))) — logic succeeded — but aborted at transfer-event layer via abort_by_post_condition. See "Known remaining issue" below. Known remaining issue (not fixed in this patch): The inline-swap leg's post-condition `sent_less_than_or_equal_to 17860402 usdcx` aborts on chain despite Clarity returning the correct (in u17860402, out u1785147200) result. Three plausible root causes for the maintainer to investigate: (a) swap-y-for-x-simple-range-multi router adds the 8,930 USDCx fee to the wallet outflow rather than deducting from y-amount (b) deny-mode requires a receive-side PC for the USDh inflow that this router specifically emits (c) asset_name mismatch — the PC uses "usdcx-token" which matches on-chain, but maybe an additional transfer event under deny mode needs coverage Not landing a speculative fix without on-chain verification. The 3 fixes in this patch are necessary-but-not-sufficient; leg 3 still needs its PC widened. bun --target=bun --no-bundle transpile clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Follow-up after commit End-to-end smoke test of the post-refactor inline architecture at HEAD On-chain evidence with this patch applied:
Test sizing: 15,000 sats sBTC supplied at LTV 0.40, Three fixes in this commit:
Known remaining issue (not fixed here): the swap leg's post-condition
Not landing a speculative fix for this one without on-chain verification of which root cause is real. Suggested directions: widen the USDCx PC by the fee amount, OR add a USDh receive PC, OR drop the swap leg to bun |
|
@arc0btc - please review |
arc0btc
left a comment
There was a problem hiding this comment.
Re-review at HEAD b3ebef4
Reviewing at current HEAD b3ebef45d11ef420b5abbfaaae310416bbf981c8. Prior reviews covered d33e71c1; two additional commits have landed since (581cec7 + b3ebef4). CI passes.
What 581cec7 + b3ebef4 add
581cec7— Hiro 429 backoff oncheckHermeticaStakingEnabled+ skill-driven leg-5 re-broadcast viainlineStake(closes the provenance caveat that diegomey and macbotmini-eng flagged)b3ebef4— Three fixes surfaced by end-to-end smoke: explicitfee: 30000nin inline tx params (bypasses auto-fee Hiro call), globalfetch429 wrapper,BITFLOW_QUOTE_SHAPE_UNEXPECTEDfix for evolvedmin_amount_outfield shape
Leg-5 proof is genuinely closed now: 0x0ee4446b…425ad, block 7942648, (ok true), built and broadcast entirely inside inlineStake.
[blocking] Swap leg (leg 3) exits with abort_by_post_condition — Zest debt stranded on every rotation
The b3ebef4 comment discloses this directly:
tx
0x4b7fa7b309e9be41…— Clarity(ok (tuple (in u17860402) (out u1785147200)))— butabort_by_post_condition
Clarity execution succeeded. The post-condition check failed. The result: 15,000 sats sBTC supplied as Zest collateral + 17.86 USDCx borrowed into the wallet — and the swap rolled back. The wallet is now carrying Zest debt with no USDh path forward. The skill's entire purpose (holding sUSDh financed by sBTC collateral) is unreachable without manual intervention.
The author correctly identified three plausible root causes:
(a) Bitflow's swap router charges fee on top of amountIn — the quote returned "fee": "8930" as a separate field; if the router deducts y-amount + fee from the sender wallet, the PC covering exactly sent_less_than_or_equal_to 17860402 usdcx is too tight by 8,930 atomic units
(b) Deny mode requires a receive-side PC for the USDh inflow from the router (less likely — Stacks Deny mode gates outflows from the tx sender, not receives)
(c) Additional outbound transfer event under Deny mode not covered
Root cause (a) is the most likely given the quoted fee field. The fix is straightforward: widen the USDCx outflow PC by the fee amount — read fee from the Bitflow quote response and set the post-condition to sent_less_than_or_equal_to (amountIn + fee).
The author explicitly deferred: "Not landing a speculative fix for this one without on-chain verification of which root cause is real." Understood, but a speculative fix + one broadcast to confirm is the path here — without it, the skill cannot autonomously complete a rotation. Shipping with this bug means any user who runs the wind path ends up with an open Zest debt position and no sUSDh.
Suggested fix: Read the fee field from the fetchBitflowQuote response. Compute totalOut = amountIn + fee. Set the USDCx post-condition to sent_less_than_or_equal_to totalOut. Broadcast a test rotation to verify the PC passes. If that doesn't resolve it, escalate to root cause (c) — add a USDh receive-side allowance if the router happens to flow through the sender wallet.
Everything else at current HEAD
- Leg-5 provenance: fully closed — skill-driven broadcast proven end-to-end
- Original 6 macbotmini-eng findings: closed at
63c21009+9dd9031a, independently verified hexToBigregression + timeout swallow: closed at3d208034- Autonomous-resume gap + precision + fetcher catches: closed at
d33e71c1 - 429 backoff + quote shape: closed at
581cec7+b3ebef4 - CI: ✅ green, frontmatter valid, manifest passes
- Security architecture: Deny PC mode,
--confirm=ROTATEgate, slippage protection, balance pre-check — all sound. The issue is specifically the swap PC being too tight, not a systemic safety gap.
The design and the review discipline here are strong — the Clarity decoder catches alone prevented silent production damage. This is one known bug away from approval.
Review by arc0btc.
diegomey
left a comment
There was a problem hiding this comment.
Re-review at HEAD b3ebef45 — superseding my prior APPROVE at d33e71c1
My prior approval was DISMISSED by the new commits. Reviewing the delta and confirming arc0btc's findings.
✅ Major positive — leg-5 provenance caveat fully closed (581cec7)
The single open follow-up from my approval at d33e71c1 was the leg-5 provenance — tx 0x42637576... was a manual diagnostic stake, not skill-driven. That's now closed.
581cec7 re-broadcasts leg 5 via inlineStake end-to-end:
- New tx:
0x0ee4446b...425ad - Block 7942648,
(ok true) contract: SPN5AKG35QZSK2M8GAMR4AFX45659RJHDW353HSG.staking-v1-1,function: stake(u200000000, none)post_condition_mode: deny+sent_equal_to 200000000 SPN5AK….usdh-token-v1::usdh- Sender SP2G6TM8…QCCCQT8,
tx_status: success
The contract-call literal — args, post-conditions, network/SDK adaptation, signing, broadcast — was built and broadcast entirely inside inlineStake (windleg-….ts:1449). PR body's transaction-links section explicitly states this and acknowledges the historical 0x42637576... tx as background, not as the leg-5 proof.
This is the kind of conscientious closure of an outstanding caveat that I want to see. Good work.
🔴 BLOCKING — Leg 3 swap exits with abort_by_post_condition (arc0btc finding)
Confirming arc0btc's review. This is a real fund-safety bug.
The bug (0x4b7fa7b309e9be41...):
Clarity execution succeeded: (ok (tuple (in u17860402) (out u1785147200))). But the tx aborted on a post-condition check. Outcome:
- 15,000 sats sBTC supplied as Zest collateral ✓ (committed on-chain)
- 17.86 USDCx borrowed into wallet ✓ (committed on-chain, wallet now carries debt)
- Swap rolled back ✗ — sBTC still in collateral, USDCx debt outstanding, no USDh path forward
The wallet is now stuck with Zest debt and no skill-driven way to get to sUSDh. Running the wind skill in this state strands every user with a debt position. This contradicts the entire purpose of the skill.
Root cause (most likely): Bitflow's swap router charges its fee on top of amountIn (the quote response includes "fee": "8930" as a separate field). The current USDCx outflow PC covers sent_less_than_or_equal_to 17860402 usdcx — exactly amountIn. The router actually deducts amountIn + fee = 17869332 from the sender wallet, so the PC trips at sent = 17869332 > 17860402.
Suggested fix (per arc0btc):
- In
fetchBitflowQuoteresponse handling, read thefeefield - Compute
totalOut = amountIn + fee - Build the USDCx post-condition as
sent_less_than_or_equal_to totalOut - Broadcast one test rotation to verify the swap PC passes
If that doesn't resolve it (i.e., fee isn't the cause), escalate to checking for additional outflows under Deny mode. Receive-side PCs are unlikely to be the root cause since Stacks Deny mode gates outflows from the sender, not receives.
Why I'm not approving while this is open: the skill's stated purpose is autonomous sBTC→sUSDh rotation. Shipping with a stranded-debt failure mode means anyone running it ends up worse off than before. This isn't a "follow up later" item — it's the core write path failing.
Closed at this HEAD
| Item | Status |
|---|---|
| Leg-5 provenance caveat | ✅ closed at 581cec7 — skill-driven inlineStake proof |
| Original 6 macbotmini-eng findings | ✅ closed at 63c21009 + 9dd9031a |
hexToBig rename regression + timeout swallow |
✅ closed at 3d208034 |
| Autonomous-resume gap + precision + fetcher catches | ✅ closed at d33e71c1 |
| 429 backoff + Bitflow quote shape evolution | ✅ closed at 581cec7 + b3ebef4 |
| Explicit fee in tx params (bypasses Hiro auto-fee call) | ✅ closed at b3ebef4 |
The fixes you've shipped are genuinely good — the engineering-between-reviews pattern is the discipline I want from competition winners.
Path to merge
- Land the swap-leg PC fix: widen USDCx outflow PC to
amountIn + feefrom the Bitflow quote response - Verify with one mainnet rotation: a full sBTC→sUSDh wind that completes leg 3 without
abort_by_post_condition - Update PR body: replace the leg-3 tx with the verified one (the current
0x385d4618...predates the bug analysis); document the fee-fix in the security notes - Ping for re-review: I'll re-approve at the new HEAD once leg 3 broadcasts cleanly
Estimated cost: another small rotation (~$45 collateral + gas + slippage on a $17 swap). Worth it to ship a working skill.
Code quality summary
This PR has done 33 commits of progressive bug-catching:
- Two critical Clarity decoder bugs (type-tag overshoot + bool-byte inversion) — caught + fixed pre-merge
- v6/v7 SDK runtime adaptation — anticipating dependency drift
- Rename-miss regression — caught + fixed
- Silent-catch audit across 9 fetchers
- 429 backoff + Bitflow quote shape evolution
- Leg-5 skill-driven proof — closing the prior caveat
- Now: swap PC width — found through real-money smoke
Every previous round closed cleanly. This one will too — the swap PC fix is mechanical. Just needs to land before approval.
COMMENTED (no formal vote). Re-approve once the swap-leg PC fix + verification rotation lands.
Re-review generated by Claude Code on behalf of @diegomey.
Generated by Claude Code
Closes Diego's review #4302027450 BLOCKER: leg-3 swap exits with abort_by_post_condition despite Clarity returning (ok ...). Root cause: Bitflow's swap-y-for-x-simple-range-multi router deducts its fee from the sender wallet in the INPUT token (USDCx here), on top of amount_in. PC at HEAD b3ebef4 was built at amount_in (17,860,402 USDCx); the router deducted amount_in + fee (= 17,860,402 + 8,930 = 17,869,332 USDCx); Stacks Deny-mode sender PC tripped because sent (17,869,332) > ceiling (17,860,402). Clarity logic completed — returned (ok (tuple (in u17860402) (out u1785147200))) — but the transfer-event layer rolled back the swap. On-chain evidence: 0x4b7fa7b309e9be410c71739a3c1c7b110178e5c7caa6c7a0b262f26aa6eb06a7. Fix reads the fee field from Bitflow's /quote response and widens the sender PC ceiling to amount_in + fee: - fetchBitflowMinOutUsdh return shape changes from Promise<bigint> to Promise<{ minOut: bigint; fee: bigint }>. Body parser now also reads fee / swap_fee / swapFee from the response, fails loud with BITFLOW_QUOTE_MISSING_FEE if absent (can't build a safe PC without it). - inlineSwap destructures both, computes usdcxOutCeiling = amount_in + fee, uses that value in both PC code paths (v7 Pc builder + v6 makeStandardFungiblePostCondition fallback). - The function-arg passed to the router is still amount_in (unchanged) — the router charges its fee implicitly on top. INLINE COMPLIANCE PRESERVED. This is parameter widening inside the existing inline path; control flow unchanged. Same @stacks/transactions + makeContractCall + broadcastTransaction + Hiro poll + checkpoint chain as b3ebef4. No new primitives, no runPrimitive dispatch, no composition — PC values are data, not architecture. Conforms to the inline-end-to-end bar across all 4 write legs. Verification protocol (pending on-chain re-run): 1. Top up wallet with fresh sBTC collateral + USDC sponsor float 2. Run --confirm=ROTATE end-to-end 3. Confirm swap leg lands tx_status=success (not just clean broadcast as in 0x4b7fa7b... at b3ebef4) 4. Stake leg then proves inline-end-to-end path with post-91083c6 refactor architecture (current 0x0ee4446b... predates the refactor) bun --no-bundle --target=bun transpile clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arc0btc
left a comment
There was a problem hiding this comment.
Re-review at HEAD 56cc42ad
My prior CHANGES_REQUESTED at HEAD b3ebef45 had one blocking item: the swap leg exiting with abort_by_post_condition due to Bitflow's router deducting its fee on top of amount_in from the sender wallet. 56cc42ad closes it.
[resolved] Swap-leg PC width — fee-widened ceiling
Fix verified correct:
fetchBitflowMinOutUsdh return shape widened to { minOut: bigint; fee: bigint }. The body parser now reads fee / swap_fee / swapFee from the quote response and fails loud with BITFLOW_QUOTE_MISSING_FEE if absent — can't build a safe PC without it. inlineSwap computes usdcxOutCeiling = amount_in + fee and passes that ceiling to both the v7 Pc.principal(wallet).willSendLte(usdcxOutCeiling) path and the v6 makeStandardFungiblePostCondition(wallet, FungibleConditionCode.LessEqual, usdcxOutCeiling, ...) fallback. Function-arg to the router remains amount_in (unchanged) — router charges fee implicitly on top. Both code paths consistently updated.
Root cause was definitively confirmed on-chain (0x4b7fa7b3...: Clarity returned (ok (tuple (in u17860402) (out u1785147200))), PC tripped at sent 17,869,332 > ceiling 17,860,402). The fix is mechanical — no speculation required. Transpile clean.
Pending: the commit notes verification protocol pending one fresh end-to-end rotation to confirm swap leg lands tx_status=success with the fee-widened PC. This is the expected confirmation step. The logic is sound; verification is the author's post-merge or pre-final-approval responsibility depending on competition rules.
All prior findings closed
| Finding | Status |
|---|---|
| Original 6 macbotmini-eng bugs (Clarity decoder, SDK compat, etc.) | ✅ 63c21009 + 9dd9031a |
hexToBig regression + timeout swallow |
✅ 3d208034 |
| Autonomous-resume gap + sUSDh precision | ✅ d33e71c1 |
| 429 backoff + Bitflow quote shape | ✅ 581cec7 + b3ebef4 |
| Leg-5 provenance (skill-driven stake proof) | ✅ 581cec7 |
| Swap-leg PC too tight (my blocking item) | ✅ 56cc42ad |
CI passes. BITFLOW_QUOTE_MISSING_FEE fail-loud is strictly better than the prior silent over-spend. Security architecture (Deny PC mode, --confirm=ROTATE gate, 7d cooldown disclosure, slippage protection, balance pre-check) is sound throughout.
Approved. Verification rotation is the remaining step before production use — the fix is correct by inspection.
Review by arc0btc.
|
@diegomey — addressed at 56cc42a. Your root-cause read was correct — Bitflow's router charges the swap fee on top of
Concretely on the failure case at https://explorer.hiro.so/txid/0x4b7fa7b309e9be410c71739a3c1c7b110178e5c7caa6c7a0b262f26aa6eb06a7?chain=mainnet: PC ceiling was 17,860,402 USDCx; router deducted 17,860,402 + 8,930 = 17,869,332 USDCx. New ceiling at Inline-end-to-end preserved. Parameter widening inside the existing inline path — same Pending: verification rotation on mainnet — |
diegomey
left a comment
There was a problem hiding this comment.
APPROVE at HEAD 56cc42ad — superseding my COMMENTED at b3ebef45
The leg-3 swap-PC blocker I raised at b3ebef45 is closed at 56cc42ad. arc0btc approved independently at the same HEAD. Code is fund-safety correct.
✅ Leg-3 abort_by_post_condition — closed at 56cc42ad
Fix verified by inspection:
| Change | Verification |
|---|---|
fetchBitflowMinOutUsdh return shape widened to { minOut: bigint; fee: bigint } |
Two-field return; both consumed by inlineSwap |
Body parser reads fee / swap_fee / swapFee from Bitflow quote response |
Field-name flexibility for upstream evolution |
Fails loud with BITFLOW_QUOTE_MISSING_FEE if absent |
Strictly safer than prior implicit behavior — can't silently build a too-tight PC |
usdcxOutCeiling = amount_in + fee |
Closes the 8,930-atomic-unit gap that aborted 0x4b7fa7b3... |
Both v7 Pc.principal(wallet).willSendLte(usdcxOutCeiling).ft(...) and v6 makeStandardFungiblePostCondition(wallet, FungibleConditionCode.LessEqual, usdcxOutCeiling, ...) updated |
Symmetric across SDK adapter paths |
Function arg to router remains amount_in (unchanged) |
Router charges fee implicitly on top — matches contract semantics |
Root cause was definitively confirmed on-chain at b3ebef45: (ok (tuple (in u17860402) (out u1785147200))) returned by Clarity, PC tripped at sent 17,869,332 > ceiling 17,860,402 = exactly the fee delta (8,930). The fix narrows the gap by exactly that amount. Mechanical.
Metadata confirmed
Verified skills/windleg-.../SKILL.md at HEAD 56cc42ad:
metadata:
author: "IamHarrie-Labs"
author-agent: "Serene Spring"
user-invocable: "false"Matches the requested attribution. Both fields present, properly quoted, lowercase-hyphenated keys per the validator schema.
All prior findings closed
| Finding | Closed at |
|---|---|
| Original 6 macbotmini-eng bugs (Clarity decoder, SDK compat, etc.) | 63c21009 + 9dd9031a |
hexToBig regression + timeout swallow |
3d208034 |
| Autonomous-resume gap + sUSDh precision + 9 fetcher silent-catches | d33e71c1 |
| 429 backoff + Bitflow quote shape evolution | 581cec7 + b3ebef4 |
Leg-5 provenance — skill-driven inlineStake proof on chain |
581cec7 (tx 0x0ee4446b...) |
| Leg-3 swap PC too tight (my blocking item) | 56cc42ad |
Code quality summary — final pass
This PR has done 34 commits of progressive bug-catching, each closing cleanly:
- Two critical Clarity decoder bugs (type-tag overshoot + bool-byte inversion) — caught + fixed pre-merge
- v6/v7 SDK runtime adaptation — anticipating dependency drift
- Rename-miss regression — caught + fixed
- Silent-catch audit across 9 fetchers
- 429 backoff + Bitflow quote shape evolution
- Explicit
fee: 30000nto bypass Hiro auto-fee - Leg-5 skill-driven
inlineStakeproof - Swap-leg PC fee-widening — closes the stranded-debt failure mode
The "engineering between reviews" pattern across this cycle is exemplary. Each round of review surfaced real bugs; each round closed with the right fix. The Clarity-decoder catches alone prevented silent production damage.
Recommended pre-production verification (not merge-blocking)
arc0btc and I both note: a fresh end-to-end rotation that broadcasts leg 3 successfully with the fee-widened PC would convert "correct by inspection" into "correct by observation." This is the standard "trust but verify" step for a fund-safety fix. Estimated cost: ~$45 collateral + gas for a small wind cycle.
This is the expected confirmation step before production use, not a merge gate. The fix is mechanically obvious; the verification is empirical confirmation that root cause (a) was definitive (vs root causes (b) or (c) from the earlier triage). Worth doing in the next session.
If a verification broadcast surfaces an unexpected issue (e.g., (b) or (c) was actually the cause), it would be a follow-up commit and re-review, not a blocker on this PR landing.
Verdict
APPROVED for merge. Author can squash-merge the 34 commits whenever ready.
Optional follow-ups (not blocking):
- Verification rotation through the skill to land a clean swap-leg tx and replace
0x385d4618...in the body proof table - Re-run
scoreandmonitor --mode hitlagainst the post-fix code, refresh body snippets
Both are operator-driven and don't change the code state.
Excellent submission across the cycle. Thanks for the engineering discipline.
Approval generated by Claude Code on behalf of @diegomey.
Generated by Claude Code
|
@diegomey @arc0btc — post-merge follow-up. Post-merge statusThis PR was merged at HEAD On-chain test against content-identical SHA Pool-PC delta is pushed at 07d2495 on branch What it changes: On-chain proofHEAD tested: agent-applied Leg 3 — Swap: https://explorer.hiro.so/txid/0x75dc35e7e367da20da28ba8e4d7cd46a97d4b7fade7cb9a45186568f3d44fdde
Hiro-verified PC structure on the swap tx:
Leg 4 — Stake: https://explorer.hiro.so/txid/0xae5613d155250ed1495413279ae71e1cc7f3ade18b91368b43701509995a4ff4
How the fix lands on aibtcdev/skills#387Commits live on
#387 head is on
Partner PR — unwind direction#605 is the symmetric unwind partner (sUSDh → USDh → USDCx → repay → withdraw sBTC). Diego's gap analysis at #605 (comment) references this PR's Note: arc0btc CHANGES_REQUESTED at aibtcdev/skills#387 (review) is a separate merge gate on the upstream promotion and will be addressed in its own round. |
… 2/3/5 Empirical P0 from on-chain test at tx 5a13b62290071b700713be9f181ecfc2929070e2bde166f5dd4fcf761674d411 block 8058615: leg-5 collateral-remove-redeem reverted (err u400009) because the `ft` arg was the underlying SIP-010 sbtc-token instead of Zest's per-asset vault wrapper v0-vault-sbtc. PC then aborted on `1 SentGe 0` because the market sent 0 of the underlying. Same bug class PR #588 fixed for the sibling sbtc-leverage-unwind-planner skill. Companion fixes for legs that emit unpredictable token movements under Deny mode (heuristic, untested but pattern-confirmed from PR #604 cycle 3 at tx d1d45dad...0ec0 block 7966778 — "Fungible asset usdh was moved by dlmm-pool-usdh-usdcx-v-1-bps-1 but not checked"): - broadcastContractCall now accepts a per-call postConditionMode override. Defaults to Deny (preserves existing leg-1 / leg-4 behavior). - Leg 5 (collateral-remove-redeem): vault wrapper as ft + Allow mode. vaultPC stays as additive informational guard on the underlying outflow; contract's min-underlying arg is the slippage floor. - Leg 3 (Bitflow DLMM swap): Allow mode. Pool's internal USDh movement is not covered by sender/receiver PCs; min-dy contract arg is the slippage floor. senderPC + receiverPC remain as additive guards. - Leg 2 (silo withdraw): willSendGte(99% of expected) + Allow mode to tolerate accrued-interest micro-drift between claim creation and withdraw. Legs 1 (unstake) and 4 (repay) stay Deny — simple single-transfer calls. Empirical proof from this run (verified working under live mainnet): inline broadcastContractCall builder, waitForTxConfirmation polling, requireTxSuccess failure handling, RESIDUAL_DEBT_AFTER_REPAY guard, fetchZestScaledDebt Hiro readonly. Test report: #605 (comment) Inline-end-to-end bar preserved: every leg still routes through the skill's own broadcastContractCall path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Skill Submission
Skill name:
windleg-zestlend-hermeticastake-yield-rotator-sBTC-USDCx-sUSDhCategory: Yield
HODLMM integration? No (declared honestly — see HODLMM section below)
What it does
Wind-only yield rotator that supplies sBTC as collateral on Zest V2, borrows USDCx against it, swaps USDCx → USDh via Bitflow's DLMM Quote Engine (viability-gated by pre-trade
price_impact_bps), and stakes USDh inline against the Hermeticastaking-v1-1contract. The caller ends holding sUSDh — yield-bearing dollar exposure financed by sBTC collateral. A 6-component strategy score (BTC regime, perp funding 7d MA, carry spread post-borrow-impact, carry trend, BTC realized vol, USDh peg via Bitflow) gates entry at composite ≥ 55;monitormode polls viability over time. The skill emitsUNWIND_RECOMMENDEDsignals when conditions deteriorate but never broadcasts unwind (that's the companion unwind skill, #605).Proof
inlineStake) was broadcast end-to-end via the skill at HEAD581cec7; provenance caveat from earlier commits is closed."Serene Spring"— verifiable viabun run agent-lookup/agent-lookup.ts lookup --name "Serene Spring"(resolves to the AIBTC-registered STX address for the agent)SPN5AKG35QZSK2M8GAMR4AFX45659RJHDW353HSG.staking-v1-1.stake(inline)supply-collateral-add,borrow(viazest-asset-deposit-primitive+zest-borrow-asset-primitive)dlmm_6(STX→sBTC top-up),dlmm_8(USDCx→USDh) viabitflow-swap-aggregator$8.91 USDCx) collateralized by sBTC supply ($44.54)Transaction links
| # | Phase | Leg | Primitive / call | Explorer link |
Proof transaction set (Stacks mainnet):
inlineStakeend-to-end at HEAD581cec7. ContractSPN5AKG35QZSK2M8GAMR4AFX45659RJHDW353HSG.staking-v1-1.stake(u200000000, none),post_condition_mode: deny+sent_equal_to 200000000SPN5AK….usdh-token-v1::usdh, sender SP2G6TM8…QCCCQT8,tx_status: success,(ok true)at block 7942648. Theswap_confirmedcheckpoint was hand-primed withswapOutUsdhBase: 200000000to invoke the stake leg in isolation (the skill has no leg-specific subcommand —resumeis the surface), but the contract-call literal — args, post-conditions, network/SDK adaptation, signing, broadcast — was built and broadcast entirely insideinlineStake(windleg-….ts:1449). This closes the prior provenance caveat from commits63c21009and9dd9031a: the post-fixinlineStakeis broadcast-tested end-to-end on chain. The historical manual0x42637576…e2f8etx (block 7934222) demonstrated Hermetica accepts stakes from this wallet but is no longer the leg-5 proof artifact.Smoke test results
bun run scripts/validate-frontmatter.ts skills/windleg-zestlend-hermeticastake-yield-rotator-sBTC-USDCx-sUSDh—status: "success", 0 errors, 0 warnings.{ "status": "success", "action": "All 1 skills passed validation", "data": { "validated": 1, "passed": 1, "failed": 0, "totalErrors": 0, "totalWarnings": 0 }, "error": null }bun run scripts/generate-manifest.ts— exit 0; skill listed inskills.jsonalongside the other passing skills.bun run skills/windleg-.../<entry>.ts doctor --wallet <addr>—status: "success". All 3 primitive dependencies resolve. Live Zest V2 contracts found (SP1A27KFY4XERQCCRCARCYD1CC5N7M6688BSYADJ7.v0-4-market,.v0-market-vault,.v0-assets,.v0-egroup). 4/4 BTC price sources reachable (3/4 in geo-blocked test envs). Bitflow token registry resolved.scoreagainst a live funded wallet (captured pre-63c21009— predates the Hermetica Clarity decoder fixes, so the carry-spread and carry-trend components were computed against the now-corrected reads. Composite may shift at HEAD9dd9031a; refresh pending a freshscoreinvocation on the post-fix code) — composite 91/100, recommendationENTER, 0 blockers. Sub-scores: btcRegime 87, realizedVol 92, peg 100.monitor --mode hitl --max-iterations 1(captured pre-63c21009— same caveat as thescoresnippet above) —status: "success", intendedAction"run", intendedReasoncomposite 92 >= min 55 and no blockers. HITL mode does not broadcast (by design).Verified contract identifiers (Issue #484 Section 6 — no fabricated IDs)
SPN5AKG35QZSK2M8GAMR4AFX45659RJHDW353HSG.staking-v1-1.stake(uint, optional buff 64)/v2/contracts/sourceat block 3,567,258 — exact bytecodeSPN5AK…HSG.usdh-token-v1(decimals 8, asset nameusdh)/v1/tokensregistrySPN5AK…HSG.susdh-token-v1(decimals 8, minted by the stake call)SPN5AK…HSG.staking-state-v1.get-cooldown-windowreturnsu604800(= 7d)SP120SBRBQJ00MCWS7TM5R8WJNTTKD5K0HFRC2CNE.usdcx(decimals 6)/v1/tokensregistrySM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token(decimals 8)/v1/tokensregistrySM1FKXGNZJWSTWDWXQZJNF7B5TV5ZB235JTCXYXKD.dlmm-pool-usdh-usdcx-v-1-bps-1(Bitflowdlmm_8)/api/app/v1/pools/dlmm_8empirical querySP1A27KFY4XERQCCRCARCYD1CC5N7M6688BSYADJ7.v0-4-market,v0-market-vault,v0-assets,v0-egroupzest-borrow-asset-primitivedoctorBitflow Quote Engine viability gate — empirical probe
Security notes
Write skill. Creates Zest V2 debt against sBTC collateral. Mainnet only. Hermetica unstake cooldown is 7 days — position cannot be unwound on short notice; this skill emits an
UNWIND_RECOMMENDEDsignal but does not broadcast unwind (that is companion PR #605's job). Explicit--confirm=ROTATErequired for forward writes;--confirm=AUTONOMOUSfor autonomous monitor start (no defaults). Swap viability gate--max-price-impact-bps(default 50 bps) refuses the swap leg when Bitflow Quote Engine reports impact above threshold; the USDh/USDCxdlmm_8pool is thinly capitalized (~$400 TVL) so active-bin capacity caps practical rotation size around $100 USDCx. Autonomous monitor rate-limited to one auto-action per 24h. Signer loaded via the canonical AIBTC pipeline matchingsrc/lib/services/x402.service.ts:getAccount()— trywallet-manager.restoreSessionFromDisk()first, fall back toprocess.env.CLIENT_MNEMONIC. No secrets logged, no secrets in JSON output. Post-conditions arePostConditionMode.Denywith explicit allowances; balance check before broadcast in inline stake (fetchWalletUsdhBalance≥ amount); slippage protection on the swap leg via--slippage-bps(default 150 bps).Author by
HODLMM integration declaration
No. The swap leg routes through HODLMM
dlmm_8because it is the only available USDh venue on Bitflow, but the skill does not LP into HODLMM as a destination. Per the bonus criterion ("skills that directly integrate HODLMM"), the qualifying integration is LP/destination, not swap-venue routing. Declared honestly rather than padded for the bonus pool.Pre-submission checklist (Issue #484 Section 14)
git diff --name-only origin/main...HEADshows onlyskills/windleg-zestlend-hermeticastake-yield-rotator-sBTC-USDCx-sUSDh/filesscripts/,README.md,package.json,bun.lock,src/, or other skill directoriesmetadata:nested,user-invocable:string,tags:/requires:comma-separated quoted strings,entry:repo-root-relative,author+author-agentboth present,descriptionquotedname,skill,description){"error": "..."}JSON (BFF-extension envelope explicitly allowed per SKILL_TEMPLATE.md);doctorsubcommand implementedbun run scripts/validate-frontmatter.tspasses (output pasted above)bun run scripts/generate-manifest.tsshows exactly one skill added (output pasted above)bun run skills/windleg-.../<entry>.ts doctorreturns OK (output pasted above)agent-lookup), contract/function matches claim,tx_status: success— see Transaction links section above; 5 txs broadcast and confirmed on Hiro, including the skill-driveninlineStaketx (0x0ee4446b…425ad) that closes the leg-5 provenance gap at HEAD581cec7.intendedAction: "run"on the live wallet)PostConditionMode.Allowwith empty array) —PostConditionMode.Deny+makeStandardFungiblePostConditionin inline stake; Zest legs use the primitives' own post-condition envelopes--slippage-bpspassed tobitflow-swap-aggregator(default 150 bps)fetchWalletUsdhBalance≥ amount in inline stake; primitives do their own balance checksbroadcastTransactionin inline stake; primitives broadcast their own legs