Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions aibtc-news/aibtc-news.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,13 +354,14 @@ program

const body: Record<string, unknown> = {
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`;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -645,6 +646,8 @@ program

const body: Record<string, unknown> = {
beat_slug: opts.beatId,
slug: opts.beatId,
created_by: headers["X-BTC-Address"],
};

if (opts.name) body.name = opts.name;
Expand Down
20 changes: 18 additions & 2 deletions stacks-alpha-engine/stacks-alpha-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,23 @@ import { Command } from "commander";
import { homedir } from "os";
import { join } from "path";
import { readFileSync, writeFileSync } from "fs";
import * as ecc from "tiny-secp256k1";
import { secp256k1 as _secp } from "@noble/curves/secp256k1.js";

function xOnlyPointAddTweak(
p: Uint8Array,
tweak: Uint8Array,
): { xOnlyPubkey: Uint8Array; parity: 0 | 1 } | null {
const tweakN = BigInt("0x" + Buffer.from(tweak).toString("hex"));
if (tweakN >= _secp.CURVE.n) return null;
const point = _secp.ProjectivePoint.fromHex("02" + Buffer.from(p).toString("hex"));
const result = point.add(_secp.ProjectivePoint.BASE.multiply(tweakN));
if (result.equals(_secp.ProjectivePoint.ZERO)) return null;
const aff = result.toAffine();
const xHex = aff.x.toString(16).padStart(64, "0");
const xOnly = new Uint8Array(32);
for (let i = 0; i < 32; i++) xOnly[i] = parseInt(xHex.slice(i * 2, i * 2 + 2), 16);
return { xOnlyPubkey: xOnly, parity: (aff.y % 2n === 0n ? 0 : 1) as 0 | 1 };
}

// == Constants ================================================================
const FETCH_TIMEOUT_MS = 30_000;
Expand Down Expand Up @@ -380,7 +396,7 @@ function xOnlyPubkeyToP2TR(xHex: string): string {
if (xHex.length !== 64) throw new Error(`Expected 32-byte x-only pubkey, got ${xHex.length / 2} bytes`);
const xBytes = Buffer.from(xHex, "hex");
const tweak = tapTaggedHash("TapTweak", xBytes);
const tweaked = ecc.xOnlyPointAddTweak(xBytes, tweak);
const tweaked = xOnlyPointAddTweak(xBytes, tweak);
if (!tweaked) throw new Error("Taproot key tweak failed");
return bech32mEncode("bc", [1, ...convertBits(tweaked.xOnlyPubkey, 8, 5)]);
}
Expand Down
Loading