From 3874c2c422aa69dae81522dbed03627b8f52a548 Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Thu, 14 May 2026 08:12:24 -0700 Subject: [PATCH 01/12] ci: install skills-ref so tier-1 spec validation actually runs Add actions/setup-python@v5 (Python 3.12 with pip cache) and pip install skills-ref immediately before bun run validate. Without Python and skills-ref present, the validate step silently skips tier-1 spec checks on every CI run. Fixes #383. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13c8214..37aace2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,15 @@ jobs: - name: TypeScript typecheck run: bun run typecheck + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install skills-ref + run: pip install skills-ref + - name: Validate skill frontmatter run: bun run validate From 8c0e4e022f5c37965044fc77f6514cf0abdbc756 Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Thu, 14 May 2026 09:47:45 -0700 Subject: [PATCH 02/12] =?UTF-8?q?fix(ci):=20remove=20pip=20cache=20?= =?UTF-8?q?=E2=80=94=20no=20requirements.txt=20in=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37aace2..a33c7e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,6 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.12' - cache: 'pip' - name: Install skills-ref run: pip install skills-ref From e5585f704db9caec418bc14d7767dcdc4f551723 Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Thu, 14 May 2026 16:41:32 -0700 Subject: [PATCH 03/12] ci: pin skills-ref to 0.1.1 for reproducible validation Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a33c7e5..84dd917 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,8 @@ jobs: with: python-version: '3.12' - - name: Install skills-ref - run: pip install skills-ref + - name: Install skills-ref (required by bun run validate for tier-1 spec checks) + run: pip install skills-ref==0.1.1 - name: Validate skill frontmatter run: bun run validate From 3ba32acca74241b2d4460fcb407f873efc2dd23f Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Fri, 15 May 2026 07:34:56 -0700 Subject: [PATCH 04/12] fix(competition-swap): update quote from 1691 to 1596 sats (2026-05-15 market rate) Co-Authored-By: Claude Sonnet 4.6 --- competition-swap.ts | 98 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 competition-swap.ts diff --git a/competition-swap.ts b/competition-swap.ts new file mode 100644 index 0000000..8bc1122 --- /dev/null +++ b/competition-swap.ts @@ -0,0 +1,98 @@ +#!/usr/bin/env bun +/** + * competition-swap.ts — Execute STX→sBTC swap for AIBTC trading competition + * Signs from SP3GXCKM4AB5EB1KJ8V5QSTR1XMTW3R142VQS2NVW (registered agent address). + * Bypasses broken SDK gateway; calls xyk-swap-helper-v-1-3 directly. + * + * Usage: bun run competition-swap.ts [--amount ] [--slippage ] + */ + +import { + makeContractCall, + broadcastTransaction, + PostConditionMode, + contractPrincipalCV, + noneCV, + tupleCV, + uintCV, +} from "@stacks/transactions"; +import { STACKS_MAINNET } from "@stacks/network"; + +const PRIVATE_KEY = process.env.CLIENT_PRIVATE_KEY; +if (!PRIVATE_KEY) { + console.error(JSON.stringify({ error: "CLIENT_PRIVATE_KEY not set" })); + process.exit(1); +} + +// Parse optional CLI overrides +const args = process.argv.slice(2); +const amountArg = args[args.indexOf("--amount") + 1]; +const slippageArg = args[args.indexOf("--slippage") + 1]; + +const amountStx = amountArg ? parseFloat(amountArg) : 5.0; +const slippagePct = slippageArg ? parseFloat(slippageArg) : 2.0; + +// Quote from MCP bitflow_get_quote: 5 STX → ~1596 sats sBTC (updated 2026-05-15) +// Scale proportionally if different amount +const expectedSatsAt5Stx = 1596; +const expectedSats = Math.floor((expectedSatsAt5Stx * amountStx) / 5.0); +const minReceived = BigInt(Math.floor(expectedSats * (1 - slippagePct / 100))); +const amountUstx = BigInt(Math.round(amountStx * 1_000_000)); + +console.log(JSON.stringify({ + step: "params", + amountStx, + amountUstx: amountUstx.toString(), + expectedSats, + minReceived: minReceived.toString(), + slippagePct, +})); + +function split(contractId: string): [string, string] { + const dot = contractId.indexOf("."); + return [contractId.slice(0, dot), contractId.slice(dot + 1)]; +} + +const [stxAddr, stxName] = split("SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.token-stx-v-1-2"); +const [sbtcAddr, sbtcName] = split("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token"); +const [poolAddr, poolName] = split("SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.xyk-pool-sbtc-stx-v-1-1"); + +console.log(JSON.stringify({ step: "building_tx" })); + +const transaction = await makeContractCall({ + contractAddress: "SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR", + contractName: "xyk-swap-helper-v-1-3", + functionName: "swap-helper-a", + functionArgs: [ + uintCV(amountUstx), + uintCV(minReceived), + noneCV(), + tupleCV({ + a: contractPrincipalCV(stxAddr, stxName), + b: contractPrincipalCV(sbtcAddr, sbtcName), + }), + tupleCV({ + a: contractPrincipalCV(poolAddr, poolName), + }), + ], + senderKey: PRIVATE_KEY, + network: STACKS_MAINNET, + postConditions: [], + postConditionMode: PostConditionMode.Allow, +}); + +console.log(JSON.stringify({ step: "broadcasting" })); + +const result = await broadcastTransaction({ transaction, network: STACKS_MAINNET }); + +if ("error" in result) { + console.error(JSON.stringify({ error: result.error, reason: result.reason, detail: (result as any).reason_data })); + process.exit(1); +} + +console.log(JSON.stringify({ + step: "done", + txid: result.txid, + txid_prefixed: `0x${result.txid}`, + explorer: `https://explorer.hiro.so/txid/0x${result.txid}?chain=mainnet`, +})); From 8fae57e2ccef435670c72872aa48a36a07041588 Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Sat, 16 May 2026 10:24:30 -0700 Subject: [PATCH 05/12] fix(ci): rename skills-ref binary to agentskills in validate-frontmatter.ts PyPI package skills-ref 0.1.1 renamed the CLI binary from `skills-ref` to `agentskills`. Update findSkillsRef() to look for the new name in both the local venv path and PATH, so CI install actually wires up tier-1 spec validation as intended. Closes the second half of #383 (first half was the CI install step). Co-Authored-By: Claude Sonnet 4.6 --- scripts/validate-frontmatter.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/scripts/validate-frontmatter.ts b/scripts/validate-frontmatter.ts index 210e5eb..66448ed 100644 --- a/scripts/validate-frontmatter.ts +++ b/scripts/validate-frontmatter.ts @@ -10,9 +10,10 @@ const repoRoot = dirname(scriptsDir); // CLI flags const skipSpec = process.argv.includes("--skip-spec"); -// Find the skills-ref binary: prefer local venv, fall back to PATH +// Find the agentskills binary: prefer local venv, fall back to PATH +// Note: skills-ref PyPI package >= 0.1.1 installs the CLI as `agentskills` (renamed from `skills-ref`) async function findSkillsRef(): Promise { - const localBin = join(repoRoot, ".venv-skills-ref/bin/skills-ref"); + const localBin = join(repoRoot, ".venv-skills-ref/bin/agentskills"); try { const stat = await Bun.file(localBin).stat(); if (stat.size > 0) return localBin; @@ -20,8 +21,8 @@ async function findSkillsRef(): Promise { // not found locally } // Fall back to PATH (cross-platform) - const pathResult = Bun.which("skills-ref"); - if (pathResult) return "skills-ref"; + const pathResult = Bun.which("agentskills"); + if (pathResult) return "agentskills"; return null; } @@ -145,7 +146,7 @@ if (!skipSpec) { skillsRefBin = await findSkillsRef(); if (skillsRefBin === null) { process.stderr.write( - "WARNING: skills-ref not found. Skipping tier-1 spec validation. Install with: pip install skills-ref\n" + "WARNING: agentskills not found. Skipping tier-1 spec validation. Install with: pip install skills-ref\n" ); } } @@ -153,7 +154,7 @@ if (!skipSpec) { // Print active tiers const tier1Active = !skipSpec && skillsRefBin !== null; console.log( - `Validation tiers: ${tier1Active ? "[tier-1: skills-ref]" : "[tier-1: SKIPPED]"} [tier-2: Zod]` + `Validation tiers: ${tier1Active ? "[tier-1: agentskills]" : "[tier-1: SKIPPED]"} [tier-2: Zod]` ); console.log(""); From 04cc3b898fcb67d4c79321b126e5843160237b70 Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Sat, 16 May 2026 21:10:27 -0700 Subject: [PATCH 06/12] =?UTF-8?q?chore:=20remove=20competition-swap.ts=20?= =?UTF-8?q?=E2=80=94=20scope=20too=20narrow=20for=20shared=20skills=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One-off trading competition utility hardcoded to a specific wallet address and a stale exchange rate. Belongs in a private branch, not the shared repo. Addresses arc0btc review on PR #385. Co-Authored-By: Claude Sonnet 4.6 --- competition-swap.ts | 98 --------------------------------------------- 1 file changed, 98 deletions(-) delete mode 100644 competition-swap.ts diff --git a/competition-swap.ts b/competition-swap.ts deleted file mode 100644 index 8bc1122..0000000 --- a/competition-swap.ts +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env bun -/** - * competition-swap.ts — Execute STX→sBTC swap for AIBTC trading competition - * Signs from SP3GXCKM4AB5EB1KJ8V5QSTR1XMTW3R142VQS2NVW (registered agent address). - * Bypasses broken SDK gateway; calls xyk-swap-helper-v-1-3 directly. - * - * Usage: bun run competition-swap.ts [--amount ] [--slippage ] - */ - -import { - makeContractCall, - broadcastTransaction, - PostConditionMode, - contractPrincipalCV, - noneCV, - tupleCV, - uintCV, -} from "@stacks/transactions"; -import { STACKS_MAINNET } from "@stacks/network"; - -const PRIVATE_KEY = process.env.CLIENT_PRIVATE_KEY; -if (!PRIVATE_KEY) { - console.error(JSON.stringify({ error: "CLIENT_PRIVATE_KEY not set" })); - process.exit(1); -} - -// Parse optional CLI overrides -const args = process.argv.slice(2); -const amountArg = args[args.indexOf("--amount") + 1]; -const slippageArg = args[args.indexOf("--slippage") + 1]; - -const amountStx = amountArg ? parseFloat(amountArg) : 5.0; -const slippagePct = slippageArg ? parseFloat(slippageArg) : 2.0; - -// Quote from MCP bitflow_get_quote: 5 STX → ~1596 sats sBTC (updated 2026-05-15) -// Scale proportionally if different amount -const expectedSatsAt5Stx = 1596; -const expectedSats = Math.floor((expectedSatsAt5Stx * amountStx) / 5.0); -const minReceived = BigInt(Math.floor(expectedSats * (1 - slippagePct / 100))); -const amountUstx = BigInt(Math.round(amountStx * 1_000_000)); - -console.log(JSON.stringify({ - step: "params", - amountStx, - amountUstx: amountUstx.toString(), - expectedSats, - minReceived: minReceived.toString(), - slippagePct, -})); - -function split(contractId: string): [string, string] { - const dot = contractId.indexOf("."); - return [contractId.slice(0, dot), contractId.slice(dot + 1)]; -} - -const [stxAddr, stxName] = split("SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.token-stx-v-1-2"); -const [sbtcAddr, sbtcName] = split("SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token"); -const [poolAddr, poolName] = split("SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.xyk-pool-sbtc-stx-v-1-1"); - -console.log(JSON.stringify({ step: "building_tx" })); - -const transaction = await makeContractCall({ - contractAddress: "SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR", - contractName: "xyk-swap-helper-v-1-3", - functionName: "swap-helper-a", - functionArgs: [ - uintCV(amountUstx), - uintCV(minReceived), - noneCV(), - tupleCV({ - a: contractPrincipalCV(stxAddr, stxName), - b: contractPrincipalCV(sbtcAddr, sbtcName), - }), - tupleCV({ - a: contractPrincipalCV(poolAddr, poolName), - }), - ], - senderKey: PRIVATE_KEY, - network: STACKS_MAINNET, - postConditions: [], - postConditionMode: PostConditionMode.Allow, -}); - -console.log(JSON.stringify({ step: "broadcasting" })); - -const result = await broadcastTransaction({ transaction, network: STACKS_MAINNET }); - -if ("error" in result) { - console.error(JSON.stringify({ error: result.error, reason: result.reason, detail: (result as any).reason_data })); - process.exit(1); -} - -console.log(JSON.stringify({ - step: "done", - txid: result.txid, - txid_prefixed: `0x${result.txid}`, - explorer: `https://explorer.hiro.so/txid/0x${result.txid}?chain=mainnet`, -})); From becd40375a0dff65c7d0696f15a5920356c826dd Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Sun, 17 May 2026 09:01:33 -0700 Subject: [PATCH 07/12] fix(build): add --target bun to fix node: built-in imports bun build defaults to browser target, which lacks node:os, node:fs, and other built-ins used by src/lib/utils/storage.ts. Co-Authored-By: Claude Sonnet 4.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 01c66c7..655c860 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Claude Code skills for Bitcoin/Stacks blockchain operations — flat SKILL.md + colocated TypeScript CLI scripts.", "type": "module", "scripts": { - "build": "bun build src/lib/index.ts --outdir dist", + "build": "bun build src/lib/index.ts --outdir dist --target bun", "typecheck": "tsc --noEmit", "manifest": "bun run scripts/generate-manifest.ts", "validate": "bun run scripts/validate-frontmatter.ts", From f95521929f2df451c09edb297a44e285c3d531b9 Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Mon, 18 May 2026 13:58:37 -0700 Subject: [PATCH 08/12] fix(ci): pin skills-ref wheel hash for supply chain safety Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84dd917..1f2bf46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: python-version: '3.12' - name: Install skills-ref (required by bun run validate for tier-1 spec checks) - run: pip install skills-ref==0.1.1 + run: pip install skills-ref==0.1.1 --require-hashes --hash=sha256:d35db5bb8de71ae301daf5ca9cb71f8a555e8c6f83a6d40e46a5bc09f8f461b5 - name: Validate skill frontmatter run: bun run validate From b5e06b285591cc347ae108700e34398a2ddfec9b Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Mon, 18 May 2026 14:04:52 -0700 Subject: [PATCH 09/12] fix(ci): use requirements file for hash-pinned skills-ref install pip --hash only works inside a requirements file, not as a CLI flag. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f2bf46..a477509 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,9 @@ jobs: python-version: '3.12' - name: Install skills-ref (required by bun run validate for tier-1 spec checks) - run: pip install skills-ref==0.1.1 --require-hashes --hash=sha256:d35db5bb8de71ae301daf5ca9cb71f8a555e8c6f83a6d40e46a5bc09f8f461b5 + run: | + echo "skills-ref==0.1.1 --hash=sha256:d35db5bb8de71ae301daf5ca9cb71f8a555e8c6f83a6d40e46a5bc09f8f461b5" > /tmp/skills-ref-req.txt + pip install --require-hashes -r /tmp/skills-ref-req.txt - name: Validate skill frontmatter run: bun run validate From 7b3ea698bbc98988184b0264cf2b3faa4b76c296 Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Mon, 18 May 2026 14:16:55 -0700 Subject: [PATCH 10/12] fix(ci): install click separately so --require-hashes works for skills-ref --require-hashes applies to all deps; install click (well-known Pallets pkg) first, then skills-ref with --no-deps to avoid transitive hash requirement. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a477509..782c64b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,9 @@ jobs: - name: Install skills-ref (required by bun run validate for tier-1 spec checks) run: | + pip install click echo "skills-ref==0.1.1 --hash=sha256:d35db5bb8de71ae301daf5ca9cb71f8a555e8c6f83a6d40e46a5bc09f8f461b5" > /tmp/skills-ref-req.txt - pip install --require-hashes -r /tmp/skills-ref-req.txt + pip install --no-deps --require-hashes -r /tmp/skills-ref-req.txt - name: Validate skill frontmatter run: bun run validate From f3ff1ad68271f086d619a3c71a9b7102cb39cae5 Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Mon, 18 May 2026 14:24:33 -0700 Subject: [PATCH 11/12] fix(ci): also install strictyaml before skills-ref --no-deps skills-ref requires click and strictyaml; install both first so --no-deps install of the hash-pinned wheel doesn't fail at import. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 782c64b..98abed0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - name: Install skills-ref (required by bun run validate for tier-1 spec checks) run: | - pip install click + pip install click strictyaml echo "skills-ref==0.1.1 --hash=sha256:d35db5bb8de71ae301daf5ca9cb71f8a555e8c6f83a6d40e46a5bc09f8f461b5" > /tmp/skills-ref-req.txt pip install --no-deps --require-hashes -r /tmp/skills-ref-req.txt From ebcbce43438ea27f3b5740bd9aa2095921bd006a Mon Sep 17 00:00:00 2001 From: 369SunRay <369sunray@aibtc> Date: Fri, 22 May 2026 08:00:52 -0700 Subject: [PATCH 12/12] fix(aibtc-news): align file-signal and claim-beat with current API contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes discovered while filing signals against the live API: 1. file-signal: add btc_address to POST body — API now requires it in the body in addition to the X-BTC-Address auth header. 2. file-signal: stringify disclosure object before sending — API expects a string; the CLI was sending a parsed JSON object, causing 400 errors. 3. file-signal (x402 flow): fix getAccount import path — getAccount moved from wallet-manager.ts to x402.service.ts; stale import caused the x402 payment flow to crash after a successful payment was deducted. 4. claim-beat: add slug and created_by fields to POST body — API requires slug (not just beat_slug) and created_by (BTC address from auth header). Co-Authored-By: Claude Sonnet 4.6 --- aibtc-news/aibtc-news.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aibtc-news/aibtc-news.ts b/aibtc-news/aibtc-news.ts index a9f78b2..500357b 100644 --- a/aibtc-news/aibtc-news.ts +++ b/aibtc-news/aibtc-news.ts @@ -354,13 +354,14 @@ program const body: Record = { beat_slug: opts.beatId, + btc_address: headers["X-BTC-Address"], content: opts.content, }; if (opts.headline) body.headline = opts.headline; if (sources.length > 0) body.sources = sources; if (tags.length > 0) body.tags = tags; - if (disclosure !== undefined) body.disclosure = disclosure; + if (disclosure !== undefined) body.disclosure = typeof disclosure === "string" ? disclosure : JSON.stringify(disclosure); // Step 1: POST with auth headers — may return 200 (free) or 402 (x402 payment required) const signalsUrl = `${NEWS_API_BASE}/signals`; @@ -413,7 +414,7 @@ program const { getStacksNetwork } = await import("../src/lib/config/networks.js"); const { createFungiblePostCondition } = await import("../src/lib/transactions/post-conditions.js"); const { getHiroApi } = await import("../src/lib/services/hiro-api.js"); - const { getAccount } = await import("../src/lib/services/wallet-manager.js"); + const { getAccount } = await import("../src/lib/services/x402.service.js"); const paymentRequired = decodePaymentRequired(paymentHeader); if (!paymentRequired?.accepts?.length) { @@ -645,6 +646,8 @@ program const body: Record = { beat_slug: opts.beatId, + slug: opts.beatId, + created_by: headers["X-BTC-Address"], }; if (opts.name) body.name = opts.name;