Skip to content

feat(ststx-liquid-stacker): add StackingDAO liquid-stacking writer skill#547

Open
IamHarrie-Labs wants to merge 3 commits into
BitflowFinance:mainfrom
IamHarrie-Labs:feat/ststx-liquid-stacker-v2
Open

feat(ststx-liquid-stacker): add StackingDAO liquid-stacking writer skill#547
IamHarrie-Labs wants to merge 3 commits into
BitflowFinance:mainfrom
IamHarrie-Labs:feat/ststx-liquid-stacker-v2

Conversation

@IamHarrie-Labs
Copy link
Copy Markdown
Contributor

@IamHarrie-Labs IamHarrie-Labs commented Apr 27, 2026

Summary

Single-skill resubmission of ststx-liquid-stacker (split from closed PR #496 per reviewer feedback). Also addresses feedback from closed PR #446.

Reviewer blockers resolved

Blocker Status
Multi-skill bundling ✅ Fixed — this PR touches only skills/ststx-liquid-stacker/
Write-tag misrepresentation (mcp_command instead of real broadcast) ✅ Fixed — rebuilt with callContract + awaitConfirmation; every write path broadcasts directly via the AIBTC wallet manager
Invalid / missing on-chain proof TX ✅ Fixed — two successful mainnet TXs below

On-chain proof (Stacks mainnet)

Both transactions broadcast and confirmed from wallet SP301E0FY52B19281VCHP41SAKKZFR761BMKQH4QE with PostConditionMode.Deny.

Action TXID Explorer
deposit (0.5 STX → stSTX) 0xbfbfecd3f986eabf8d24bc3c3c128ddf11f2f74811e7b7532c0715cd5e424687 View
init-withdraw (burn stSTX → NFT ticket) 0x41c679115d75086835a8168c38cc8819d337376eef5b89b41eb6591cab832b55 View

Skill files

File Purpose
skills/ststx-liquid-stacker/SKILL.md Registry frontmatter + documentation
skills/ststx-liquid-stacker/AGENT.md Agent decision order + guardrails
skills/ststx-liquid-stacker/ststx-liquid-stacker.ts CLI implementation

Frontmatter validation

✅ ststx-liquid-stacker
   errorCount: 0, warningCount: 0

Single-skill verification

git diff --name-only origin/main...HEAD
# skills/ststx-liquid-stacker/AGENT.md
# skills/ststx-liquid-stacker/SKILL.md
# skills/ststx-liquid-stacker/ststx-liquid-stacker.ts

Write path architecture

  • callContract from @aibtc/mcp-server builds, signs, and broadcasts each transaction
  • awaitConfirmation polls Hiro API every 10 s, up to 30 attempts (5 min), before returning
  • PostConditionMode.Deny on every broadcast — unexpected token movement aborts on-chain
  • Pc.principal(wallet).willSendLte(...) for STX and FT post-conditions
  • Active StackingDAO protocol contracts: stacking-dao-core-v6, direct-helpers-v4, data-core-v3, ststx-withdraw-nft-v2

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 27, 2026

✅ Validation Passed

Skill: ststx-liquid-stacker
Errors: 0
Warnings: 0

All checks passed. This submission is ready for review.

@IamHarrie-Labs
Copy link
Copy Markdown
Contributor Author

@TheBigMacBTC — this is the single-skill resubmission of ststx-liquid-stacker from closed PR #496, split per your feedback. Also addresses the bundling and feedback items from closed PR #446.

Bundling: this PR now contains exactly one skill directory (skills/ststx-liquid-stacker/).

An on-chain proof TX with tx_status: success will be added to this PR before merge review.

Thank you for the detailed review on #496.

Serene Spring and others added 2 commits April 27, 2026 13:01
…tTransaction

Replace mcp_command plan emission with direct callContract + awaitConfirmation
via the AIBTC wallet manager and @stacks/transactions contractPrincipalCV args.
Every write action (deposit, init-withdraw, withdraw) now broadcasts on-chain
and polls for tx_status: success before returning. PostConditionMode.Deny is
enforced at the actual broadcast layer, not on an emitted plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ts (core-v6, direct-helpers-v4, data-core-v3)

- Upgrade DEFAULT_CORE from stacking-dao-core-v2 to stacking-dao-core-v6 (v2 is inactive in DAO protocol)
- Upgrade DEFAULT_DIRECT_HELPERS from direct-helpers-v1 to direct-helpers-v4 (v1-v3 all inactive)
- Add DEFAULT_DATA_CORE = data-core-v3 (used by core-v6 for get-stx-per-ststx)
- Add DEFAULT_WITHDRAW_NFT = ststx-withdraw-nft-v2 (core-v6 mints to v2 NFT contract)
- Rewrite getStxPerStstx() to call data-core-v3.get-stx-per-ststx(reserve) directly instead
  of deriving rate from reserve total-stx / ststx total-supply
- Fix serializeCV hex encoding: in @stacks/transactions v7 serializeCV returns a plain hex
  string (no 0x prefix), not a Uint8Array — prepend "0x" directly
- Add --data-core and --withdraw-nft override flags for future protocol-version rolls
- Fix getWithdrawalTickets() to query ststx-withdraw-nft-v2 asset identifier
- Fix getTicketCycle() to read from data-core-v1.get-withdrawals-by-nft (v6 stores ticket
  data in data-core-v1, not core contract)
- Fix withdraw post-condition NFT principal to use opts.withdrawNft (ststx-withdraw-nft-v2)

On-chain proof (Stacks mainnet, wallet SP301E0FY52B19281VCHP41SAKKZFR761BMKQH4QE):
  deposit    0xbfbfecd3f986eabf8d24bc3c3c128ddf11f2f74811e7b7532c0715cd5e424687
  init-withdraw 0x41c679115d75086835a8168c38cc8819d337376eef5b89b41eb6591cab832b55

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

@TheBigMacBTC @arc0btc — all three blockers from the original review have been addressed in this PR:

1. Multi-skill bundling — resolved. This PR contains only the ststx-liquid-stacker skill directory, nothing else.

2. Write-tag misrepresentation — resolved. The skill has been fully rebuilt to use callContract + awaitConfirmation from the AIBTC wallet manager. Every write path now builds, signs, and broadcasts directly on-chain — no more mcp_command plan output.

3. Missing / invalid on-chain proof TX — resolved. Two successful mainnet transactions confirmed with PostConditionMode.Deny:

The root cause of the earlier failed TX attempts was that StackingDAO had silently migrated to newer protocol contracts (, , ) — the old v2 core was marked inactive in the DAO registry, causing every broadcast to abort with . Those contracts are now correctly targeted. Happy to answer any questions!

Copy link
Copy Markdown

@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.

Thanks for the thorough resubmission and the clean split into a single skill. The write architecture is significantly better — callContract + awaitConfirmation + PostConditionMode.Deny is exactly the right shape. Two issues need to be addressed before this can merge.


[blocking] AGENT.md still references the mcp_command pattern

AGENT.md lines 50 and 70–71 still say:

Treat the emitted `mcp_command` as `post_condition_mode: "deny"` — any unexpected token flow aborts the transaction.

and

Capture the emitted `mcp_command` block and pass it to the AIBTC MCP wallet for signing + broadcast.
After broadcast, record the returned txid and persist it in the local spend ledger.

The actual implementation broadcasts directly via callContract — there is no mcp_command emitted. This is the exact blocker from the original review (write-tag misrepresentation). The implementation fixed it, but the AGENT.md still documents the old pattern. Any agent loading this skill will try to capture a mcp_command that never appears.

Fix: update the "Guardrails" and "On success" sections in AGENT.md to match the actual broadcast-and-await architecture.


[blocking] getTicketCycle parses unlock-burn-height but the result is compared as a PoX cycle ID

The function comment at line 507 says:

// Response is a tuple: { unlock-burn-height: uint, stx-amount: uint, ststx-amount: uint }
// We parse unlock-burn-height — the first uint in the tuple after the tuple header

The parsed value is then used in status as cycle_id and in withdraw as:

if (currentCycle <= ticketCycle) {
  blocked("ticket_not_matured", `Ticket #${nftId} matures in cycle ${ticketCycle}; current PoX cycle is ${currentCycle}`, ...);
}

Burn height and PoX cycle are different units. Current PoX cycle is ~around 100–120; current Stacks block height is ~900k+. If unlock-burn-height is truly a burn height (as the comment says), the comparison currentCycle <= ticketCycle will always pass (100 ≤ 900000), allowing withdrawals on unmatured tickets. Conversely, if the actual contract returns a cycle number, the comment is wrong and everything works — but the disconnect needs to be resolved explicitly.

The two on-chain proof TXs cover deposit and init-withdraw only; withdraw is the unverified path. Please either:

  1. Confirm the tuple field is actually a PoX cycle number (not burn height) and update the comment accordingly, or
  2. Switch to comparing burn heights throughout (use GET /v2/infostacks_tip_height instead of the PoX cycle).

[suggestion] encodeUintHex duplicates serializeCV(uintCV(value))

The file already imports uintCV and serializeCV. The hand-rolled 17-byte encoder at lines 390–398 is doing what "0x" + Buffer.from(serializeCV(uintCV(value))).toString("hex") already does via the SDK. Removing the duplicate reduces maintenance surface and eliminates a potential off-by-one class of bugs.


[suggestion] --data-core flag is not threaded into getTicketCycle

DEFAULT_DATA_CORE points to data-core-v3, but getTicketCycle is called with a hardcoded data-core-v1 at both call sites (lines 701 and 1174). If data-core-v1 is ever deprecated, ticket maturity reads will silently fail regardless of what the caller passes via --data-core. Either thread opts.dataCore through, or document clearly that ticket maturity always reads from v1.


[suggestion] ledger.totalUstxMoved mixes uSTX and stSTX micro-units

deposit adds amountUstx (uSTX) to totalUstxMoved. init-withdraw adds amountStstx (stSTX micro-units) to the same counter (line 1104). At current rates (~1.86 STX per stSTX), these are different magnitudes. A 1M stSTX init-withdraw would add 1M to a counter that was calibrated for uSTX, understating the actual daily volume significantly. The daily cap check would give false confidence. Suggest tracking deposit and withdrawal volume in separate ledger fields, or converting stSTX to approximate uSTX value before adding.


The two blocking items need fixes; the three suggestions are worth addressing but not merge-blockers. The overall architecture (direct broadcast, PostConditionMode.Deny, per-action confirm tokens, slippage gates, daily caps) is solid — just needs the docs reconciled and the burn-height/cycle-ID question answered.

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.

2 participants