Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
053c095
feat(skills): add unwindleg-yield-rotator-sUSDh-USDCx-sBTC structural…
TheBigMacBTC May 11, 2026
9cd2806
chore(unwindleg): blank author + author-agent (placeholders for now)
TheBigMacBTC May 11, 2026
31d85f4
feat(unwindleg): full 5-leg unwind implementation across the Hermetic…
TheBigMacBTC May 11, 2026
3019978
fix(unwindleg): waitForTxConfirmation between inline legs to avoid no…
TheBigMacBTC May 11, 2026
da1e3b4
fix(unwindleg): asset-name static map + unstake-broadcast race + resi…
TheBigMacBTC May 11, 2026
585c1c4
chore(unwindleg): set metadata.author + author-agent to clear CI
TheBigMacBTC May 11, 2026
6da6a3b
fix(unwindleg): HARD BLOCKER inlineUnstake arg-count + proper tuple d…
TheBigMacBTC May 11, 2026
2cd0130
refactor(unwindleg): scope to 4 legs (unwind = remove leverage); drop…
TheBigMacBTC May 12, 2026
da88d91
Revert "refactor(unwindleg): scope to 4 legs (unwind = remove leverag…
TheBigMacBTC May 12, 2026
fdda79f
refactor(unwindleg): de-bundle leg-3 swap + pre-leg-5 readonly into d…
TheBigMacBTC May 12, 2026
7411eff
docs(unwindleg): refresh AGENT.md — drop primitive references, docume…
TheBigMacBTC May 12, 2026
c9eec5a
docs(unwindleg): clarify Hermetica cooldown clock semantic at consume…
TheBigMacBTC May 12, 2026
43560a4
feat(unwindleg): enforce --mempool-depth-limit before each inline bro…
TheBigMacBTC May 12, 2026
18add8a
fix(unwindleg): address Diego gap analysis Blockers 1 + 2 on PR #605
TheBigMacBTC May 16, 2026
6eabcf9
fix(unwindleg): leg-5 ft-trait vault routing + Allow-mode PCs on legs…
TheBigMacBTC May 23, 2026
0c6ac02
fix(unwindleg): leg-5 redeem amount must equal recorded collateral (n…
TheBigMacBTC May 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
name: unwindleg-hermeticaunstake-zestrepay-yield-rotator-sUSDh-USDCx-sBTC-agent
skill: unwindleg-hermeticaunstake-zestrepay-yield-rotator-sUSDh-USDCx-sBTC
description: "Coordinates a 5-leg unwind of the wind skill's (#604) position. Phase 1: unstake sUSDh on Hermetica (irreversible, starts 7d cooldown). Phase 2 (after cooldown): silo-withdraw -> swap USDh -> USDCx on Bitflow -> repay Zest USDCx debt -> withdraw sBTC collateral. Never broadcasts wind operations."
---

# Agent briefing — unwindleg

## Your job

Take a wallet from {sUSDh held, USDCx debt on Zest, sBTC collateral on Zest} back to {sBTC held, debt zero}. Five legs across a 7-day cooldown.

## Pre-flight (always run before any write)

1. `doctor --wallet <addr>` — must return `status: success`. Confirms Hermetica state is readable, the Bitflow token registry resolves USDh + borrow + collateral, and surfaces the six contract addresses (`data.contracts`) the skill calls or reads. This skill has zero external skill dependencies — leg 3 swap is a direct call to the Bitflow DLMM router and the pre-leg-5 residual-debt check is a direct readonly to the Zest market vault.
2. Read the wallet's sUSDh balance from the doctor output. Confirm `--susdh-amount-base` ≤ balance.
3. If a checkpoint already exists for the wallet (doctor surfaces it), do NOT call `run`. Either call `resume` (if the operator wants to continue the existing unwind) or `cancel` (if abandoning it).

## Run phase 1 (broadcast unstake)

```
run --wallet <addr> --susdh-amount-base <base> --min-sbtc-withdraw-sats <sats> --confirm=UNWIND
```

Expected outcomes:

- **`status: success` with `data.checkpoint.step = "unstake_confirmed"`** — unstake broadcast, silo claim recorded, cooldown is running. The response includes `cooldownExpiresAt` ISO timestamp. Schedule phase 2 (`resume`) for after that timestamp + the operator's grace margin.
- **`status: error` with `error.code = "CLAIM_ID_INDETERMINATE"`** — unstake broadcast but the silo claim-id counter didn't advance within `--wait-seconds`. The unstake tx is on chain; the controller just couldn't snapshot the claim-id atomically. Recovery: read `staking-silo-v1-1.get-current-claim-id` after the unstake tx confirms, then hand-edit the checkpoint to record the claim-id, then run `resume`. The error data includes `unstakeTxid` and `preClaimId` for forensics.
- **`status: error` with `error.code = "INSUFFICIENT_SUSDH_BALANCE"`** — operator requested more sUSDh than the wallet holds. Reduce `--susdh-amount-base`.
- **`status: error` with `error.code = "SIGNER_UNAVAILABLE"`** — none of `AIBTC_SESSION_FILE` / `STACKS_PRIVATE_KEY` / `CLIENT_MNEMONIC` resolved to a key matching `--wallet`. Surface the per-path attempt list to the operator and stop.

## Wait the cooldown

7 days by default. Read `data.checkpoint.cooldownExpiresAt` and the operator's `--cooldown-grace-seconds` (default 300). Do not call `resume` before `cooldownExpiresAt + grace`. The skill will reject early `resume` with `COOLDOWN_NOT_EXPIRED` including `secondsRemaining`.

## Run phase 2 (broadcast legs 2-5)

```
resume --wallet <addr> --confirm=UNWIND
```

This broadcasts in order: silo-withdraw → swap → repay → withdraw. If any leg fails, the checkpoint records the partial state and the next `resume` picks up from there. Idempotent across retries.

Expected outcomes:

- **`status: success` with `data.checkpoint.step = "complete"`** — all 5 legs broadcast. Wallet now holds sBTC (no sUSDh, no Zest debt). The response includes all 5 txids: `unstakeTxid`, `claimTxid`, `swapTxid`, `repayTxid`, `withdrawTxid`.
- **`status: blocked` with `error.code = "COOLDOWN_NOT_EXPIRED"`** — too early. Wait `error.data.secondsRemaining` seconds and retry.
- **`status: error` with `error.code = "SWAP_OUTPUT_UNKNOWN"`** — the swap tx confirmed but no ft_transfer event for USDCx to the wallet was found in the tx events. Inspect the tx on the explorer; this usually means the swap routed through a different asset path than expected. Repair before retrying.
- **`status: error` with `error.code = "BITFLOW_QUOTE_FETCH_FAILED"`** — Bitflow `/quote` endpoint unreachable when deriving the swap min-out. Retry after Bitflow recovers; the skill refuses to broadcast a swap without a fresh min-out.
- **`status: error` with `error.code = "RESIDUAL_DEBT_UNREADABLE"`** — the direct readonly to `v0-market-vault.get-account-scaled-debt` returned null. Inspect Hiro connectivity and the wallet address before retrying.
- **`status: error` with `error.code = "RESIDUAL_DEBT_AFTER_REPAY"`** — the repay leg confirmed but scaled debt is still > 0 (slippage / interest accrual). Top up the wallet with the borrow asset and call `v0-4-market.repay` directly until scaled debt returns 0, then resume.
- **`status: error` with `error.code = "BROADCAST_FAILED"`** — one of the inline broadcasts (silo-withdraw / swap / repay / collateral-remove-redeem) was rejected by the node. The error includes the reason; do not retry until the underlying cause is resolved.

## Recovery shortcuts

- **Silo claim exists on chain but checkpoint is wrong:** the silo claim is the source of truth. Call `staking-silo-v1-1.withdraw(claim-id)` directly via any Stacks tool to recover the USDh. Then complete legs 3-5 manually or fix the checkpoint and `resume`.
- **Operator wants partial unwind:** this skill is full-unwind for a given `--susdh-amount-base`. For partial collateral withdraw at the end, the operator should call Zest's `collateral-remove-redeem` directly with a specific `amount` instead of letting this skill use `max-uint128`.

## What you must NEVER do

- Broadcast any wind operation. The wind skill at https://github.com/BitflowFinance/bff-skills/pull/604 owns that path.
- Bypass the cooldown by re-broadcasting an unstake that already has a pending silo claim. The silo claim from the first unstake still exists; just wait for it.
- Hand-edit the checkpoint file unless explicitly recovering from `CLAIM_ID_INDETERMINATE`. The checkpoint is the controller's truth; arbitrary edits create inconsistent state.
- Set `--cooldown-grace-seconds 0`. Miner-time skew means the on-chain `unlock-ts` check can fire microseconds after the wall-clock matches. The default 300s margin is cheap insurance.

## Output contract

Every subcommand prints exactly one JSON object: `{ status, action, data, error }`. The agent should treat `status: "success"` as advance, `status: "blocked"` as wait-and-retry per `error.next`, and `status: "error"` as halt-and-surface-to-operator.

## Companion

Wind path: https://github.com/BitflowFinance/bff-skills/pull/604
Loading
Loading