diff --git a/.beads/.gitignore b/.beads/.gitignore new file mode 100644 index 0000000..c4f9d34 --- /dev/null +++ b/.beads/.gitignore @@ -0,0 +1,73 @@ +# Dolt database (managed by Dolt, not git) +dolt/ +dolt-access.lock + +# Runtime files +bd.sock +bd.sock.startlock +sync-state.json +last-touched +.exclusive-lock + +# Daemon runtime (lock, log, pid) +daemon.* + +# Interactions log (runtime, not versioned) +interactions.jsonl + +# Push state (runtime, per-machine) +push-state.json + +# Lock files (various runtime locks) +*.lock + +# Credential key (encryption key for federation peer auth β€” never commit) +.beads-credential-key + +# Local version tracking (prevents upgrade notification spam after git ops) +.local_version + +# Worktree redirect file (contains relative path to main repo's .beads/) +# Must not be committed as paths would be wrong in other clones +redirect + +# Sync state (local-only, per-machine) +# These files are machine-specific and should not be shared across clones +.sync.lock +export-state/ +export-state.json + +# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned) +ephemeral.sqlite3 +ephemeral.sqlite3-journal +ephemeral.sqlite3-wal +ephemeral.sqlite3-shm + +# Dolt server management (auto-started by bd) +dolt-server.pid +dolt-server.log +dolt-server.lock +dolt-server.port +dolt-server.activity + +# Corrupt backup directories (created by bd doctor --fix recovery) +*.corrupt.backup/ + +# Backup data (auto-exported JSONL, local-only) +backup/ + +# Per-project environment file (Dolt connection config, GH#2520) +.env + +# Legacy files (from pre-Dolt versions) +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm +db.sqlite +bd.db +# NOTE: Do NOT add negation patterns here. +# They would override fork protection in .git/info/exclude. +# Config files (metadata.json, config.yaml) are tracked by git by default +# since no pattern above ignores them. diff --git a/.beads/README.md b/.beads/README.md new file mode 100644 index 0000000..dbfe363 --- /dev/null +++ b/.beads/README.md @@ -0,0 +1,81 @@ +# Beads - AI-Native Issue Tracking + +Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. + +## What is Beads? + +Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. + +**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) + +## Quick Start + +### Essential Commands + +```bash +# Create new issues +bd create "Add user authentication" + +# View all issues +bd list + +# View issue details +bd show + +# Update issue status +bd update --claim +bd update --status done + +# Sync with Dolt remote +bd dolt push +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in Dolt database with version control and branching +- **AI-friendly**: CLI-first design works perfectly with AI coding agents +- **Branch-aware**: Issues can follow your branch workflow +- **Always in sync**: Auto-syncs with your commits + +## Why Beads? + +✨ **AI-Native Design** +- Built specifically for AI-assisted development workflows +- CLI-first interface works seamlessly with AI coding agents +- No context switching to web UIs + +πŸš€ **Developer Focused** +- Issues live in your repo, right next to your code +- Works offline, syncs when you push +- Fast, lightweight, and stays out of your way + +πŸ”§ **Git Integration** +- Automatic sync with git commits +- Branch-aware issue tracking +- Dolt-native three-way merge resolution + +## Get Started with Beads + +Try Beads in your own projects: + +```bash +# Install Beads +curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize in your repo +bd init + +# Create your first issue +bd create "Try out Beads" +``` + +## Learn More + +- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) +- **Quick Start Guide**: Run `bd quickstart` +- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) + +--- + +*Beads: Issue tracking that moves at the speed of thought* ⚑ diff --git a/.beads/config.yaml b/.beads/config.yaml new file mode 100644 index 0000000..232b151 --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,54 @@ +# Beads Configuration File +# This file configures default behavior for all bd commands in this repository +# All settings can also be set via environment variables (BD_* prefix) +# or overridden with command-line flags + +# Issue prefix for this repository (used by bd init) +# If not set, bd init will auto-detect from directory name +# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. +# issue-prefix: "" + +# Use no-db mode: JSONL-only, no Dolt database +# When true, bd will use .beads/issues.jsonl as the source of truth +# no-db: false + +# Enable JSON output by default +# json: false + +# Feedback title formatting for mutating commands (create/update/close/dep/edit) +# 0 = hide titles, N > 0 = truncate to N characters +# output: +# title-length: 255 + +# Default actor for audit trails (overridden by BEADS_ACTOR or --actor) +# actor: "" + +# Export events (audit trail) to .beads/events.jsonl on each flush/sync +# When enabled, new events are appended incrementally using a high-water mark. +# Use 'bd export --events' to trigger manually regardless of this setting. +# events-export: false + +# Multi-repo configuration (experimental - bd-307) +# Allows hydrating from multiple repositories and routing writes to the correct database +# repos: +# primary: "." # Primary repo (where this database lives) +# additional: # Additional repos to hydrate from (read-only) +# - ~/beads-planning # Personal planning repo +# - ~/work-planning # Work planning repo + +# JSONL backup (periodic export for off-machine recovery) +# Auto-enabled when a git remote exists. Override explicitly: +# backup: +# enabled: false # Disable auto-backup entirely +# interval: 15m # Minimum time between auto-exports +# git-push: false # Disable git push (export locally only) +# git-repo: "" # Separate git repo for backups (default: project repo) + +# Integration settings (access with 'bd config get/set') +# These are stored in the database, not in this file: +# - jira.url +# - jira.project +# - linear.url +# - linear.api-key +# - github.org +# - github.repo diff --git a/.beads/hooks/post-checkout b/.beads/hooks/post-checkout new file mode 100755 index 0000000..cacfeda --- /dev/null +++ b/.beads/hooks/post-checkout @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.63.3 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run post-checkout "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'post-checkout' timed out after ${_bd_timeout}s β€” continuing without beads" + _bd_exit=0 + fi + else + bd hooks run post-checkout "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized β€” skipping hook 'post-checkout'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v0.63.3 --- diff --git a/.beads/hooks/post-merge b/.beads/hooks/post-merge new file mode 100755 index 0000000..fd349bf --- /dev/null +++ b/.beads/hooks/post-merge @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.63.3 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run post-merge "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'post-merge' timed out after ${_bd_timeout}s β€” continuing without beads" + _bd_exit=0 + fi + else + bd hooks run post-merge "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized β€” skipping hook 'post-merge'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v0.63.3 --- diff --git a/.beads/hooks/pre-commit b/.beads/hooks/pre-commit new file mode 100755 index 0000000..5bee03c --- /dev/null +++ b/.beads/hooks/pre-commit @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.63.3 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run pre-commit "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'pre-commit' timed out after ${_bd_timeout}s β€” continuing without beads" + _bd_exit=0 + fi + else + bd hooks run pre-commit "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized β€” skipping hook 'pre-commit'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v0.63.3 --- diff --git a/.beads/hooks/pre-push b/.beads/hooks/pre-push new file mode 100755 index 0000000..6042948 --- /dev/null +++ b/.beads/hooks/pre-push @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.63.3 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run pre-push "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'pre-push' timed out after ${_bd_timeout}s β€” continuing without beads" + _bd_exit=0 + fi + else + bd hooks run pre-push "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized β€” skipping hook 'pre-push'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v0.63.3 --- diff --git a/.beads/hooks/prepare-commit-msg b/.beads/hooks/prepare-commit-msg new file mode 100755 index 0000000..ae93b95 --- /dev/null +++ b/.beads/hooks/prepare-commit-msg @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.63.3 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run prepare-commit-msg "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'prepare-commit-msg' timed out after ${_bd_timeout}s β€” continuing without beads" + _bd_exit=0 + fi + else + bd hooks run prepare-commit-msg "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized β€” skipping hook 'prepare-commit-msg'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v0.63.3 --- diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 0000000..d1e237a --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,7 @@ +{ + "database": "dolt", + "backend": "dolt", + "dolt_mode": "embedded", + "dolt_database": "Flashstack", + "project_id": "e447095d-7552-46f4-b146-acf955ee30d9" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 60008a8..83fc9c7 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,9 @@ settings/Testnet.toml secrets.json wallet.json mnemonics.txt +# External-deployer mainnet mnemonic files (24-word secrets) β€” NEVER commit +mbegu +mbegu2 # ============================================ # Build Artifacts @@ -224,3 +227,8 @@ web/tsconfig.tsbuildinfo contracts/snp-flashstack-receiver.clar contracts/snp-flashstack-receiver-v3.clar contracts/bitflow-arb-compounder.clar + +# Beads / Dolt files (added by bd init) +.dolt/ +*.db +.beads-credential-key diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2080244 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,84 @@ +# Agent Instructions + +This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started. + +## Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work atomically +bd close # Complete work +bd dolt push # Push beads data to remote +``` + +## Non-Interactive Shell Commands + +**ALWAYS use non-interactive flags** with file operations to avoid hanging on confirmation prompts. + +Shell commands like `cp`, `mv`, and `rm` may be aliased to include `-i` (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input. + +**Use these forms instead:** +```bash +# Force overwrite without prompting +cp -f source dest # NOT: cp source dest +mv -f source dest # NOT: mv source dest +rm -f file # NOT: rm file + +# For recursive operations +rm -rf directory # NOT: rm -r directory +cp -rf source dest # NOT: cp -r source dest +``` + +**Other commands that may prompt:** +- `scp` - use `-o BatchMode=yes` for non-interactive +- `ssh` - use `-o BatchMode=yes` to fail instead of prompting +- `apt-get` - use `-y` flag +- `brew` - use `HOMEBREW_NO_AUTO_UPDATE=1` env var + + +## Beads Issue Tracker + +This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands. + +### Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work +bd close # Complete work +``` + +### Rules + +- Use `bd` for ALL task tracking β€” do NOT use TodoWrite, TaskCreate, or markdown TODO lists +- Run `bd prime` for detailed command reference and session close protocol +- Use `bd remember` for persistent knowledge β€” do NOT use MEMORY.md files + +## Session Completion + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + bd dolt push + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds + diff --git a/contracts/hk-stx-bitflow-receiver-v1.clar b/contracts/hk-stx-bitflow-receiver-v1.clar new file mode 100644 index 0000000..df2380f --- /dev/null +++ b/contracts/hk-stx-bitflow-receiver-v1.clar @@ -0,0 +1,176 @@ +;; HK STX Bitflow Receiver v1 +;; +;; External-developer flash-loan receiver that executes a REAL DEX round-trip: +;; borrow STX from flashstack-stx-core -> swap STX->stSTX on Bitflow -> +;; swap stSTX->STX back -> repay principal + fee, atomically. +;; +;; Deployed under an EXTERNAL wallet (not the protocol deployer), so every +;; cross-contract reference uses the ABSOLUTE mainnet principal. The `.flashstack-stx-core` +;; sugar used by the in-repo bitflow-arb-receiver would resolve to THIS deployer's +;; address and break for an external deploy. +;; +;; Combines: +;; - hk-stx-real-receiver-v2 : contract-caller gate + absolute principals +;; - bitflow-arb-receiver-v4 : the STX/stSTX Bitflow round-trip +;; +;; Objective is NOT profit. It is: external strategy execution + successful flash +;; loan + real DEX interaction + successful repayment. The repayment assert and the +;; core's own reserve check are the safety gates; min-out=u1 lets the swaps clear. +;; +;; Live contracts used: +;; Core: SP20XD46NGAX05ZQZDKFYCCX49A3852BQABNP0VG5.flashstack-stx-core +;; Bitflow pool: SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M.stableswap-stx-ststx-v-1-2 +;; stSTX token: SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.ststx-token +;; Bitflow LP: SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M.stx-ststx-lp-token-v-1-2 +;; +;; Clarity version: 3 (epoch 3.0 / Nakamoto) + +(impl-trait 'SP3TGRVG7DKGFVRTTVGGS60S59R916FWB4DAB9STZ.stx-flash-receiver-trait.stx-flash-receiver-trait) + +;; Minimal SIP-010 trait for calling the stSTX token +(define-trait sip-010-trait + ( + (transfer (uint principal principal (optional (buff 34))) (response bool uint)) + (get-name () (response (string-ascii 32) uint)) + (get-symbol () (response (string-ascii 32) uint)) + (get-decimals () (response uint uint)) + (get-balance (principal) (response uint uint)) + (get-total-supply () (response uint uint)) + (get-token-uri () (response (optional (string-utf8 256)) uint)) + ) +) + +;; ============================================= +;; Constants +;; ============================================= + +(define-constant CONTRACT-OWNER tx-sender) + +;; The ONLY legitimate caller of execute-stx-flash. Hard-coded (Form A gate). +(define-constant FLASHSTACK-STX-CORE 'SP20XD46NGAX05ZQZDKFYCCX49A3852BQABNP0VG5.flashstack-stx-core) + +(define-constant ERR-NOT-OWNER (err u400)) +(define-constant ERR-SWAP-FAILED (err u401)) +(define-constant ERR-WRONG-CALLER (err u403)) +(define-constant ERR-REPAY-FAILED (err u500)) + +;; Bitflow STX/stSTX stableswap pool +(define-constant BITFLOW-POOL 'SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M.stableswap-stx-ststx-v-1-2) + +;; stSTX SIP-010 token (y-token in the pool) +(define-constant STSTX 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.ststx-token) + +;; Bitflow STX/stSTX LP token (lp-token parameter) +(define-constant BITFLOW-LP 'SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M.stx-ststx-lp-token-v-1-2) + +;; Slippage tolerance in basis points (default 300 = 3%). Retained as an ops knob; +;; the live legs use min-out=u1 and rely on the repayment assert as the safety gate. +(define-data-var slippage-bp uint u300) + +;; ============================================= +;; Flash Loan Callback +;; ============================================= + +(define-public (execute-stx-flash (amount uint) (core principal)) + (begin + ;; Gate: only the live flashstack-stx-core may invoke this callback. + ;; Closes the direct-drain path a public execute-stx-flash would otherwise expose. + (asserts! (is-eq contract-caller FLASHSTACK-STX-CORE) ERR-WRONG-CALLER) + (let ( + ;; Repayment math - look up the fee dynamically (never hard-code u5). + (fee-bp (unwrap! (contract-call? FLASHSTACK-STX-CORE get-fee-basis-points) ERR-SWAP-FAILED)) + (raw-fee (/ (* amount fee-bp) u10000)) + (fee (if (> raw-fee u0) raw-fee u1)) + (total-owed (+ amount fee)) + + ;; min-out=u1 on both legs - the swap always clears; the repay assert is the gate. + (min-ststx u1) + (min-stx u1) + ) + ;; Leg 1: STX -> stSTX on Bitflow. + ;; as-contract: the borrowed STX sits in THIS contract's balance. + (unwrap! (as-contract (contract-call? BITFLOW-POOL swap-x-for-y + STSTX ;; y-token (stSTX SIP-010) + BITFLOW-LP ;; lp-token + amount ;; STX in (microSTX) + min-ststx ;; min stSTX out + )) ERR-SWAP-FAILED) + + ;; How much stSTX did we receive? + (let ( + (ststx-balance (unwrap! + (contract-call? STSTX get-balance (as-contract tx-sender)) + ERR-SWAP-FAILED)) + ) + (asserts! (> ststx-balance u0) ERR-SWAP-FAILED) + + ;; Leg 2: stSTX -> STX on Bitflow. + (unwrap! (as-contract (contract-call? BITFLOW-POOL swap-y-for-x + STSTX + BITFLOW-LP + ststx-balance ;; all stSTX we hold + min-stx ;; min STX back + )) ERR-SWAP-FAILED) + + ;; Repay STX + fee back to the core. Fail closed if the round-trip came up short. + (let ((stx-now (stx-get-balance (as-contract tx-sender)))) + (asserts! (>= stx-now total-owed) ERR-REPAY-FAILED) + (unwrap! (as-contract (stx-transfer? total-owed tx-sender core)) ERR-REPAY-FAILED) + (print { event: "bitflow-roundtrip", amount: amount, fee: fee, + ststx-mid: ststx-balance, stx-after: stx-now }) + (ok true) + ) + ) + ) + ) +) + +;; ============================================= +;; Admin +;; ============================================= + +(define-public (set-slippage-bp (new-bp uint)) + (begin + (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER) + (asserts! (<= new-bp u500) ERR-SWAP-FAILED) ;; max 5% + (ok (var-set slippage-bp new-bp)) + ) +) + +;; Rescue stuck STX (owner only) - escape hatch if a partial round-trip strands STX. +(define-public (rescue-stx (amount uint) (to principal)) + (begin + (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER) + (unwrap! (as-contract (stx-transfer? amount tx-sender to)) ERR-NOT-OWNER) + (ok true) + ) +) + +;; ============================================= +;; Read-only +;; ============================================= + +(define-read-only (get-slippage-bp) + (ok (var-get slippage-bp)) +) + +(define-read-only (get-owner) + (ok CONTRACT-OWNER) +) + +(define-read-only (estimate-repayment (amount uint)) + (let ( + ;; Literal principal (not the constant) so the analyzer can prove this + ;; cross-contract call is read-only inside a define-read-only function. + (fee-bp (unwrap-panic (contract-call? 'SP20XD46NGAX05ZQZDKFYCCX49A3852BQABNP0VG5.flashstack-stx-core get-fee-basis-points))) + (raw-fee (/ (* amount fee-bp) u10000)) + (fee (if (> raw-fee u0) raw-fee u1)) + ) + (ok { + loan-amount: amount, + fee-to-pay: fee, + total-owed: (+ amount fee), + note: "Round-trip must return >= total-owed or the tx reverts. Seed covers any shortfall." + }) + ) +) diff --git a/docs-feedback-report.md b/docs-feedback-report.md new file mode 100644 index 0000000..b37ddf7 --- /dev/null +++ b/docs-feedback-report.md @@ -0,0 +1,135 @@ +# FlashStack β€” Integration Documentation Review + +**Author:** External integrator (HK), having completed a full end-to-end FlashStack integration on mainnet +**Date:** 2026-06-06 +**Basis:** Milestone 1 β€” designed, deployed, whitelisted, seeded, and executed `hk-stx-bitflow-receiver-v1` (a real STXβ†’stSTXβ†’STX Bitflow round-trip) on Stacks mainnet. Flash-loan tx `865df576…639737` β†’ `(ok true)`. +**Method:** Every claim below was verified against the contract source in `contracts/` and/or live mainnet reads. File and line citations are given so each can be checked independently. + +> **Bottom line.** The documentation set is genuinely good β€” among the better protocol docs on Stacks β€” and it got us to a successful mainnet integration. `ONBOARDING.md` in particular is excellent. But a developer who follows only the two "start here" docs (`INTEGRATION_GUIDE.md`, `BUILD_A_RECEIVER.md`) will hit three avoidable walls (receiver funding, external-principal rule, whitelist channel) and will copy at least one snippet that does not compile or does not return what the doc claims. There is also a cluster of stale/contradictory numeric facts (a 100Γ— wrong max-loan, an impl-trait that points at the wrong contract, troubleshooting error codes that don't match the source). None of these are hard to fix and most are one-line edits. + +--- + +## What Worked Well + +These are the things that materially helped and were **correct** when checked against source: + +1. **The core mental model is communicated clearly and repeatedly.** The "borrow β†’ strategy β†’ repay principal+fee β†’ reserve-grew-by-fee-or-revert" loop appears as an ASCII diagram in `INTEGRATION_GUIDE.md:49`, `BUILD_A_RECEIVER.md:13`, and `ONBOARDING.md:42`. This is the single most important thing to understand and the docs nail it. Verified against `flashstack-stx-core.clar:140-179`. + +2. **`ONBOARDING.md` is the standout document.** It is realistic and battle-tested: + - It correctly explains **seed-before-loan** and *why* (`ONBOARDING.md:63`) β€” the one fact that unblocked us. + - It gives honest mainnet cost floors (`ONBOARDING.md:108`). + - The troubleshooting section anticipates real failures (the `mbegu2` filename gotcha `:133/:425`, `ConflictingNonceInMempool` `:410`, the `bd init` git-push failure `:413`) β€” we hit several of these verbatim. + - It distinguishes Scenario A (deploy your receiver on the live protocol) from Scenario B (stand up the whole stack on testnet) β€” exactly the right framing. + +3. **The three hard Clarity rules are stated and they are the right rules.** Dynamic fee lookup (never hard-code `u5`), `as-contract` on repayment, and literal principals for trait arguments appear in `BUILD_A_RECEIVER.md:85-90` and `INTEGRATION_GUIDE.md:111-114`. All three are real and all three bit us during development β€” the docs were correct to emphasize them. Verified against `flashstack-stx-core.clar:201-203` (dynamic fee) and our own deploy experience. + +4. **Minimum-fee handling is documented and matches source.** `(if (> raw-fee u0) raw-fee u1)` for tiny loans (`BUILD_A_RECEIVER.md:90`) matches `flashstack-stx-core.clar:147`. + +5. **`API_REFERENCE.md` correctly captures the STX-vs-sBTC error-code divergence** (`:294-303`) β€” it is the only doc that does. It also correctly documents `is-approved-receiver` as returning bare `bool` for the STX core (`:219`). + +6. **Confirmed mainnet transactions are cited and they verify.** The arb tx and the sBTC tx (`API_REFERENCE.md:389-391`, `README.md:84-86`) are real β€” we independently confirmed the sBTC evidence tx `0x67f0c77d…` is `success / (ok true)`, block 7875468. Citing live, checkable evidence is exactly right for grant reviewers. + +7. **Worked use cases are concrete** (arb, flash liquidation, collateral swap, yield vault β€” `INTEGRATION_GUIDE.md:223-308`), each with a code pattern and a link to a real contract. + +--- + +## Missing Information + +Things that were **required during our integration but are absent from the primary docs** (`INTEGRATION_GUIDE.md` / `BUILD_A_RECEIVER.md`): + +### M1 β€” Receiver funding ("seed-before-loan") is missing from the two start-here docs +This was our single biggest non-obvious blocker. The receiver pays the fee β€” and absorbs any DEX slippage β€” **from its own balance** inside `execute-stx-flash`, *before* returning (`flashstack-stx-core.clar:166-167` checks `reserve-after β‰₯ reserve-before + fee`). A freshly deployed 0-balance receiver therefore cannot borrow even 1 Β΅STX. + +- It is correctly explained in `ONBOARDING.md:63` and, for sBTC, `TESTING_GUIDE_SBTC.md:176`. +- It is **absent** from `INTEGRATION_GUIDE.md` and `BUILD_A_RECEIVER.md`, both of which instead lead with "**Zero capital required**" / "**You do not need capital**" (`INTEGRATION_GUIDE.md:13`, `BUILD_A_RECEIVER.md:25`). +- That headline is true only for a **strictly net-profitable** strategy. For an execution/validation or break-even receiver (like ours), the round-trip returns slightly less than principal, so the seed is mandatory. We seeded 1 STX; the round-trip cost ~0.0015 STX + the 0.0005 STX fee. + +**Fix:** add a "Funding your receiver" box to both primary docs; reframe "zero capital" as "**zero collateral**" (accurate) and add "your receiver must hold enough of the borrowed asset to cover the fee plus any slippage your strategy incurs." + +### M2 β€” The external-deploy absolute-principal rule is not explained +The templates use absolute principals, but never say *why*, and all three docs point developers to **read `bitflow-arb-receiver.clar` as the model** (`INTEGRATION_GUIDE.md:233`, `BUILD_A_RECEIVER.md:135`, `ONBOARDING.md:438`). That in-repo contract uses `.flashstack-stx-core` **sugar**, which resolves to *the deployer's own address*. It works only because it is deployed under the protocol owner. An external developer who copies it verbatim gets a receiver that calls a non-existent contract under their own address. We had to rewrite every reference to the absolute `SP20XD46….flashstack-stx-core` (our ADR-001). + +**Fix:** a short "External (non-deployer) receivers" section stating the rule, plus an external-ready copy of the example that uses absolute principals. + +### M3 β€” sBTC reserve size is not stated, and it is the real cap +Docs advertise an sBTC max-single-loan of 0.1 BTC (`TESTING_GUIDE_SBTC.md:87`). But the borrow guard requires `reserve β‰₯ amount` (`flashstack-sbtc-core.clar:68`), and the **live sBTC reserve is only 15,010 sats** (~0.00015 BTC; verified via `get-reserve-balance`). So an sBTC borrower today can draw at most ~15k sats regardless of the 0.1 BTC cap. (The STX reserve is healthier at ~75 STX.) Borrowers need to know to check `get-reserve-balance` first β€” and that it can be far below the advertised max. + +### M4 β€” No documented read-only pre-flight / estimator pattern +`BUILD_A_RECEIVER.md:211` and `TESTING_GUIDE_STX.md:151` reference a `simulate` / `estimate-profit` read-only, but no doc shows how to write one, and the **literal-principal-in-`define-read-only`** gotcha is undocumented. We hit it as a real pre-broadcast bug: a `define-constant` core principal is rejected inside a read-only function because Clarity can't prove the cross-contract call is read-only; the fix is to inline the literal principal. A "pre-flight" recipe (estimate repayment + quote the DEX round-trip via Bitflow `get-dy`/`get-dx`) would save every integrator this round. + +### M5 β€” The Clarinet coverage gap is not stated +`README.md:130` advertises `npm run check` ("Clarinet contract verification"), but the STX/Bitflow suite is **not in the Clarinet project** β€” it is script-deployed via `makeContractDeploy`, which does not type-check Clarity. So `clarinet check` silently does **not** validate a new STX receiver, and a simnet remap of hard-coded mainnet principals fails with `NoSuchContract`. We had to validate in a separate stub Clarinet project. The docs should state what `clarinet check` does and does not cover. + +--- + +## Confusing Areas + +Places where wording is ambiguous, contradictory, or requires reading source to resolve: + +1. **"Zero capital required" vs. "pre-fund your receiver."** Until you understand the fee-source model (M1), `INTEGRATION_GUIDE.md:13` ("you do not need capital") reads as a direct contradiction of `TESTING_GUIDE_SBTC.md:176` ("Pre-fund your receiver"). Both are "true," but only with the nuance spelled out. + +2. **"Same interface" for the sBTC core is misleading** (`API_REFERENCE.md:307`). The sBTC core is **not** the same interface: + - `get-stats` returns **4 fields** (`total-loans, total-volume, total-fees-collected, paused` β€” `flashstack-sbtc-core.clar:182-189`) vs the STX core's **7** (adds `reserve, fee-basis-points, max-single-loan` β€” `flashstack-stx-core.clar:189-199`). Different field name too (`total-fees-collected` vs `total-fees`). + - `is-approved-receiver` returns `(ok bool)` (`:191-193`) vs the STX core's bare `bool` (`flashstack-stx-core.clar:209-211`). + - `withdraw-reserve` takes `(amount, to)` (`:108`) vs the STX core's `(amount)` (`flashstack-stx-core.clar:66`). + - Admin transfer is single-step `set-admin` (`:155`) vs the STX core's two-step `transfer-admin` + `accept-admin` (`flashstack-stx-core.clar:114-130`). (`API_REFERENCE.md` documents neither `accept-admin` nor `get-pending-admin`.) + +3. **Error-code numbers are reused across contracts.** `u300` is `ERR-NOT-ADMIN` on the STX core (`flashstack-stx-core.clar:25`) but `ERR-PAUSED` on the sBTC core (`flashstack-sbtc-core.clar:24`); the pools use `u700+` (`flashstack-sbtc-pool.clar:30`). The same number means different things on different contracts β€” easy to misread when debugging. + +4. **Two deployer wallets.** The STX trait lives under the **legacy** `SP3TGRVG7…` deployer while everything else is under `SP20XD46…`. `ONBOARDING.md:81` calls this out; the other docs just print the address without explanation, so it looks like a typo until you check. + +--- + +## Confirmed Factual Errors (verifiable) + +Each of these was checked against source or chain. These should be corrected directly. + +| # | Where | Claim | Reality (source/chain) | +|---|---|---|---| +| E1 | `INTEGRATION_GUIDE.md:71,383`; `API_REFERENCE.md:137`; `TESTING_GUIDE_STX.md:102,171` | max-single-loan = **5,000 STX** (variously `u5000000000` or "500,000,000,000 microSTX = 5,000 STX") | Live `get-stats` β†’ `max-single-loan u500000000000` = **500,000 STX**. The literal in `flashstack-stx-core.clar:44` is `u500000000000` and even its inline comment ("5,000 STX") is wrong. Docs are **100Γ— off**, and `TESTING_GUIDE_STX`'s `u5000000000` is a different (also wrong) value. | +| E2 | `README.md:174` | STX receiver `impl-trait 'SP20XD46….flashstack-stx-core.stx-flash-receiver-trait` | Trait is at `SP3TGRVG7DKGFVRTTVGGS60S59R916FWB4DAB9STZ.stx-flash-receiver-trait.stx-flash-receiver-trait` (`flashstack-stx-core.clar:17`, `API_REFERENCE.md:315`, our deployed receiver). The README snippet **will not deploy**. | +| E3 | `ONBOARDING.md:401,404,407` | `(err u3)` NOT-APPROVED, `(err u4)` EXCEEDS-LIMIT, `(err u6)` INSUFFICIENT-RESERVE | Source: `u306`, `u304`, `u303` (`flashstack-stx-core.clar:31,29,28`). Off by the `u300` base. | +| E4 | `TESTING_GUIDE_STX.md:77-79` | `get-stats` has `total-flash-mints`, `total-fees-collected` | STX `get-stats` returns `total-loans, total-volume, total-fees` (`flashstack-stx-core.clar:189-199`). `total-flash-mints` exists nowhere; `total-fees-collected` is the **sBTC** field. | +| E5 | `TESTING_GUIDE_STX.md:16-18,184` | The sBTC path is "legacy β€” SP3TGRVG7… wallet… do not test" | `flashstack-sbtc-core` (under `SP20XD46…`) is **live**: reserve 15,010 sats, `total-loans u2`, fee 5 bps, not paused; evidence tx `0x67f0c77d…` `(ok true)` block 7875468. Contradicts `README.md:46`, `INTEGRATION_GUIDE.md:39`, `ONBOARDING.md:74`, `TESTING_GUIDE_SBTC.md:16`. | +| E6 | `TESTING_GUIDE_STX.md:104-105` | STX `is-approved-receiver` β†’ `(ok true)` | STX core returns bare `bool` `true` (`flashstack-stx-core.clar:209-211`). The `(ok …)` form is the **sBTC** core's. | +| E7 | `INTEGRATION_GUIDE.md:269` | `(max (/ (* amount fee-bp) u10000) u1)` | Clarity has no `max` builtin; this snippet does not compile. Use the `(if (> raw-fee u0) raw-fee u1)` form used everywhere else. | +| E8 | `INTEGRATION_GUIDE.md:145` | Whitelist check: `"0x$(printf 'YOUR-ADDRESS.my-receiver' | xxd -p)"` | This hex-encodes the **ASCII string**, not a Clarity-serialized principal. The read-only `is-approved-receiver` needs a serialized `contract-principal` clarity value (`0x06…`), so this curl cannot return `true`. (Generate it with `Cl.serialize(Cl.contractPrincipal(addr,name))`.) | +| E9 | `README.md:6,257` vs `:129,282,305`; `TESTING_GUIDE_STX.md:190` vs `ONBOARDING.md:176` | Test count is **82** in some places, **86** in others | Pick one; it appears inconsistently within `README.md` alone. | + +--- + +## Recommended Improvements + +**Priority 1 β€” correct the confirmed errors (E1–E9).** Mostly one-line edits. E1, E2, E3, E8 are the highest-impact because they actively mislead or break copy-paste. + +**Priority 2 β€” close the three integration walls (M1–M2 + whitelist):** +- Add a **"Funding your receiver"** callout to `INTEGRATION_GUIDE.md` and `BUILD_A_RECEIVER.md`: the fee-source model, when a seed is required (any non-strictly-profitable strategy), how to size it (fee + worst-case slippage), and how to recover it (`rescue-stx`). Reframe "zero capital" β†’ "zero collateral." +- Add an **"External (non-deployer) receivers"** section: the absolute-principal rule, why `.sugar` resolves to the deployer, and an external-ready copy of the arb example. +- Establish **one canonical whitelist channel** with the read-only verification snippet (using a correctly serialized principal β€” see E8) and one realistic SLA. Today the channel is given four different ways (GitHub issue / DM @flashstackbtc / "DM the txid" / "Slack or email Matt") across `INTEGRATION_GUIDE.md:137`, `BUILD_A_RECEIVER.md:205`, `ONBOARDING.md:250`, `TESTING_GUIDE_STX.md:198`. + +**Priority 3 β€” add two reference aids:** +- A **per-contract reference card** (one table: contract β†’ error-code base β†’ `get-stats` shape β†’ `is-approved-receiver` return type β†’ admin model) to kill the "same interface" confusion (Confusing #2/#3). +- A **read-only pre-flight recipe** (M4): an `estimate-repayment`-style function, the literal-principal-in-read-only rule, and a Bitflow `get-dy`/`get-dx` round-trip quote. + +**Priority 4 β€” set expectations on tooling and reserves (M5, M3):** +- State exactly what `npm run check` / Clarinet covers (the legacy sBTC simnet set) and that new STX receivers must be validated via a stub project or testnet (Scenario B). +- Surface the **live reserve** (or a dashboard link) so borrowers size loans to `get-reserve-balance`, not the advertised max. + +**Suggested diagrams:** +- A **fee-source / seed lifecycle** diagram (where the fee comes from, why a 0-balance receiver reverts). +- A **principal-resolution** diagram contrasting `.sugar` (resolves to *your* address) vs absolute principal for external vs protocol-owned receivers. + +--- + +## Appendix β€” verification + +All checks run 2026-06-06 against `https://api.hiro.so` (mainnet) and the `contracts/` source in this repo. + +- max-single-loan: `get-stats` on `flashstack-stx-core` β†’ `max-single-loan u500000000000` (= 500,000 STX). Source literal `flashstack-stx-core.clar:44`. +- STX trait location: `flashstack-stx-core.clar:17` `(use-trait … 'SP3TGRVG7…stx-flash-receiver-trait …)`. +- Error codes: `flashstack-stx-core.clar:25-34`; `flashstack-sbtc-core.clar:24-31`. +- `get-stats` shapes: `flashstack-stx-core.clar:189-199` (7 fields) vs `flashstack-sbtc-core.clar:182-189` (4 fields). +- sBTC live state: `flashstack-sbtc-core.get-reserve-balance` β†’ `(ok u15010)`; `get-stats` β†’ `total-loans u2`; evidence tx `0x67f0c77d9d7ab9762c08a3638ba0990d5bbc3d19db8adc1a0d616cd7170f9baa` β†’ `success / (ok true)` / block 7875468. +- Our Milestone 1 flash loan (reference integration): `865df57633fd111c76df3db5caa73577093e91e967af901a89db8de9cf639737` β†’ `(ok true)`, block 8197535. + +*This review is written from the perspective of an external developer who completed the integration successfully β€” the protocol works and the docs are good; these notes are about making the next integrator's path as clean as ours eventually became.* diff --git a/project-docs/session-handoffs/FLASHSTACK_PROJECT_HANDOFF_2026-06-06.md b/project-docs/session-handoffs/FLASHSTACK_PROJECT_HANDOFF_2026-06-06.md new file mode 100644 index 0000000..45429c1 --- /dev/null +++ b/project-docs/session-handoffs/FLASHSTACK_PROJECT_HANDOFF_2026-06-06.md @@ -0,0 +1,429 @@ +--- +title: FlashStack Project Handoff +date: 2026-06-06 +status: active +milestone: 1 +type: handoff +entrypoint: true +--- + +# FLASHSTACK PROJECT HANDOFF β€” 2026-06-06 + +> **This is the primary project entry point.** A new session should read [[#19 START HERE NEXT SESSION]] first, then [[#20 RECOVERY PROMPT FOR NEXT CLAUDE SESSION]]. +> Related: [[00-Index]] Β· [[FlashStack-Architecture]] Β· [[hk-stx-bitflow-receiver-v1-Design]] Β· [[Bitflow-Roundtrip-Analysis]] Β· [[Phase6-Validation]] Β· [[Phase7-Deliverables]] Β· [[Blockers]] Β· [[ADR]] + +--- + +## 1. Executive Summary + +**Technical.** FlashStack is an atomic flash-loan protocol on Stacks (a Bitcoin L2, Clarity 3 / Nakamoto). It runs an *admin-reserve + LP-pool* model (like Aave): `flashstack-stx-core` holds an STX reserve, lends it uncollateralised to a whitelisted *receiver* contract within a single transaction, and asserts the reserve grew by β‰₯ the fee (0.05%) before returning β€” otherwise the whole transaction reverts. A sibling sBTC stack (`flashstack-sbtc-core`) does the same for canonical sBTC. + +**Non-technical.** FlashStack lets a developer borrow a large amount of STX for a few seconds with no collateral, *provided* they pay it back (plus a tiny fee) inside the same transaction. If they can't, it's as if the loan never happened. This is useful for arbitrage, liquidations, and collateral swaps. + +**This engagement.** We are an **external integrator** validating that a third-party developer can build and run a real strategy on the live protocol. The larger goal is to prove FlashStack's external-developer path end-to-end and give Matt (the protocol owner) credible, on-chain evidence plus integration feedback. + +**Why the receiver was built / why it matters.** Matt asked for a receiver that actually *does something* against a real DEX β€” not a do-nothing borrow/repay. We built `hk-stx-bitflow-receiver-v1`, which borrows STX, performs a real **STX β†’ stSTX β†’ STX** round-trip on the **Bitflow** stableswap, and repays atomically. It proves three things at once: (1) the whitelist + callback model works for an external wallet, (2) a receiver can interact with an unrelated live DEX inside the loan, and (3) the atomic repayment guarantee holds against real market mechanics. Objective is **successful execution + repayment, not profit.** + +--- + +## 2. Current Project Status + +**Overall completion: 100% of Milestone 1 β€” COMPLETE (2026-06-06).** Receiver designed, built, validated, deployed, whitelisted, seeded, executed, and proven on mainnet. Full proof: [[Milestone-1-Execution-Evidence]]. + +| Item | State | +|---|---| +| Active milestone | **Milestone 1 β€” External Strategy Receiver vs real DEX (Bitflow)** | +| Receiver designed | βœ… | +| Receiver implemented | βœ… `contracts/hk-stx-bitflow-receiver-v1.clar` | +| `clarinet check` | βœ… 0 errors / 0 warnings (stub harness) | +| Deploy dry-run | βœ… | +| Mainnet preflight | βœ… core healthy, reserve 75 STX | +| **Deployed to mainnet** | βœ… tx `7e5b3ec5…dea567`, block 8197121 | +| Branch pushed to origin | βœ… `feature/hk-stx-bitflow-receiver-v1` | +| **Whitelist by Matt** | βœ… tx `7ae15a50…370b`, block 8197338 (`is-approved-receiver β†’ true`) | +| Seed receiver (1 STX) | βœ… tx `732374df…732b4c`, block 8197525 | +| Execute `flash-loan(u1000000)` | βœ… **`(ok true)`** tx `865df576…639737`, block 8197535 | +| Collect on-chain proof | βœ… [[Milestone-1-Execution-Evidence]] | +| Seed recovered (`rescue-stx`) | βœ… tx `f605a7bf…743d`, block 8197553 | +| Milestone 1 closed | βœ… | + +**Blockers:** none β€” all resolved. +**Result:** flash loan executed `(ok true)`; Bitflow round-trip cleared (861,402 Β΅stSTX mid); reserve +500 Β΅STX (fee); total-loans 8β†’9; total wallet spend 0.3215 STX; seed fully recovered. Our wallet `SP3NZYZA88…` now ~36.74 STX. + +--- + +## 3. Original Requests From Matt + +| # | Request | Purpose | Value to FlashStack | Status | +|---|---|---|---|---| +| 1 | **External strategy receiver against a real DEX** | Prove an outside dev can run a non-trivial strategy on the live protocol | Real external validation; reusable reference for future integrators | **In progress β€” deployed, awaiting whitelist** (Milestone 1) | +| 2 | **Test integration against `flashstack-sbtc-core`** | Exercise the canonical-sBTC flash-loan path the same way | Validates the sBTC engine externally | **Not started** (deferred until M1 done) β†’ future `hk-sbtc-real-receiver-v1` | +| 3 | **Feedback on integration documentation** | Surface gaps an external dev actually hits | Improves onboarding/DX; reduces support load | **Not started** (deferred) β€” material already accumulating (see Β§18) | + +Per the engagement rules, only **one milestone at a time**; #2 and #3 do not start until Milestone 1 is complete. + +--- + +## 4. Milestone 1 Summary + +**Goal.** Deliver an external-wallet receiver that interacts with a real DEX through FlashStack and repays atomically. Success = execution + flash loan + DEX interaction + repayment + on-chain evidence + documentation. **Not** profit. + +**Scope.** One new receiver contract + deploy script + executor script + full knowledge base. **No protocol contracts modified.** + +**Architecture.** `hk-stx-bitflow-receiver-v1` = **hk-stx-real-receiver-v2** (contract-caller gate + absolute mainnet principals, required for an external deploy) **βŠ• bitflow-arb-receiver-v4** (the STX/stSTX Bitflow round-trip). See Β§7 and [[hk-stx-bitflow-receiver-v1-Design]]. + +**Design decisions (full set in [[ADR]]):** +- **ADR-001** Absolute mainnet principals (the `.flashstack-stx-core` sugar would resolve to *our* address). +- **ADR-002** Caller gate Form A (hard-coded core principal) over Form B (trust passed arg). +- **ADR-003** `min-out = u1` on both legs; the repay-assert + core reserve check are the real safety gates. +- **ADR-004** Knowledge base lives outside the repo (clean deliverable diff). + +**Validation.** clarinet check 0/0, deploy dry-run, mainnet preflight, live 1-STX round-trip quote. One real bug found+fixed (read-only literal principal). See Β§9 and [[Phase6-Validation]]. + +**Deliverables.** Branch, 3 files (+468), architecture summary, risk assessment, test evidence β€” see Β§6 and [[Phase7-Deliverables]]. + +--- + +## 5. Repository Work Performed + +- **Synchronization:** local `main` had diverged (1 local-only `bd init` commit; origin had advanced 4). Rebased the local commit onto `origin/main`, pulling 4 upstream commits (yield-vault-v5, Nova audit fixes, Zest V2 liquidation receiver + scanner, monitor repoint). +- **Branches reviewed:** `main`, `feature/hk-real-receiver` (v1), `feature/hk-real-receiver-v2` (v2 caller-gate), `fix/deploy-testnet-seed-receiver`. All feature branches in sync with origin. +- **Contract mapping:** STX suite (`flashstack-stx-core`, `flashstack-stx-pool`, `flashstack-pool-oracle`, `stx-flash-receiver-trait`) + sBTC suite + many receivers. Full map in [[FlashStack-Architecture]]. +- **Receiver mapping / comparison:** `stx-test-receiver` (do-nothing), `hk-stx-real-receiver-v2` (caller-gate template), `bitflow-arb-receiver-v4` (live DEX arb). See [[Receiver-Comparison]]. +- **Dependency mapping:** core ← trait (`SP3TGRVG7…`); receiver β†’ core + Bitflow pool + stSTX + LP. Bitflow round-trip in [[Bitflow-Roundtrip-Analysis]]. + +**Important findings:** +1. **`Clarinet.toml` only registers the OLD 11-contract sBTC suite** β€” the entire STX/Bitflow suite is *not* in the Clarinet project and is deployed directly via `scripts/*.mjs` (`makeContractDeploy`), which does **not** type-check Clarity. So the STX suite has never been `clarinet check`ed in-repo. +2. **External-deploy principal rule** β€” the in-repo `bitflow-arb-receiver` uses `.flashstack-stx-core` sugar (works only because it's deployed under the protocol deployer). An external receiver must use the absolute `SP20XD46….flashstack-stx-core`. +3. **`./mbegu` is a plaintext 24-word mainnet seed phrase** at repo root β€” was not gitignored (now fixed). + +--- + +## 6. Deliverables Produced + +**Branch:** `feature/hk-stx-bitflow-receiver-v1` (pushed to origin, commit `581fade`). + +**Contracts:** +- `contracts/hk-stx-bitflow-receiver-v1.clar` (176 lines) β€” the receiver. Caller-gated, dynamic fee lookup, two Bitflow legs, fail-closed repay, `rescue-stx`/`set-slippage-bp` admin, read-only `estimate-repayment`/`get-slippage-bp`/`get-owner`. + +**Scripts:** +- `scripts/deploy-hk-bitflow-receiver.mjs` (148) β€” mainnet deploy, `DRY_RUN` aware, reads `MAINNET_MNEMONIC`/`./mbegu2`/`./mbegu`. +- `scripts/execute-bitflow-flash-loan.mjs` (141) β€” calls `flash-loan(amount, receiver)` on the core; `AMOUNT_USTX`/`DRY_RUN` env; built-in read-only preflight. + +**Repo config:** `.gitignore` β€” added `mbegu`/`mbegu2`. + +**Obsidian notes (vault `../Flashstack-Vault/`):** see Β§17. + +**ADRs:** ADR-001…004 in [[ADR]]. + +**Beads:** epic `Flashstack-n9p` (Milestone 1) with phase tasks; memories (see Β§16). + +--- + +## 7. Architecture Deep Dive + +**FlashStack architecture.** `flashstack-stx-core` holds the reserve, a whitelist (`approved-receivers`), a fee (5 bps), guards (paused / amount>0 / ≀ max-single-loan / approved / reserveβ‰₯amount), and the post-callback reserve check. LP pool + oracle sit alongside. Full detail in [[FlashStack-Architecture]]. + +**Flash loan lifecycle** (from [[Flash-Loan-Lifecycle]]): +``` +caller β†’ core.flash-loan(amount, receiver) + guards β†’ stx-transfer amount β†’ receiver + β†’ receiver.execute-stx-flash(amount, core) [YOUR STRATEGY] + receiver repays amount+fee β†’ core + β†’ assert reserve-after β‰₯ reserve-before + fee β†’ (ok true) | revert +``` + +**Receiver lifecycle.** Deploy (external wallet) β†’ **whitelist** (admin) β†’ **seed β‰₯1 STX** (the receiver pays the fee from its own balance, so a 0-STX receiver can't borrow even 1 Β΅STX) β†’ borrow/execute/repay. + +**Dynamic fee lookup.** The receiver reads `get-fee-basis-points` from the core each call rather than hard-coding `u5`; if Matt changes the fee, a hard-coded receiver would silently under-repay and revert. + +**Bitflow integration.** Pool `SPQC38PW…stableswap-stx-ststx-v-1-2`. `swap-x-for-y(y-token, lp-token, x-amount, min-y-amount)` = STXβ†’stSTX; `swap-y-for-x(…)` = stSTXβ†’STX. y-token = `SP4SZE49…ststx-token`, lp-token = `SPQC38PW…stx-ststx-lp-token-v-1-2`. Signatures verified against the live mainnet interface. + +**STX β†’ stSTX β†’ STX flow** (sequence diagram in [[Bitflow-Roundtrip-Analysis]]): +``` +borrow STX β†’ swap-x-for-y β†’ stSTX (held by receiver) β†’ swap-y-for-x β†’ STX β†’ repay amount+fee β†’ core +``` +Both legs run under `as-contract` (the assets sit in the receiver's own balance). `min-out = u1` so swaps always clear. + +**Atomic repayment model.** The receiver asserts `stx-balance β‰₯ total-owed` then transfers; the core re-checks the reserve. Any sub-failure β†’ `(err …)` β†’ the entire transaction reverts. The reserve is never at risk; the worst case is a revert (DoS), not a loss. + +**Whitelist model.** `add-approved-receiver` / `remove-approved-receiver` are admin-only. This is the choke point that stops anyone pointing the loan at a malicious contract β€” and the reason we're blocked on Matt. + +--- + +## 8. Deployment Information + +| Field | Value | +|---|---| +| **Contract** | `SP3NZYZA88ENNF0FCR57KBGPFY5RAXWHXXVSB6FBW.hk-stx-bitflow-receiver-v1` | +| **Deploy TXID** | `7e5b3ec55357d119470aa79fe7d5e32c07922b7b7f2c975281cb8456f7dea567` | +| **Status** | success Β· `tx_result = (ok true)` | +| **Block** | 8197121 | +| **Clarity version** | 3 | +| **Deployer wallet** | `SP3NZYZA88ENNF0FCR57KBGPFY5RAXWHXXVSB6FBW` (nonce was 8; ~37.06 STX after deploy) | +| **Explorer** | https://explorer.hiro.so/txid/7e5b3ec55357d119470aa79fe7d5e32c07922b7b7f2c975281cb8456f7dea567?chain=mainnet | + +**Deployment validation performed:** tx confirmed `success`/`(ok true)`; contract source published (6,900 bytes); live read-only `estimate-repayment(u1000000)` β†’ `fee-to-pay 500, total-owed 1,000,500` (proves the deployed contract's dynamic fee lookup from the core works on-chain). + +--- + +## 9. Validation Evidence + +Full detail in [[Phase6-Validation]]. + +- **Clarinet:** `βœ” 6 contracts checked`, **0 errors / 0 warnings** on the receiver. Validated via a local-stub harness (`/tmp/fs-stub`) because the repo project can't host hard-coded mainnet principals (simnet remap β†’ `NoSuchContract`). Stub signatures mirror the verified live interfaces; receiver principals rewritten to local `.name` sugar (addresses-only delta). +- **Dry-run:** wallet `SP3NZYZA88…`, 37.56 STX, nonce 8, tx builds (14,096 bytes). +- **Mainnet preflight:** core not paused, fee = 5 bps, reserve 75.126 STX, max-single-loan 500,000 STX, receiver not-yet-approved. +- **Live quote:** 1.0 STX β†’ 880,583 Β΅stSTX β†’ **0.999001 STX** back; owed 1.0005 STX; **shortfall 1,499 Β΅STX (~0.0015 STX)**, covered by a 1 STX seed ~660Γ—. +- **Bug found + fixed:** `estimate-repayment` (read-only) called the core through the `FLASHSTACK-STX-CORE` *constant* β€” Clarity can't prove a constant-dispatched call is read-only β†’ rejected (would also fail at mainnet publish). Fixed by using the statically-resolvable **literal** core principal in that function. + +--- + +## 10. Risk Register + +| Risk | Severity | Mitigation | Outstanding? | +|---|---|---|---| +| `./mbegu` plaintext mainnet **seed phrase** at repo root | **High** | Added to `.gitignore`; kept out of all commits | **Yes** β€” recommend moving out of repo (`~/.flashstack/mbegu`) or env-only | +| `min-out = u1` (MEV / sandwich / hostile pool) | Medium β€” **DoS only, no loss** | repay-assert + core reserve check; reserve never at risk | Accept for execution objective; tighten floor for a profit strategy | +| Admin changes fee mid-tx | Low | dynamic fee lookup each call | No | +| Direct call to `execute-stx-flash` | Low | caller gate β†’ `ERR-WRONG-CALLER u403` | No | +| STX stranded after partial round-trip | Low | owner-only `rescue-stx` | No | +| Round-trip returns < owed | Low | seed covers ~0.0015 STX shortfall ~660Γ— on 1 STX; fails closed otherwise | No | +| HTTPS git remote can't auth | Low | switched `origin` to SSH (works) | No | + +**Seed-phrase handling observation:** the scripts read `MAINNET_MNEMONIC` env first, then `./mbegu2`, then `./mbegu`. The file is now gitignored but remains a live key on disk; treat with care. + +--- + +## 11. Communication History + +- **Milestone submission / deliverables:** presented the full Phase-7 package (branch, diff +468/4 files, architecture summary, risk assessment, test evidence) to the user for the pre-broadcast gate. +- **Approval request β†’ granted:** user approved broadcasting the deploy (0.5 STX). +- **Deploy executed:** confirmed on mainnet (tx `7e5b3ec5…`). +- **Whitelist request (pending):** drafted a DM for Matt asking him to `add-approved-receiver` the contract id. **User still needs to send / confirm this.** +- **Current waiting state:** awaiting Matt's whitelist tx before seed + flash-loan. +- **Push:** branch pushed to origin (`581fade`). + +--- + +## 12. Current Blockers + +**None β€” Milestone 1 is complete.** The former primary blocker (whitelist approval from Matt) was resolved: Matt called `add-approved-receiver` from the admin wallet `SP20XD46…` (tx `7ae15a50…370b`, block 8197338), confirmed by read-only `is-approved-receiver(receiver) β†’ true`, after which the receiver was seeded and the flash loan executed `(ok true)`. See [[Milestone-1-Execution-Evidence]] and Β§21. + +--- + +## 13. Immediate Next Actions + +Once whitelist approval is received: + +1. **Verify approval (read-only):** + ```bash + curl -s -X POST https://api.hiro.so/v2/contracts/call-read/SP20XD46NGAX05ZQZDKFYCCX49A3852BQABNP0VG5/flashstack-stx-core/is-approved-receiver \ + -H "Content-Type: application/json" \ + -d '{"sender":"SP3NZYZA88ENNF0FCR57KBGPFY5RAXWHXXVSB6FBW","arguments":[""]}' + # expect (ok true) / 0x0703 + ``` +2. **Seed the receiver (β‰₯1 STX):** send `1_000_000` Β΅STX from `SP3NZYZA88…` to `SP3NZYZA88….hk-stx-bitflow-receiver-v1` (Leather/Xverse send, or a small script). +3. **Execute the flash loan:** + ```bash + cd /home/unixx/Desktop/Workspace/Matt/folk/Flashstack + AMOUNT_USTX=1000000 node scripts/execute-bitflow-flash-loan.mjs # add DRY_RUN=1 first to preview + ``` +4. **Collect transaction IDs** (deploy βœ…, whitelist, seed, flash-loan). +5. **Collect event logs** β€” the `bitflow-roundtrip` print `{amount, fee, ststx-mid, stx-after}` from the flash-loan tx. +6. **Verify reserve repayment** β€” core `get-stats` `total-loans`+1, `reserve` grew by β‰₯ fee. +7. **Produce on-chain proof package** β€” fill the "after successful execution" block in [[Phase7-Deliverables]]. +8. **Close Milestone 1** β€” `bd close Flashstack-n9p.6`, `bd close Flashstack-n9p.5`, `bd close Flashstack-n9p`. + +--- + +## 14. Future Work + +**Priority 1 β€” Documentation feedback report (Matt request #3).** Lowest effort, immediately useful, and material is already accumulating (the Clarinet.toml gap, the external-principal rule, the `mbegu2` vs `mbegu` filename gotcha, the seed-before-loan requirement). Deliver as a structured report. + +**Priority 2 β€” sBTC architecture research (precursor to #2).** Read `flashstack-sbtc-core` / `flashstack-sbtc-pool` / `sbtc-flash-receiver-trait`, map the canonical-sBTC flash-loan path and the sBTC token model. + +**Priority 3 β€” `hk-sbtc-real-receiver-v1` implementation (Matt request #2).** Mirror the STX receiver pattern for sBTC (likely a Velar or Bitflow sBTC pair). Depends on P2. + +**Recommended order:** P1 first (cheap, closes a Matt request, no on-chain risk), then P2β†’P3 as a unit. Rationale: finish the doc-feedback loop while the integration is fresh, then take on the sBTC track as its own milestone. + +--- + +## 15. Repository State + +- **Active branch:** `feature/hk-stx-bitflow-receiver-v1` (tracks `origin/feature/hk-stx-bitflow-receiver-v1`, **0 ahead / 0 behind** β€” fully pushed). +- **Latest commit:** `581fade` Add hk-stx-bitflow-receiver-v1: external receiver vs Bitflow DEX. +- **Pending commits / pushes:** none (in sync with origin). +- **Uncommitted changes (pre-existing, NOT ours, intentionally left):** `.beads/config.yaml` (M β€” backup git-push disabled), `ONBOARDING.md` (?? β€” untracked). +- **Ignored (never commit):** `mbegu`, `mbegu2` (mainnet seed phrases). +- **Important files:** `contracts/hk-stx-bitflow-receiver-v1.clar`, `scripts/deploy-hk-bitflow-receiver.mjs`, `scripts/execute-bitflow-flash-loan.mjs`, `contracts/flashstack-stx-core.clar`, `ONBOARDING.md`, `AGENTS.md`, `.beads/`. + +``` +branch feature/hk-stx-bitflow-receiver-v1 β†’ origin (0/0) + M .beads/config.yaml (pre-existing, not ours) + ?? ONBOARDING.md (pre-existing, untracked) + mbegu / mbegu2 (gitignored secrets) +``` + +--- + +## 16. Beads Memory Export + +Tracker: epic **`Flashstack-n9p`** (Milestone 1) β€” phases 1–6 closed; `Flashstack-n9p.5` (deliverables) and `Flashstack-n9p.6` (whitelist+seed+execute) open. Prior epic `Flashstack-64o` (v2 receiver) is 8/9. + +Stored memories (via `bd remember`, surfaced at `bd prime`): +- **`bitflow-stx-ststx-live-mainnet-interface-verified-2026`** β€” Bitflow pool + swap-x-for-y/swap-y-for-x signatures, token/LP principals. +- **`flashstack-external-deploy-rule-a-receiver-deployed-under`** β€” absolute-principal rule; core sends STX then calls `execute-stx-flash`; whitelist + seed required; `mbegu`/`mbegu2` are secrets. +- **`clarinet-toml-gotcha-it-only-registers-the-old`** β€” Clarinet.toml only has the old sBTC suite; STX suite is script-deployed; use a scratch project / local stubs to type-check. +- **`hk-stx-bitflow-receiver-v1-validation-2026-06`** β€” clarinet 0/0, wallet/nonce, core health, round-trip quote, the read-only bug fix. +- **`hk-stx-bitflow-receiver-v1-deployed-2026-06`** β€” deploy txid, block, live read-only check, next steps. +- **`flashstack-session-resume-state`** (this handoff) β€” current milestone, deployment, blocker, next actions, roadmap. + +--- + +## 17. Obsidian Vault Index + +Vault root: `/home/unixx/Desktop/Workspace/Matt/folk/Flashstack-Vault/` + +| Note | Purpose | Contents | Relevance | +|---|---|---|---| +| [[00-Index]] | Map of the vault | Address map, note links, status log | Entry point (now points here) | +| [[FlashStack-Architecture]] | Protocol overview | Contracts, trait, constraints, error codes | High | +| [[Flash-Loan-Lifecycle]] | Loan mechanics | Step-by-step, fee handling, repayment | High | +| [[Receiver-Comparison]] | Receiver survey | test vs v2 vs bitflow-v4, what v1 takes from each | High | +| [[Bitflow-Roundtrip-Analysis]] | DEX strategy | Asset flow, params, slippage, **sequence diagram** | High | +| [[hk-stx-bitflow-receiver-v1-Design]] | The new receiver | Decisions D1–D8, contract shape, validation plan | High | +| [[ADR]] | Decision records | ADR-001…004 | Medium | +| [[Phase6-Validation]] | Validation evidence | clarinet, dry-run, preflight, quote, bug | High | +| [[Phase7-Deliverables]] | Deliverables + on-chain proof | Branch/diff/risk/evidence; deploy txid filled | High | +| [[Blockers]] | Open items | Whitelist blocker, resolved items, risks | High | +| [[FLASHSTACK_PROJECT_HANDOFF_2026-06-06]] | **This handoff** | Full project state | **Primary entry point** | + +Relationships: handoff ↔ architecture ↔ design ↔ bitflow-analysis ↔ deployment(Phase7) ↔ roadmap(Β§14). All notes backlink to [[00-Index]]. + +--- + +## 18. Session Learnings + +1. **Clarinet β‰  deploy path here.** The STX/Bitflow suite is deployed by scripts that don't type-check Clarity; `clarinet check` only covers the old sBTC set. Always validate new STX receivers in a scratch/stub Clarinet project β€” don't assume the repo project covers them. +2. **Read-only cross-contract calls need a *literal* target.** A `define-constant` principal works in public functions but is rejected in `define-read-only` (Clarity can't prove read-only-ness). Real bug, caught pre-broadcast. +3. **External deploy β‡’ absolute principals.** The `.contract` sugar resolves to the *deployer's* address; an external receiver must hard-code `SP20XD46….flashstack-stx-core`. +4. **Seed-before-loan is non-obvious but mandatory.** The receiver pays the fee from its own balance inside the callback, so it must hold STX before the first loan. +5. **stSTX trades off-peg (~1.135Γ—).** A percentage slippage floor on the STX amount makes legs revert; `min-out=u1` + repay-assert is the pragmatic pattern for execution. +6. **Secret hygiene gap.** A live mainnet seed phrase sat un-ignored at repo root. Future sessions: verify `git check-ignore mbegu` before any `git add`. +7. **Git auth:** HTTPS origin can't auth in this env; SSH works (`unixwhisperer`). origin was switched to SSH. + +--- + +## 22. POST-M1 CONTINUATION β€” Matt requests #3 + #2 research/design (2026-06-06) + +After M1 closed, executed Matt's remaining requests in the recommended order (one milestone at a time). Beads epic `Flashstack-0w4` (children `.1`/`.2`/`.3` all closed, 3/3). + +- **Phase A β€” Documentation review (#3): DONE.** [[docs-feedback-report]] (repo `docs-feedback-report.md` + vault `Reviews/`). External-dev review, every claim source-verified. 9 confirmed factual errors (E1–E9), incl. max-single-loan documented as 5,000 STX but live = **500,000 STX** (100Γ— off); `README.md:174` STX `impl-trait` points at the wrong contract (won't deploy); ONBOARDING error codes `u3/u4/u6` should be `u306/u304/u303`; `TESTING_GUIDE_STX` calls the **live** sBTC core "legacy/do-not-test". Plus missing-info (seed-before-loan absent from primary docs, external-principal rule, sBTC reserve size) and the STX-vs-sBTC "same interface" myth. +- **Phase B β€” sBTC architecture research (#2 precursor): DONE.** [[sBTC-Architecture-Review]] (repo `sbtc-architecture-review.md` + `docs/` + vault `Architecture/`). `flashstack-sbtc-core` is **live** (reserve ~15,010 sats, 2 loans, fee 5 bps, max 0.1 BTC). Structurally == STX but SIP-010 sBTC token; differs in get-stats shape, `is-approved` return, withdraw-reserve args, admin model, error base. Found a latent core bug (`set-fee-basis-points` wrong error + no lower bound; the pool gets it right). +- **Phase C β€” `hk-sbtc-real-receiver-v1` DESIGN (no implementation): DONE.** [[hk-sbtc-real-receiver-v1-Design]] (vault `Design/` + repo project-docs). Design = M1 skeleton βŠ• Velar sBTCβ†’wSTXβ†’sBTC legs βŠ• a **new caller gate** (the `velar-sbtc-arb-receiver` reference lacks one). ADR-S1…S7 (in [[ADR]]). **Key dependency:** needs a **real sBTC seed** + possibly a reserve top-up from Matt; loan capped at ~15k sats by current reserve. + +**Hard gate honored:** P3 implementation NOT started. Before implementing: approve the design, confirm target loan size / reserve top-up with Matt, acquire the sBTC seed. + +--- + +## 21. MILESTONE 1 β€” COMPLETE (2026-06-06) + +**Milestone 1 is formally complete.** All success criteria met and proven on mainnet. Authoritative evidence: [[Milestone-1-Execution-Evidence]]. + +- Whitelist: `7ae15a501a011710eeff0a4a330bab57e77d931296a4cf7a3659811ce108370b` (block 8197338) β€” `is-approved-receiver β†’ true`. +- Seed (1 STX): `732374dfb1f6123d41d8d872bcbc27e09a47ceb26da0dd95b3a43b1cdf732b4c` (block 8197525). +- **Flash loan: `865df57633fd111c76df3db5caa73577093e91e967af901a89db8de9cf639737` β€” `(ok true)`, block 8197535.** Bitflow STXβ†’stSTXβ†’STX round-trip cleared; `bitflow-roundtrip` event emitted; core reserve +500 Β΅STX; total-loans 8β†’9. +- Seed recovered (`rescue-stx`): `f605a7bf6c51b760e65cf87ea383c29a39f2726bd9f29f71e0be25b9d2de743d` (block 8197553). +- Wallet spend: 0.3215 STX total; seed fully recovered; wallet now ~36.74 STX. + +**Next session β€” start the deferred Matt requests (M1 is closed, so these may now begin):** +1. **P1 β€” Documentation feedback report (Matt request #3).** Lowest effort; material already accumulated (see Β§14, Β§18). No on-chain risk. +2. **P2 β†’ P3 β€” sBTC track (Matt request #2).** Research `flashstack-sbtc-core` path, then build `hk-sbtc-real-receiver-v1`. Treat as its own milestone. +- Housekeeping carried forward: relocate `./mbegu` out of the repo root (or env-only); consider opening a PR for `feature/hk-stx-bitflow-receiver-v1`. + +--- + +## 19. START HERE NEXT SESSION + +**Current state.** Milestone 1 is **COMPLETE** (see Β§21). `hk-stx-bitflow-receiver-v1` deployed, whitelisted, seeded, and the flash loan executed `(ok true)` on mainnet (`865df576…639737`, block 8197535). Branch pushed. + +**Highest-priority task.** Begin the deferred Matt requests β€” start with the documentation-feedback report (#3, cheapest), then the sBTC track (#2) as a new milestone. Do NOT re-run M1. + +**Current blocker.** None. + +**Required verification steps (do first).** +1. `is-approved-receiver(receiver)` β†’ must be `(ok true)`. +2. Confirm our wallet `SP3NZYZA88…` still funded (~37 STX) and the receiver's seed balance. + +**Files to review first.** This handoff β†’ [[hk-stx-bitflow-receiver-v1-Design]] β†’ `contracts/hk-stx-bitflow-receiver-v1.clar` β†’ `scripts/execute-bitflow-flash-loan.mjs` β†’ [[Phase6-Validation]]. + +**Commands to run first.** +```bash +cd /home/unixx/Desktop/Workspace/Matt/folk/Flashstack +bd prime # load beads memories +git status && git log --oneline -3 +# whitelist check (see Β§13 step 1) +DRY_RUN=1 AMOUNT_USTX=1000000 node scripts/execute-bitflow-flash-loan.mjs +``` + +**Questions awaiting answers.** +- Has Matt whitelisted the receiver yet? (If not, hold.) +- Should `./mbegu` be relocated out of the repo? (Recommended yes.) +- Open a PR for the branch? (Not yet done.) + +**Definition of success for next session.** Whitelist verified β†’ receiver seeded β†’ `flash-loan(u1000000)` confirmed `(ok true)` β†’ txids + `bitflow-roundtrip` event + reserve delta captured into [[Phase7-Deliverables]] β†’ Milestone 1 closed in Beads β†’ handoff updated. + +--- + +## 20. RECOVERY PROMPT FOR NEXT CLAUDE SESSION + +> Paste the block below into a brand-new session. + +``` +You are continuing an external-integrator engagement with the FlashStack flash-loan +protocol on Stacks mainnet. Resume with zero context loss. + +PROJECT: External integration of FlashStack (atomic flash loans on Stacks, Clarity 3). +We are an external developer validating the protocol for the owner, "Matt". + +CURRENT MILESTONE: Milestone 1 β€” external strategy receiver against a real DEX (Bitflow). +~90% complete. Do NOT start sBTC work or docs feedback until M1 is closed. + +WHAT EXISTS: +- Receiver DEPLOYED to mainnet: + SP3NZYZA88ENNF0FCR57KBGPFY5RAXWHXXVSB6FBW.hk-stx-bitflow-receiver-v1 + Deploy TXID: 7e5b3ec55357d119470aa79fe7d5e32c07922b7b7f2c975281cb8456f7dea567 + Status: success / (ok true) / block 8197121. +- It borrows STX from SP20XD46NGAX05ZQZDKFYCCX49A3852BQABNP0VG5.flashstack-stx-core, + does a real STX->stSTX->STX round-trip on Bitflow (SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M.stableswap-stx-ststx-v-1-2), + and repays principal+fee atomically. Objective is execution, not profit. +- Branch feature/hk-stx-bitflow-receiver-v1 is pushed to origin (commit 581fade). + +CURRENT BLOCKER: Matt must whitelist the receiver via add-approved-receiver on +flashstack-stx-core (admin-only). Until is-approved-receiver returns (ok true), +the flash loan will fail with ERR-NOT-APPROVED (u306). + +OUR WALLET: SP3NZYZA88ENNF0FCR57KBGPFY5RAXWHXXVSB6FBW (~37 STX). Mnemonic in +./mbegu (gitignored mainnet secret β€” never commit; scripts also read MAINNET_MNEMONIC). + +OBSIDIAN VAULT: /home/unixx/Desktop/Workspace/Matt/folk/Flashstack-Vault/ + Read FLASHSTACK_PROJECT_HANDOFF_2026-06-06.md first, then 00-Index.md. +REPO: /home/unixx/Desktop/Workspace/Matt/folk/Flashstack + Also project-docs/session-handoffs/FLASHSTACK_PROJECT_HANDOFF_2026-06-06.md +BEADS: run `bd prime`. Epic Flashstack-n9p (Milestone 1); open tasks .5 and .6. + Memory key: flashstack-session-resume-state. + +NEXT ACTIONS (once Matt whitelists): + 1. Verify: is-approved-receiver(receiver) == (ok true). + 2. Seed >=1 STX to the receiver. + 3. Run: AMOUNT_USTX=1000000 node scripts/execute-bitflow-flash-loan.mjs (DRY_RUN=1 first). + 4. Capture txids + the bitflow-roundtrip event + reserve delta. + 5. Fill the "after successful execution" block in Phase7-Deliverables, close Milestone 1 in Beads. + +SUCCESS CRITERIA: receiver whitelisted, flash loan executed (ok true), on-chain proof +collected, Milestone 1 documented and closed. + +FIRST STEP: ask the user whether Matt has whitelisted the receiver yet. If not, hold +and offer to poll is-approved-receiver. If yes, proceed with NEXT ACTIONS. +``` diff --git a/project-docs/session-handoffs/Milestone-1-Completion-Report.md b/project-docs/session-handoffs/Milestone-1-Completion-Report.md new file mode 100644 index 0000000..be69036 --- /dev/null +++ b/project-docs/session-handoffs/Milestone-1-Completion-Report.md @@ -0,0 +1,80 @@ +--- +title: Milestone 1 β€” Completion Report +date: 2026-06-06 +status: complete +milestone: 1 +type: completion-report +audience: [Matt, FlashStack records, Stacks Endowment] +--- + +# FlashStack Milestone 1 β€” Completion Report + +> **Status: βœ… COMPLETE (2026-06-06).** An external-wallet receiver borrowed STX from the live `flashstack-stx-core`, executed a real STX β†’ stSTX β†’ STX round-trip on the Bitflow stableswap, and repaid principal + fee **atomically** on Stacks mainnet. Objective β€” *successful external strategy execution + repayment, not profit* β€” fully met. + +Related: [[Milestone-1-Execution-Evidence]] Β· [[FLASHSTACK_PROJECT_HANDOFF_2026-06-06]] Β· [[Phase7-Deliverables]] Β· [[Phase6-Validation]] Β· [[hk-stx-bitflow-receiver-v1-Design]] Β· [[ADR]] Β· [[Future-Roadmap]] Β· [[Dashboard]] + +--- + +## 1. Objective vs. outcome + +| Success criterion | Result | +|---|---| +| External-wallet receiver deployed | βœ… `SP3NZYZA88…hk-stx-bitflow-receiver-v1` (deploy tx `7e5b3ec5…dea567`, block 8197121) | +| Whitelisted by the protocol admin | βœ… tx `7ae15a50…370b`, block 8197338 (`is-approved-receiver β†’ true`) | +| Flash loan executed | βœ… **`(ok true)`** β€” tx `865df576…639737`, block 8197535 | +| Real DEX interaction (not a no-op) | βœ… Bitflow STXβ†’stSTXβ†’STX round-trip, 861,402 Β΅stSTX mid | +| Atomic repayment | βœ… 1.000500 STX repaid; core reserve +500 Β΅STX; `total-loans` 8β†’9 | +| On-chain evidence collected | βœ… [[Milestone-1-Execution-Evidence]] | +| Documentation | βœ… full vault + repo handoff | + +**Result: every criterion met and independently re-confirmed canonical on-chain.** + +--- + +## 2. What was delivered + +- **Contract:** `contracts/hk-stx-bitflow-receiver-v1.clar` (176 lines) β€” caller-gated, dynamic fee lookup, two Bitflow legs, fail-closed repay, owner-only `rescue-stx`/`set-slippage-bp`, read-only `estimate-repayment`. +- **Scripts:** `deploy-hk-bitflow-receiver.mjs`, `execute-bitflow-flash-loan.mjs`, and new `seed-bitflow-receiver.mjs`. +- **Knowledge base:** architecture, lifecycle, receiver comparison, Bitflow analysis, design + ADRs, validation, deliverables, execution evidence. +- **Architecture:** `hk-stx-bitflow-receiver-v1` = `hk-stx-real-receiver-v2` (caller gate + absolute principals) βŠ• `bitflow-arb-receiver-v4` (the Bitflow round-trip). **No protocol contract was modified.** + +--- + +## 3. On-chain transaction chain + +| # | Step | TXID | Result | Block | +|---|---|---|---|---| +| 1 | Deploy | `7e5b3ec55357d119470aa79fe7d5e32c07922b7b7f2c975281cb8456f7dea567` | `(ok true)` | 8197121 | +| 2 | Whitelist (Matt) | `7ae15a501a011710eeff0a4a330bab57e77d931296a4cf7a3659811ce108370b` | success | 8197338 | +| 3 | Seed 1 STX | `732374dfb1f6123d41d8d872bcbc27e09a47ceb26da0dd95b3a43b1cdf732b4c` | `(ok true)` | 8197525 | +| 4 | **Flash loan** | **`865df57633fd111c76df3db5caa73577093e91e967af901a89db8de9cf639737`** | **`(ok true)`** | 8197535 | +| 5 | Rescue seed | `f605a7bf6c51b760e65cf87ea383c29a39f2726bd9f29f71e0be25b9d2de743d` | `(ok true)` | 8197553 | + +Net cost: **0.3215 STX** (0.30 loan tx fee + two 0.01 tx fees + 0.0015 round-trip). Seed fully recovered. The protocol reserve was never at risk β€” the loan is atomic. + +--- + +## 4. Lessons learned + +These are distilled for reuse on the sBTC track and as input to the [[docs-feedback-report|documentation feedback report]]. + +1. **Verify on-chain state, never trust pasted hashes.** The resume handoff carried a garbled 62-char whitelist txid; the authoritative 64-char tx was recovered by scanning the admin wallet's `add-approved-receiver` calls, and approval was confirmed independently by read-only `is-approved-receiver β†’ true`. Ground every claim in a live read. +2. **Seed-before-loan is mandatory and non-obvious.** The receiver pays the fee (and absorbs any round-trip slippage) from its *own* balance inside the callback, so a 0-balance receiver cannot borrow even 1 Β΅STX. The public docs' "zero capital required" framing is true only for net-profitable strategies β€” execution/validation receivers need a seed buffer. +3. **External deploys require absolute principals.** `.flashstack-stx-core` sugar resolves to the *deployer's* address; the in-repo `bitflow-arb-receiver` (which the docs point to as the model) only works because it's deployed under the protocol deployer. An external receiver must hard-code `SP20XD46….flashstack-stx-core`. +4. **Read-only cross-contract calls need a *literal* target.** A `define-constant` principal works in public functions but is rejected inside `define-read-only` (Clarity can't prove read-only-ness). Caught and fixed pre-broadcast. +5. **stSTX trades off-peg; percentage slippage floors backfire.** `min-out = u1` + a repay-assert is the pragmatic pattern for an execution objective; tighten the floor only for a profit strategy. +6. **Clarinet β‰  the deploy path here.** The STX/Bitflow suite is script-deployed (`makeContractDeploy`, no type-check); `clarinet check` only covers the old sBTC set. Validate new receivers in a scratch/stub Clarinet project. +7. **Secret hygiene.** A live 24-word mainnet seed phrase (`./mbegu`) sits at the repo root; now gitignored. Recommend relocating out of the repo. +8. **`rescue-stx` is worth always including.** The owner-only escape hatch cleanly recovered the residual seed and is itself a useful piece of evidence that the safety hatch works. + +--- + +## 5. Status roll-up + +- **Beads:** epic `Flashstack-n9p` closed (6/6, 100%); memory `milestone-1-complete-flash-loan-executed-2026-06`. +- **Obsidian:** [[Milestone-1]] = complete; [[Milestone-1-Execution-Evidence]] filed; [[Blockers]] cleared; [[Dashboard]] + [[Future-Roadmap]] updated. +- **Next (Matt requests, now unblocked):** P1 documentation feedback ([[docs-feedback-report]]) β†’ P2 sBTC architecture research ([[sbtc-architecture-review]]) β†’ P3 `hk-sbtc-real-receiver-v1` design ([[hk-sbtc-real-receiver-v1-Design]]). One milestone at a time. + +--- + +*Generated 2026-06-06 from live mainnet reads and source verification.* diff --git a/project-docs/session-handoffs/hk-sbtc-real-receiver-v1-Design.md b/project-docs/session-handoffs/hk-sbtc-real-receiver-v1-Design.md new file mode 100644 index 0000000..20a4aa1 --- /dev/null +++ b/project-docs/session-handoffs/hk-sbtc-real-receiver-v1-Design.md @@ -0,0 +1,130 @@ +--- +title: hk-sbtc-real-receiver-v1 β€” Design Proposal (Phase C) +date: 2026-06-06 +type: design +status: proposal +implementation: NOT STARTED +milestone: 2 (proposed) +--- + +# hk-sbtc-real-receiver-v1 β€” Design Proposal + +> **Status: DESIGN ONLY β€” no implementation.** This is the Phase C planning deliverable for Matt request #2 (external sBTC strategy receiver). It assumes [[docs-feedback-report|Phase A]] and [[sBTC-Architecture-Review|Phase B]] are complete (they are). Implementation must not begin until this proposal is reviewed. + +Related: [[sBTC-Architecture-Review]] Β· [[hk-stx-bitflow-receiver-v1-Design]] Β· [[ADR]] Β· [[Milestone-1-Completion-Report]] Β· [[Future-Roadmap]] + +--- + +## 1. Objective + +Mirror the Milestone 1 achievement on the **canonical sBTC** engine: an external-wallet receiver that borrows sBTC from the live `flashstack-sbtc-core`, performs a **real DEX round-trip** (sBTC β†’ wSTX β†’ sBTC on Velar), and repays principal + fee **atomically**. Objective is **successful external strategy execution + repayment, not profit** β€” identical bar to M1. + +--- + +## 2. Architecture proposal + +`hk-sbtc-real-receiver-v1` = **`hk-stx-bitflow-receiver-v1` skeleton** (caller gate + absolute principals + dynamic fee + fail-closed repay + rescue + read-only estimate) **βŠ• `velar-sbtc-arb-receiver` swap legs** (the proven sBTCβ†’wSTXβ†’sBTC round-trip on Velar pool 70). + +``` +flashstack-sbtc-core.flash-loan(amount, hk-sbtc-real-receiver-v1) + β†’ transfers sBTC (sats) to receiver + β†’ receiver.execute-sbtc-flash(amount, core) + assert contract-caller == flashstack-sbtc-core ;; CALLER GATE (new vs velar ref) + fee-bp = (sbtc-core get-fee-basis-points) ;; dynamic + owed = amount + max(amount*fee-bp/10000, 1) + Leg 1: as-contract Velar swap sBTC β†’ wSTX (pool 70, min-out u1) + Leg 2: as-contract Velar swap wSTX β†’ sBTC (all wSTX, min-out u1) + assert sbtc-balance(self) >= owed else ERR-REPAY + as-contract sbtc-token.transfer owed β†’ core (memo none) + print { event: "sbtc-velar-roundtrip", amount, fee, wstx-mid, sbtc-after } + (ok true) +``` + +**Key structural points:** +- `(impl-trait 'SP20XD46….sbtc-flash-receiver-trait.sbtc-flash-receiver-trait)` (protocol-deployer trait). +- Import a minimal **SIP-010 trait** to call `sbtc-token` and `wstx` (`get-balance`, `transfer`). +- Repay via `(as-contract (contract-call? SBTC transfer owed tx-sender core none))` β€” not `stx-transfer?`. +- Every cross-contract principal is **absolute** (external deploy). +- Owner-only `rescue-sbtc(amount, to)` and `set-slippage-bp`; read-only `estimate-repayment(amount)`. + +--- + +## 3. Architectural Decision Records (sBTC) + +| ADR | Decision | Rationale | +|---|---|---| +| **ADR-S1** | Reuse the M1 receiver skeleton, swap STX primitives β†’ SIP-010 sBTC calls | M1 is proven; minimizes new surface | +| **ADR-S2** | Add a **caller gate** (`contract-caller == flashstack-sbtc-core` β†’ `ERR-WRONG-CALLER`) | The reference `velar-sbtc-arb-receiver` exposes a **public** `execute-sbtc-flash` with no gate β€” a direct-drain vector our `hk-stx-real-receiver-v2` specifically closed. Non-negotiable. | +| **ADR-S3** | Venue = **Velar univ2 pool 70** (sBTC/wSTX) for v1 | Only proven, whitelisted on-chain sBTC round-trip in-repo; router/pool/wstx all verified live. Accept the ~0.6% round-trip cost; objective is execution, not profit. | +| **ADR-S4** | `min-out = u1` on both legs; repay-assert + core reserve check are the gates | Same pattern as M1; avoids percentage-floor reverts on a thin pool | +| **ADR-S5** | **Absolute** mainnet principals everywhere | External deploy; `.sugar` would resolve to our wallet (M1 ADR-001) | +| **ADR-S6** | Seed the receiver with **real canonical sBTC**, sized to round-trip cost + margin; recover via `rescue-sbtc` | Receiver pays fee + slippage from its own balance; Velar ~0.6% cost dominates the 5 bps fee | +| **ADR-S7** | Size the loan to the **live reserve** (~15,010 sats) unless Matt tops up `deposit-reserve` | Guard `reserve β‰₯ amount`; advertised 0.1 BTC max is irrelevant at current reserve | + +*(To be appended to [[ADR]] on approval.)* + +--- + +## 4. Deployment plan (mirrors M1) + +1. **Pre-flight:** confirm `flashstack-sbtc-core` not paused, fee, **live reserve** (`get-reserve-balance`), max-loan; confirm Velar pool 70 liquidity via a read-only quote. +2. **Acquire sBTC seed** (external dependency, see Β§6): bridge BTC or buy a few thousand sats on a Stacks DEX with STX we hold. +3. **Validate** in a stub Clarinet project (sBTC-core + sbtc-token + Velar router stubs mirroring live interfaces) β€” `clarinet check` 0/0. +4. **Deploy** `hk-sbtc-real-receiver-v1` to mainnet (~0.5 STX; `DRY_RUN` first). +5. **Whitelist:** Matt calls `add-approved-receiver` on `flashstack-sbtc-core`; verify `is-approved-receiver β†’ (ok true)` (note: **`(ok bool)`**, unwrap it). +6. **Seed** the receiver with sBTC; verify its `sbtc-token` balance. +7. **Dry-run** the executor, then **execute** `flash-loan(amount, receiver)` β†’ expect `(ok true)`. +8. **Collect evidence** (txid, `sbtc-velar-roundtrip` event, Velar swap events, reserve delta, `total-loans` +1) into an execution-evidence note. +9. **Recover** the residual sBTC seed via `rescue-sbtc`. + +New scripts (to write at implementation time): `deploy-hk-sbtc-receiver.mjs`, `seed-hk-sbtc-receiver.mjs` (a SIP-010 transfer, not `stx-transfer?`), `execute-sbtc-flash-loan.mjs`. + +--- + +## 5. Testing plan + +- **Static:** `clarinet check` in a stub project (the live sBTC suite is not in the Clarinet project β€” same gap as STX, see [[docs-feedback-report]] M5). Stubs: `sbtc-token` (SIP-010), `flashstack-sbtc-core` (get-fee-basis-points + execute path), Velar `univ2-router` (swap-exact-tokens-for-tokens). +- **Read-only pre-flight:** `estimate-repayment(amount)` (fee math) + a live Velar round-trip quote (sBTCβ†’wSTXβ†’sBTC) to confirm `seed + return β‰₯ owed` before broadcasting β€” the M1 lesson. +- **Dry-run:** build the flash-loan tx with `DRY_RUN=1`, confirm preflight reads (`is-approved-receiver`, reserve). +- **Live:** smallest viable loan (e.g. a few thousand sats, ≀ reserve). Success = `(ok true)` + round-trip event + reserve `+fee`. +- **Negative:** direct call to `execute-sbtc-flash` from a non-core sender must return `ERR-WRONG-CALLER` (proves the caller gate). + +--- + +## 6. Funding, dependencies, and requirements + +| Question | Answer | +|---|---| +| **Is real sBTC required?** | **Yes.** The receiver must hold canonical sBTC to cover fee + Velar round-trip slippage before the first loan. (Unlike M1, we do **not** already hold sBTC.) | +| **Estimated funding** | Deploy ~0.5 STX + tx fees ~0.3 STX (STX-denominated) **plus** an sBTC seed of ~1,000–5,000 sats (covers ~0.6% Velar cost on a small loan + margin). Total < ~$10 equivalent beyond STX already held. | +| **How to source sBTC** | Bridge BTC via the sBTC bridge, **or** buy sBTC on Velar/Bitflow using STX. Has a confirmation delay β€” do it before the execution window. | +| **Whitelist** | Admin-only `add-approved-receiver` on `flashstack-sbtc-core` β€” same dependency on Matt as M1. | +| **Reserve top-up** | Live reserve ~15,010 sats caps the loan. For a non-trivial demo, ask Matt to `deposit-reserve` more sBTC. Confirm desired loan size with him. | +| **External deps** | (1) acquire sBTC, (2) Matt whitelist, (3) Velar pool 70 liquidity, (4) optional reserve top-up. | + +--- + +## 7. Risk assessment + +| Risk | Severity | Mitigation | +|---|---|---| +| Reusing `velar` legs **without** the caller gate | High | ADR-S2 β€” add `contract-caller == core` gate; negative test it | +| Velar univ2 ~0.6% round-trip cost > 5 bps fee β†’ larger seed | Medium (DoS/seed-size only, no loss) | `min-out=u1` + repay-assert; size seed; keep loan small | +| Tiny sBTC reserve (~15k sats) caps loan | Medium (demo scope) | ADR-S7; request reserve top-up from Matt | +| Acquiring real sBTC (bridge/buy delay) | Medium | Sequence sBTC acquisition before deploy/execute | +| sBTC core `set-fee-basis-points` bug (wrong err / no floor) | Low (protocol, not ours) | Flag to Matt; dynamic fee lookup tolerates fee changes | +| SIP-010 transfer post-conditions | Low | `PostConditionMode.Allow` on the execute tx (as M1) | +| Stranded sBTC after partial round-trip | Low | owner-only `rescue-sbtc` | +| Reserve at risk | **None** | Atomic; under-repay reverts the whole tx | + +--- + +## 8. Definition of done (proposed Milestone 2) + +Receiver deployed β†’ whitelisted (`is-approved-receiver β†’ (ok true)`) β†’ seeded with sBTC β†’ `flash-loan(amount)` confirmed `(ok true)` β†’ `sbtc-velar-roundtrip` event + Velar swap events + reserve `+fee` captured β†’ seed recovered β†’ evidence package + docs + Beads closed. + +**Gate before implementation:** approval of this proposal, confirmation of the target loan size / reserve top-up with Matt, and acquisition of the sBTC seed. + +--- + +*Phase C deliverable. Design only β€” implementation deferred pending review. Verified against `contracts/` source and live mainnet reads, 2026-06-06.* diff --git a/sbtc-architecture-review.md b/sbtc-architecture-review.md new file mode 100644 index 0000000..eb81260 --- /dev/null +++ b/sbtc-architecture-review.md @@ -0,0 +1,153 @@ +# FlashStack β€” sBTC Architecture Review + +**Author:** External integrator (HK) +**Date:** 2026-06-06 +**Purpose:** Map the entire canonical-sBTC flash-loan path before designing `hk-sbtc-real-receiver-v1` (Matt request #2). Precursor research only β€” **no implementation**. +**Method:** Verified against `contracts/flashstack-sbtc-core.clar`, `flashstack-sbtc-pool.clar`, `sbtc-flash-receiver-trait.clar`, `sbtc-test-receiver.clar`, `velar-sbtc-arb-receiver.clar`, and live mainnet reads (2026-06-06). + +--- + +## 1. Executive summary + +The sBTC flash-loan path is **structurally identical** to the STX path β€” same "borrow β†’ callback β†’ repay β†’ reserve-grew-by-fee-or-revert" invariant β€” but every STX primitive (`stx-transfer?`, `stx-get-balance`) is replaced by a **SIP-010 cross-contract call** against the canonical sBTC token (`SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token`). The practical consequences for an external receiver are: (1) the receiver repays with `contract-call? sbtc-token transfer …` instead of `stx-transfer?`; (2) the receiver must be seeded with **real canonical sBTC** (a genuine external dependency we did not have in the STX milestone); and (3) the **live reserve is only ~15,010 sats**, which caps loan size far below the advertised 0.1 BTC max. + +A near-complete reference already exists on mainnet: `velar-sbtc-arb-receiver` performs a real sBTCβ†’wSTXβ†’sBTC round-trip on Velar. It is missing the **caller gate** that our `hk-stx-real-receiver-v2` introduced, so the recommended design is `velar round-trip βŠ• v2 caller gate` β€” a direct mirror of the Milestone 1 composition. + +--- + +## 2. Live on-chain state (verified 2026-06-06) + +| Contract | Deployed? | Notes | +|---|---|---| +| `SP20XD46….flashstack-sbtc-core` | βœ… | reserve **15,010 sats**, `total-loans u2`, `total-volume u20000`, fee **5 bps**, max-loan **10,000,000 sats (0.1 BTC)**, paused **false** | +| `SP20XD46….flashstack-sbtc-pool` | βœ… | LP pool; also exposes a flash-loan + collateral oracle | +| `SP20XD46….sbtc-flash-receiver-trait` | βœ… | the interface | +| `SP20XD46….sbtc-test-receiver` | βœ… | whitelisted | +| `SP20XD46….velar-sbtc-arb-receiver` | βœ… | whitelisted | +| `SM3VDXK3…sbtc-token` (canonical sBTC) | βœ… | real mainnet token | + +Reference evidence tx: `0x67f0c77d…f9baa` β†’ `success / (ok true)`, block 7875468. + +> **Note:** this directly **contradicts `docs/TESTING_GUIDE_STX.md:16-18`**, which labels the sBTC path "legacy β€” do not test." The live `flashstack-sbtc-core` under `SP20XD46…` is active. (Logged in [[docs-feedback-report]] as E5.) + +--- + +## 3. Contract interfaces + +### 3.1 `sbtc-flash-receiver-trait` (`sbtc-flash-receiver-trait.clar`) +```clarity +(define-trait sbtc-flash-receiver-trait + ((execute-sbtc-flash (uint principal) (response bool uint)))) +``` +One function. Deployed under the **protocol deployer** `SP20XD46…` (contrast: the STX trait is under the *legacy* `SP3TGRVG7…`). An sBTC receiver declares `(impl-trait 'SP20XD46….sbtc-flash-receiver-trait.sbtc-flash-receiver-trait)`. + +### 3.2 `flashstack-sbtc-core` (`flashstack-sbtc-core.clar`) +- **Error codes** (`:24-31`): `u300 PAUSED`, `u301 ZERO-AMOUNT`, `u302 REPAY-FAILED`, `u303 INSUFFICIENT-RESERVE`, `u304 EXCEEDS-LIMIT`, `u306 NOT-APPROVED`, `u310 NOT-ADMIN`, `u311 TRANSFER-FAILED`. (Note `u300`/`u310` differ from the STX core, where `u300 = NOT-ADMIN`, `u305 = PAUSED`.) +- **State** (`:37-46`): `admin`, `paused`, `fee-basis-points u5`, `max-single-loan u10000000`, `total-loans`, `total-volume`, `total-fees-collected`, `approved-receivers` map. +- **`flash-loan(amount, receiver)`** (`:52-91`): + 1. `reserve-before` = `sbtc-token.get-balance(self)` (cross-contract, `unwrap!` β€” `:60`). + 2. Guards: not paused, amount>0, ≀ max, approved, reserveβ‰₯amount (`:64-68`). + 3. `as-contract (sbtc-token.transfer amount self β†’ receiver none)` (`:71-74`). + 4. `try! (receiver.execute-sbtc-flash amount self)` (`:77`). + 5. `reserve-after` β‰₯ `reserve-before + fee` else `ERR-REPAY-FAILED` (`:80-83`). + 6. Stats; **`total-fees-collected += (reserve-after βˆ’ reserve-before)`** β€” the *actual* surplus, not the nominal fee (`:87`). (STX core counts the nominal `fee`.) +- **Admin:** `deposit-reserve(amount)` (`:97`), `withdraw-reserve(amount, to)` (`:108` β€” **2 args**, vs STX's 1), `add/remove-approved-receiver`, `set-paused`, `set-fee-basis-points`, `set-max-single-loan`, `set-admin` (single-step, `:155`). +- **Read-only:** `get-reserve-balance` (returns the token's `(ok uint)`), `get-fee-basis-points`, `get-max-single-loan`, `get-admin`, `get-stats` (**4 fields only**: total-loans/total-volume/total-fees-collected/paused β€” `:182-189`), `is-approved-receiver` (**`(ok bool)`** β€” `:191-193`), `calculate-fee`. + +> **Latent contract bug (flag to Matt, not our blocker):** `set-fee-basis-points` asserts `(<= new-fee u100)` but returns `ERR-NOT-ADMIN` on violation (`:143`) β€” wrong error constant β€” and has **no lower bound**, so the fee could be set to `0`. The STX core uses `(and (>= u1) (<= u1000)) ERR-INVALID-FEE`, and β€” notably β€” `flashstack-sbtc-pool.clar:177` gets it *right* (`(>= u1)(<= u100) ERR-INVALID-FEE`). The sibling contracts disagree. + +### 3.3 `flashstack-sbtc-pool` (`flashstack-sbtc-pool.clar`) +A **separate** LP-funded contract (error base `u700`) that *also* offers flash loans (`:113-154`) against an LP-deposited sBTC reserve, plus an LP share system and a **collateral oracle** (`get-share-price`, `get-lp-value`, `get-collateral-snapshot` β€” sats/share scaled by `1e8`). So there are **two** sBTC flash-loan sources β€” the admin-reserve `core` and the LP-reserve `pool` β€” each with its own `approved-receivers` whitelist. For an external receiver, the `core` is the documented target. + +--- + +## 4. Flash-loan lifecycle (sBTC) + +``` +caller β†’ flashstack-sbtc-core.flash-loan(amount, receiver) + reserve-before = sbtc-token.get-balance(core) + guards: not paused Β· amount>0 Β· amount ≀ max Β· approved Β· reserve β‰₯ amount + as-contract sbtc-token.transfer amount: core β†’ receiver + β†’ receiver.execute-sbtc-flash(amount, core) [STRATEGY] + (amount sBTC sats now in receiver's token balance) + ... DEX legs / liquidation ... + as-contract sbtc-token.transfer (amount+fee): receiver β†’ core + assert sbtc-token.get-balance(core) β‰₯ reserve-before + fee β†’ (ok true) | revert +``` + +Identical control flow to STX; the only substitutions are the **asset transfer mechanism** (SIP-010 `transfer` with a trailing `none` memo, wrapped in `as-contract`) and the **balance read** (cross-contract `get-balance`, which returns a `response` and must be `unwrap!`-ed). + +--- + +## 5. Comparative analysis β€” STX core vs sBTC core + +### Similarities (reusable as-is) +- The reserve-invariant security model (repayment verified by before/after balance; reserve never at risk; worst case = atomic revert). +- The whitelist gate (`approved-receivers`, admin-only `add-approved-receiver`). +- Dynamic fee = `max(amountΒ·bp/10000, 1)`; default 5 bps. +- The receiver pays the fee from its **own balance** β†’ **seed-before-loan applies equally** (the sBTC seed is real sBTC). +- The callback shape `(uint principal) β†’ (response bool uint)`. + +### Differences (must change for sBTC) +| Aspect | STX core | sBTC core | +|---|---|---| +| Asset primitive | native `stx-transfer?` / `stx-get-balance` | SIP-010 `contract-call? sbtc-token transfer/get-balance` | +| Balance read | cheap, infallible | cross-contract, returns `response` β†’ `unwrap!` | +| Repay call | `(as-contract (stx-transfer? owed tx-sender core))` | `(as-contract (contract-call? SBTC transfer owed tx-sender core none))` | +| Receiver seed asset | STX (we already held it) | **real canonical sBTC** (must acquire) | +| Trait deployer | `SP3TGRVG7…` (legacy) | `SP20XD46…` (protocol) | +| Callback fn | `execute-stx-flash` | `execute-sbtc-flash` | +| Error base | `u300 = NOT-ADMIN`, `u305 = PAUSED` | `u300 = PAUSED`, `u310 = NOT-ADMIN` | +| `get-stats` | 7 fields | 4 fields | +| `is-approved-receiver` | bare `bool` | `(ok bool)` | +| `withdraw-reserve` | `(amount)` | `(amount, to)` | +| Admin transfer | 2-step (`transfer-admin`+`accept-admin`) | 1-step (`set-admin`) | +| Fee accounting | nominal `fee` | actual delta `reserve-after βˆ’ reserve-before` | +| Default max-loan | 500,000 STX | 0.1 BTC (10,000,000 sats) | +| **Live reserve** | ~75 STX | **~15,010 sats** | + +### Reusable components for `hk-sbtc-real-receiver-v1` +- **Our `hk-stx-bitflow-receiver-v1` skeleton**: caller gate (`contract-caller == core` β†’ `ERR-WRONG-CALLER`), dynamic fee lookup, two DEX legs, balance assert, fail-closed repay, owner-only `rescue` + `set-slippage`, read-only `estimate-repayment`. Swap STX primitives β†’ sBTC SIP-010 calls. +- **`velar-sbtc-arb-receiver`** is a near-complete two-leg sBTC round-trip (sBTCβ†’wSTXβ†’sBTC on Velar pool 70) β€” but its `execute-sbtc-flash` is **public with no caller gate** (`velar-sbtc-arb-receiver.clar:47`). Reusing its swap legs while adding the v2 caller gate is the recommended path. + +### New implementation requirements (don't exist in the STX build) +1. **Acquire real canonical sBTC** for the receiver seed β€” the dominant new dependency (see Β§6). +2. Import a SIP-010 trait (for `sbtc-token` transfer/get-balance/get-balance calls) into the receiver. +3. Choose a venue. The only proven on-chain sBTC round-trip in-repo is **Velar univ2** (constant-product, ~0.3%/leg β†’ **~0.6% round-trip cost**) β€” far lossier than Bitflow's stableswap (~0.15% in M1). Seed must be sized to that, or the loan kept small. +4. Handle the cross-contract `get-balance` `response` unwraps (the sBTC core/pool do this; a receiver must too). + +--- + +## 6. Funding & deployment requirements + +- **Real sBTC required:** yes. The receiver must hold canonical sBTC to cover fee + round-trip slippage before the first loan. Minimum protocol fee is 1 sat, but a Velar round-trip on a ~0.6%-cost venue means the seed should cover ~0.6% of the loan plus margin. For a 10,000-sat loan that's ~60–100 sats of seed; for safety, seed ~1,000–5,000 sats. +- **Sourcing sBTC:** bridge BTC via the sBTC bridge, or buy sBTC on a Stacks DEX (Velar/Bitflow) using STX we already hold. Acquiring a few thousand sats is cheap (<$5) but is a real logistics step with a confirmation delay. +- **Reserve ceiling:** the live core reserve is **15,010 sats**, so the maximum borrowable today is ~15k sats regardless of the 0.1 BTC cap (guard `reserve β‰₯ amount`, `:68`). A meaningful demo may need Matt to top up the sBTC reserve via `deposit-reserve`. +- **Deploy cost:** ~0.5 STX (same as the STX receiver; deploy fee is paid in STX). +- **Whitelist:** admin-only `add-approved-receiver` on `flashstack-sbtc-core` β€” same external dependency on Matt as M1. + +--- + +## 7. Risk areas (for the Phase C design) + +| Risk | Severity | Note | +|---|---|---| +| Tiny sBTC reserve (15k sats) caps loan size | Medium (demo scope) | May need Matt to fund reserve for a non-trivial loan | +| Velar univ2 ~0.6% round-trip cost β†’ larger seed | Medium | Pick smallest viable loan; size seed to slippage; `min-out=u1` + repay-assert as in M1 | +| Acquiring real sBTC is an external dependency | Medium | Bridge/buy a few thousand sats before execution | +| `velar-sbtc-arb-receiver` lacks a caller gate | High (if reused verbatim) | Add the v2 `contract-caller == core` gate β€” do not ship without it | +| sBTC core `set-fee-basis-points` wrong error + no lower bound | Low (protocol, not ours) | Flag to Matt; dynamic fee lookup means our receiver tolerates fee changes anyway | +| SIP-010 post-conditions on transfers | Low | Use `PostConditionMode.Allow` in the execute tx, as in M1 | +| Two sBTC flash-loan sources (core vs pool) | Low | Target `core`; don't cross-wire the pool's whitelist | + +--- + +## 8. Open questions for Phase C + +1. **Venue:** stick with Velar pool 70 (proven, whitelisted reference) or evaluate a Bitflow sBTC stableswap pair (lower slippage) if one exists? β€” to verify on-chain in Phase C. +2. **Loan size & reserve:** confirm with Matt whether the sBTC reserve will be topped up, which sets the demo loan size. +3. **Seed source:** bridge vs DEX-buy for the sBTC seed; who funds it. + +--- + +*Verified against `contracts/` source and live mainnet reads on 2026-06-06. Next: [[hk-sbtc-real-receiver-v1-Design]] (Phase C β€” design only).* diff --git a/scripts/deploy-hk-bitflow-receiver.mjs b/scripts/deploy-hk-bitflow-receiver.mjs new file mode 100644 index 0000000..f73b6b2 --- /dev/null +++ b/scripts/deploy-hk-bitflow-receiver.mjs @@ -0,0 +1,148 @@ +/** + * FlashStack -- Deploy hk-stx-bitflow-receiver-v1 to mainnet + * + * External-developer receiver that runs a REAL Bitflow STX->stSTX->STX round-trip + * inside a flash loan and repays principal + fee atomically. + * + * Deploys from OUR own mainnet wallet (NOT the protocol deployer). The admin-only + * whitelist call (`add-approved-receiver` on flashstack-stx-core) must be done by + * Matt separately before the receiver can borrow. + * + * Usage: + * MAINNET_MNEMONIC="word1 ... word24" node scripts/deploy-hk-bitflow-receiver.mjs + * Or, if ./mbegu2 holds the 24-word mnemonic on a single line: + * node scripts/deploy-hk-bitflow-receiver.mjs + * + * Optional env: + * DRY_RUN=1 Build and print the tx fields but do NOT broadcast. + * DEPLOY_FEE_USTX=... Override the deploy fee (default 500_000 microSTX = 0.5 STX). + */ + +import { makeContractDeploy, PostConditionMode, ClarityVersion, privateKeyToAddress } from "@stacks/transactions"; +import networkPkg from "@stacks/network"; +const { STACKS_MAINNET } = networkPkg; +import walletPkg from "@stacks/wallet-sdk"; +const { generateWallet } = walletPkg; +import { readFileSync, existsSync } from "fs"; + +const NAME = "hk-stx-bitflow-receiver-v1"; +const PATH = "contracts/hk-stx-bitflow-receiver-v1.clar"; +const API = "https://api.hiro.so"; +const EXPLORER = "https://explorer.hiro.so/txid"; +const FEE = Number(process.env.DEPLOY_FEE_USTX ?? 500_000); +const DRY_RUN = process.env.DRY_RUN === "1"; +const network = STACKS_MAINNET; + +function loadMnemonic() { + if (process.env.MAINNET_MNEMONIC) return process.env.MAINNET_MNEMONIC.trim(); + if (existsSync("mbegu2")) return readFileSync("mbegu2", "utf8").trim(); + if (existsSync("mbegu")) return readFileSync("mbegu", "utf8").trim(); + throw new Error("Set MAINNET_MNEMONIC env var, or place 24-word mnemonic in ./mbegu2"); +} + +async function getNonce(addr) { + const res = await fetch(`${API}/v2/accounts/${addr}?proof=0`); + return (await res.json()).nonce; +} + +async function getBalance(addr) { + const res = await fetch(`${API}/extended/v1/address/${addr}/balances`); + return BigInt((await res.json()).stx.balance); +} + +async function broadcast(tx) { + const raw = tx.serialize(); + const body = typeof raw === "string" ? Buffer.from(raw.replace(/^0x/, ""), "hex") : raw; + const res = await fetch(`${API}/v2/transactions`, { + method: "POST", headers: { "Content-Type": "application/octet-stream" }, body, + }); + const text = await res.text(); + let data; try { data = JSON.parse(text); } catch { throw new Error(`Non-JSON: ${text.slice(0, 200)}`); } + if (data?.error) throw new Error(`${data.error} -- ${data.reason ?? ""}`); + const txid = typeof data === "string" ? data : data.txid; + if (!txid) throw new Error(`No txid: ${text.slice(0, 200)}`); + return txid; +} + +async function waitForConfirm(txid, label) { + process.stdout.write(` Waiting for "${label}"`); + for (let i = 0; i < 80; i++) { + await new Promise(r => setTimeout(r, 8000)); + const res = await fetch(`${API}/extended/v1/tx/0x${txid}`); + const data = await res.json(); + if (data.tx_status === "success") { console.log(" confirmed."); return; } + if (data.tx_status?.startsWith("abort")) { + console.log(`\n FAILED: ${data.tx_result?.repr ?? "unknown"}`); + throw new Error(`"${label}" failed`); + } + process.stdout.write("."); + } + throw new Error(`Timeout: "${label}"`); +} + +async function main() { + const mnemonic = loadMnemonic(); + const wc = mnemonic.split(/\s+/).length; + if (wc !== 24) throw new Error(`Expected 24-word mnemonic, got ${wc} words`); + + const wallet = await generateWallet({ secretKey: mnemonic, password: "" }); + const pk = wallet.accounts[0].stxPrivateKey; + const sender = privateKeyToAddress(pk, "mainnet"); + const balance = await getBalance(sender); + const nonce = await getNonce(sender); + + console.log("======================================================="); + console.log(" FlashStack -- Deploy hk-stx-bitflow-receiver-v1 "); + console.log("======================================================="); + console.log(` Sender: ${sender}`); + console.log(` Balance: ${Number(balance) / 1e6} STX`); + console.log(` Nonce: ${nonce}`); + console.log(` Fee: ${FEE} microSTX (${FEE / 1e6} STX)`); + console.log(` Contract: ${sender}.${NAME}`); + console.log(` Source: ${PATH}`); + console.log(` Network: mainnet`); + console.log(` Mode: ${DRY_RUN ? "DRY RUN (no broadcast)" : "LIVE BROADCAST"}`); + console.log(); + + if (balance < BigInt(FEE)) { + throw new Error(`Insufficient balance: ${balance} microSTX < fee ${FEE} microSTX`); + } + + const tx = await makeContractDeploy({ + contractName: NAME, + codeBody: readFileSync(PATH, "utf8"), + senderKey: pk, + network, + clarityVersion: ClarityVersion.Clarity3, + postConditionMode: PostConditionMode.Allow, + anchorMode: 1, + fee: FEE, + nonce, + }); + + if (DRY_RUN) { + console.log(" DRY_RUN=1 -- not broadcasting. Set DRY_RUN=0 (or unset) to broadcast."); + console.log(` Built tx OK. Serialized length: ${tx.serialize().length} bytes-ish.`); + return; + } + + const txid = await broadcast(tx); + console.log(` Broadcast: ${txid}`); + console.log(` Explorer: ${EXPLORER}/${txid}?chain=mainnet`); + await waitForConfirm(txid, "deploy hk-stx-bitflow-receiver-v1"); + + console.log(); + console.log("======================================================="); + console.log(" DEPLOYMENT COMPLETE "); + console.log("======================================================="); + console.log(` Contract: ${sender}.${NAME}`); + console.log(` Tx: ${EXPLORER}/${txid}?chain=mainnet`); + console.log(); + console.log(" Next steps:"); + console.log(` 1. Ask Matt to whitelist ${sender}.${NAME}`); + console.log(" (admin-only: add-approved-receiver on flashstack-stx-core)"); + console.log(" 2. Send >=1 STX to the receiver to cover principal + fee + 2x Bitflow pool fee on repay"); + console.log(" 3. Call flashstack-stx-core.flash-loan(u1000000, receiver) ;; 1 STX round-trip"); +} + +main().catch(e => { console.error("\nFAILED:", e.message); process.exit(1); }); diff --git a/scripts/execute-bitflow-flash-loan.mjs b/scripts/execute-bitflow-flash-loan.mjs new file mode 100644 index 0000000..914ee41 --- /dev/null +++ b/scripts/execute-bitflow-flash-loan.mjs @@ -0,0 +1,141 @@ +/** + * FlashStack -- Execute a flash loan through hk-stx-bitflow-receiver-v1 + * + * Calls flashstack-stx-core.flash-loan(amount, receiver) signed by OUR wallet. + * The receiver must already be (a) deployed and (b) whitelisted by Matt, and + * (c) seeded with >= 1 STX to cover the fee + Bitflow pool fees. + * + * Usage: + * MAINNET_MNEMONIC="word1 ... word24" node scripts/execute-bitflow-flash-loan.mjs + * Or with ./mbegu2 (or ./mbegu) holding the 24-word mnemonic: + * node scripts/execute-bitflow-flash-loan.mjs + * + * Optional env: + * AMOUNT_USTX=1000000 Loan size in microSTX (default 1 STX). + * DRY_RUN=1 Build + print, do NOT broadcast. + * TX_FEE_USTX=... Tx fee (default 300_000 microSTX = 0.3 STX). + */ + +import { makeContractCall, PostConditionMode, Cl, privateKeyToAddress } from "@stacks/transactions"; +import networkPkg from "@stacks/network"; +const { STACKS_MAINNET } = networkPkg; +import walletPkg from "@stacks/wallet-sdk"; +const { generateWallet } = walletPkg; +import { readFileSync, existsSync } from "fs"; + +const CORE_ADDR = "SP20XD46NGAX05ZQZDKFYCCX49A3852BQABNP0VG5"; +const CORE_NAME = "flashstack-stx-core"; +const RECV_NAME = "hk-stx-bitflow-receiver-v1"; +const API = "https://api.hiro.so"; +const EXPLORER = "https://explorer.hiro.so/txid"; +const AMOUNT = BigInt(process.env.AMOUNT_USTX ?? 1_000_000); +const TX_FEE = Number(process.env.TX_FEE_USTX ?? 300_000); +const DRY_RUN = process.env.DRY_RUN === "1"; +const network = STACKS_MAINNET; + +function loadMnemonic() { + if (process.env.MAINNET_MNEMONIC) return process.env.MAINNET_MNEMONIC.trim(); + if (existsSync("mbegu2")) return readFileSync("mbegu2", "utf8").trim(); + if (existsSync("mbegu")) return readFileSync("mbegu", "utf8").trim(); + throw new Error("Set MAINNET_MNEMONIC env var, or place 24-word mnemonic in ./mbegu2"); +} + +async function getNonce(addr) { + return (await (await fetch(`${API}/v2/accounts/${addr}?proof=0`)).json()).nonce; +} +async function getBalance(addr) { + return BigInt((await (await fetch(`${API}/extended/v1/address/${addr}/balances`)).json()).stx.balance); +} +async function readBool(path, fn, args, sender) { + const res = await fetch(`${API}/v2/contracts/call-read/${CORE_ADDR}/${CORE_NAME}/${fn}`, { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ sender, arguments: args }), + }); + return res.json(); +} + +async function broadcast(tx) { + const raw = tx.serialize(); + const body = typeof raw === "string" ? Buffer.from(raw.replace(/^0x/, ""), "hex") : raw; + const res = await fetch(`${API}/v2/transactions`, { + method: "POST", headers: { "Content-Type": "application/octet-stream" }, body, + }); + const text = await res.text(); + let data; try { data = JSON.parse(text); } catch { throw new Error(`Non-JSON: ${text.slice(0,200)}`); } + if (data?.error) throw new Error(`${data.error} -- ${data.reason ?? ""}`); + return typeof data === "string" ? data : data.txid; +} + +async function waitForConfirm(txid, label) { + process.stdout.write(` Waiting for "${label}"`); + for (let i = 0; i < 80; i++) { + await new Promise(r => setTimeout(r, 8000)); + const data = await (await fetch(`${API}/extended/v1/tx/0x${txid}`)).json(); + if (data.tx_status === "success") { console.log(` confirmed. result=${data.tx_result?.repr}`); return data; } + if (data.tx_status?.startsWith("abort")) { + console.log(`\n FAILED: ${data.tx_result?.repr ?? "unknown"}`); + throw new Error(`"${label}" failed: ${data.tx_result?.repr}`); + } + process.stdout.write("."); + } + throw new Error(`Timeout: "${label}"`); +} + +async function main() { + const mnemonic = loadMnemonic(); + const wallet = await generateWallet({ secretKey: mnemonic, password: "" }); + const pk = wallet.accounts[0].stxPrivateKey; + const sender = privateKeyToAddress(pk, "mainnet"); + const receiver = `${sender}.${RECV_NAME}`; + const balance = await getBalance(sender); + const nonce = await getNonce(sender); + + console.log("======================================================="); + console.log(" FlashStack -- flash-loan via hk-stx-bitflow-receiver-v1"); + console.log("======================================================="); + console.log(` Sender: ${sender}`); + console.log(` Balance: ${Number(balance) / 1e6} STX`); + console.log(` Nonce: ${nonce}`); + console.log(` Core: ${CORE_ADDR}.${CORE_NAME}`); + console.log(` Receiver: ${receiver}`); + console.log(` Amount: ${AMOUNT} microSTX (${Number(AMOUNT)/1e6} STX)`); + console.log(` Tx fee: ${TX_FEE} microSTX`); + console.log(` Mode: ${DRY_RUN ? "DRY RUN (no broadcast)" : "LIVE BROADCAST"}`); + console.log(); + + // Preflight read-only checks + const approved = await readBool("is-approved-receiver", "is-approved-receiver", + [Cl.serialize(Cl.contractPrincipal(sender, RECV_NAME))], sender).catch(() => null); + console.log(` Preflight is-approved-receiver -> ${JSON.stringify(approved?.result ?? approved)}`); + const stats = await (await fetch(`${API}/v2/contracts/call-read/${CORE_ADDR}/${CORE_NAME}/get-stats`, { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ sender, arguments: [] }) })).json().catch(() => null); + console.log(` Preflight get-stats raw -> ${JSON.stringify(stats?.result ?? stats)?.slice(0,160)}`); + + const tx = await makeContractCall({ + contractAddress: CORE_ADDR, + contractName: CORE_NAME, + functionName: "flash-loan", + functionArgs: [Cl.uint(AMOUNT), Cl.contractPrincipal(sender, RECV_NAME)], + senderKey: pk, + network, + postConditionMode: PostConditionMode.Allow, + anchorMode: 1, + fee: TX_FEE, + nonce, + }); + + if (DRY_RUN) { + console.log("\n DRY_RUN=1 -- not broadcasting. Tx built OK."); + return; + } + + const txid = await broadcast(tx); + console.log(`\n Broadcast: ${txid}`); + console.log(` Explorer: ${EXPLORER}/${txid}?chain=mainnet`); + const res = await waitForConfirm(txid, "flash-loan via bitflow receiver"); + console.log("\n DONE. Save this txid as on-chain evidence."); + console.log(` ${EXPLORER}/${txid}?chain=mainnet`); +} + +main().catch(e => { console.error("\nFAILED:", e.message); process.exit(1); }); diff --git a/scripts/seed-bitflow-receiver.mjs b/scripts/seed-bitflow-receiver.mjs new file mode 100644 index 0000000..f25d830 --- /dev/null +++ b/scripts/seed-bitflow-receiver.mjs @@ -0,0 +1,100 @@ +/** + * FlashStack -- Seed hk-stx-bitflow-receiver-v1 with STX. + * + * The receiver pays the flash-loan fee (and absorbs round-trip slippage) from its + * OWN balance inside the callback, so it must hold STX before the first loan. + * + * Usage: + * node scripts/seed-bitflow-receiver.mjs # reads ./mbegu2 then ./mbegu + * MAINNET_MNEMONIC="w1 ... w24" node scripts/seed-bitflow-receiver.mjs + * + * Optional env: + * SEED_USTX=1000000 Seed size in microSTX (default 1 STX). + * DRY_RUN=1 Build + print, do NOT broadcast. + * TX_FEE_USTX=10000 Tx fee (default 10_000 microSTX = 0.01 STX). + */ +import { makeSTXTokenTransfer, privateKeyToAddress } from "@stacks/transactions"; +import networkPkg from "@stacks/network"; +const { STACKS_MAINNET } = networkPkg; +import walletPkg from "@stacks/wallet-sdk"; +const { generateWallet } = walletPkg; +import { readFileSync, existsSync } from "fs"; + +const RECV_NAME = "hk-stx-bitflow-receiver-v1"; +const API = "https://api.hiro.so"; +const EXPLORER = "https://explorer.hiro.so/txid"; +const SEED = BigInt(process.env.SEED_USTX ?? 1_000_000); +const TX_FEE = Number(process.env.TX_FEE_USTX ?? 10_000); +const DRY_RUN = process.env.DRY_RUN === "1"; +const network = STACKS_MAINNET; + +function loadMnemonic() { + if (process.env.MAINNET_MNEMONIC) return process.env.MAINNET_MNEMONIC.trim(); + if (existsSync("mbegu2")) return readFileSync("mbegu2", "utf8").trim(); + if (existsSync("mbegu")) return readFileSync("mbegu", "utf8").trim(); + throw new Error("Set MAINNET_MNEMONIC env var, or place 24-word mnemonic in ./mbegu2"); +} +async function getNonce(addr) { + return (await (await fetch(`${API}/v2/accounts/${addr}?proof=0`)).json()).nonce; +} +async function getBalance(addr) { + return BigInt((await (await fetch(`${API}/extended/v1/address/${addr}/balances`)).json()).stx.balance); +} +async function broadcast(tx) { + const raw = tx.serialize(); + const body = typeof raw === "string" ? Buffer.from(raw.replace(/^0x/, ""), "hex") : raw; + const res = await fetch(`${API}/v2/transactions`, { + method: "POST", headers: { "Content-Type": "application/octet-stream" }, body }); + const text = await res.text(); + let data; try { data = JSON.parse(text); } catch { throw new Error(`Non-JSON: ${text.slice(0,200)}`); } + if (data?.error) throw new Error(`${data.error} -- ${data.reason ?? ""}`); + return typeof data === "string" ? data : data.txid; +} +async function waitForConfirm(txid, label) { + process.stdout.write(` Waiting for "${label}"`); + for (let i = 0; i < 80; i++) { + await new Promise(r => setTimeout(r, 8000)); + const data = await (await fetch(`${API}/extended/v1/tx/0x${txid}`)).json(); + if (data.tx_status === "success") { console.log(` confirmed. result=${data.tx_result?.repr}`); return data; } + if (data.tx_status?.startsWith("abort")) throw new Error(`"${label}" failed: ${data.tx_result?.repr}`); + process.stdout.write("."); + } + throw new Error(`Timeout: "${label}"`); +} + +async function main() { + const mnemonic = loadMnemonic(); + const wallet = await generateWallet({ secretKey: mnemonic, password: "" }); + const pk = wallet.accounts[0].stxPrivateKey; + const sender = privateKeyToAddress(pk, "mainnet"); + const recipient = `${sender}.${RECV_NAME}`; + const balance = await getBalance(sender); + const nonce = await getNonce(sender); + + console.log("======================================================="); + console.log(" FlashStack -- seed hk-stx-bitflow-receiver-v1"); + console.log("======================================================="); + console.log(` Sender: ${sender}`); + console.log(` Balance: ${Number(balance) / 1e6} STX`); + console.log(` Nonce: ${nonce}`); + console.log(` Recipient: ${recipient}`); + console.log(` Seed: ${SEED} microSTX (${Number(SEED)/1e6} STX)`); + console.log(` Tx fee: ${TX_FEE} microSTX`); + console.log(` Mode: ${DRY_RUN ? "DRY RUN (no broadcast)" : "LIVE BROADCAST"}\n`); + + const tx = await makeSTXTokenTransfer({ + recipient, amount: SEED, senderKey: pk, network, fee: TX_FEE, nonce, + memo: "seed hk-stx-bitflow-receiver-v1", + }); + + if (DRY_RUN) { console.log(" DRY_RUN=1 -- not broadcasting. Tx built OK."); return; } + + const txid = await broadcast(tx); + console.log(` Broadcast: ${txid}`); + console.log(` Explorer: ${EXPLORER}/${txid}?chain=mainnet`); + await waitForConfirm(txid, "seed receiver"); + const recvBal = await getBalance(recipient); + console.log(`\n DONE. Receiver balance now: ${Number(recvBal)/1e6} STX`); + console.log(` Seed txid: ${txid}`); +} +main().catch(e => { console.error("\nFAILED:", e.message); process.exit(1); });