From 36cbe5df4edd1fa3d2d60d1effa4895a547eb3d2 Mon Sep 17 00:00:00 2001 From: jayteemoney Date: Sun, 31 May 2026 14:39:13 +0100 Subject: [PATCH 1/3] fix(frontend): label UI with the live network (mainnet), not testnet The app runs on mainnet but several UI surfaces still hardcoded "testnet", telling visitors the wrong thing: - landing CTA copy ("on Stacks testnet") - footer Hiro Explorer link + bottom network link (?chain=testnet) - sidebar network badge ("Testnet") Derive all of these from the existing NETWORK config via a new NETWORK_LABEL constant and the network-aware EXPLORER_BASE, so the site always reflects the chain it is actually running on. --- frontend/src/components/landing/cta-section.tsx | 3 ++- frontend/src/components/landing/footer.tsx | 8 ++++---- frontend/src/components/layout/sidebar.tsx | 4 ++-- frontend/src/lib/constants.ts | 4 ++++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/landing/cta-section.tsx b/frontend/src/components/landing/cta-section.tsx index 5157a20..3ea7273 100644 --- a/frontend/src/components/landing/cta-section.tsx +++ b/frontend/src/components/landing/cta-section.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { motion } from "framer-motion"; import { Button } from "@/components/ui/button"; +import { NETWORK_LABEL } from "@/lib/constants"; import { ArrowRight } from "lucide-react"; export function CTASection() { @@ -20,7 +21,7 @@ export function CTASection() {

Set up your first payment stream and start sending funds in real time - on Stacks testnet. + on Stacks {NETWORK_LABEL.toLowerCase()}.

diff --git a/frontend/src/components/landing/footer.tsx b/frontend/src/components/landing/footer.tsx index c24f39b..bd81876 100644 --- a/frontend/src/components/landing/footer.tsx +++ b/frontend/src/components/landing/footer.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import { Twitter, Github, Linkedin, Zap, ExternalLink } from "lucide-react"; -import { FEEDBACK_URL } from "@/lib/constants"; +import { FEEDBACK_URL, EXPLORER_BASE, NETWORK_LABEL } from "@/lib/constants"; const TelegramIcon = () => ( - Testnet + {NETWORK_LABEL} · diff --git a/frontend/src/components/layout/sidebar.tsx b/frontend/src/components/layout/sidebar.tsx index f5042fe..dd9682f 100644 --- a/frontend/src/components/layout/sidebar.tsx +++ b/frontend/src/components/layout/sidebar.tsx @@ -6,7 +6,7 @@ import { useEffect } from "react"; import { cn } from "@/lib/utils"; import { useBlockHeight } from "@/hooks/use-block-height"; import { useAppStore } from "@/stores/app-store"; -import { FEEDBACK_URL } from "@/lib/constants"; +import { FEEDBACK_URL, NETWORK_LABEL } from "@/lib/constants"; import { LayoutDashboard, PlusCircle, @@ -158,7 +158,7 @@ export function Sidebar() { - Testnet + {NETWORK_LABEL} #{blockHeight.toLocaleString()} diff --git a/frontend/src/lib/constants.ts b/frontend/src/lib/constants.ts index 8710130..15e6fdf 100644 --- a/frontend/src/lib/constants.ts +++ b/frontend/src/lib/constants.ts @@ -33,6 +33,10 @@ export const EXPLORER_BASE = IS_MAINNET ? "https://explorer.hiro.so" : "https://explorer.hiro.so/?chain=testnet"; +// Human-readable network name for UI badges and labels. Derived from NETWORK +// so the site always reflects the chain it is actually running on. +export const NETWORK_LABEL = IS_MAINNET ? "Mainnet" : "Testnet"; + // ============================================================================ // Stream Status Codes (matching smart contract) // ============================================================================ From 94b92fb23941b5b3e432ba79e2a746871de7634d Mon Sep 17 00:00:00 2001 From: jayteemoney Date: Sun, 31 May 2026 14:39:21 +0100 Subject: [PATCH 2/3] fix(frontend): deny-by-default post-conditions on deposit flows create-stream and top-up-stream are the calls where a user's funds leave their wallet, but both used PostConditionMode.Allow. Switch them to PostConditionMode.Deny with the existing explicit-amount post- condition bounding the sender's outflow, matching the deny-by-default exit paths (claim/cancel). The wallet now enforces the exact token and amount leaving the wallet at signing time. --- frontend/src/lib/stacks.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/stacks.ts b/frontend/src/lib/stacks.ts index 81d9b95..f8ebe65 100644 --- a/frontend/src/lib/stacks.ts +++ b/frontend/src/lib/stacks.ts @@ -245,7 +245,11 @@ export function buildCreateStreamTx(params: { contractName: mgrName, functionName: "create-stream", functionArgs, - postConditionMode: PostConditionMode.Allow, + // Deny-by-default: the wallet rejects any token movement not covered by an + // explicit post-condition. create-stream moves the deposit out of the + // sender's wallet, so we bound that outflow at exactly depositAmount — + // matching the deny-by-default exit paths (claim/cancel). + postConditionMode: PostConditionMode.Deny, postConditions: [ Pc.principal(params.senderAddress) .willSendLte(params.depositAmount) @@ -386,7 +390,10 @@ export function buildTopUpStreamTx(params: { principalCV(params.tokenContract), uintCV(params.amount), ], - postConditionMode: PostConditionMode.Allow, + // Deny-by-default: top-up moves additional tokens out of the sender's + // wallet, so we bound that outflow at exactly the top-up amount — + // matching the deny-by-default exit paths (claim/cancel). + postConditionMode: PostConditionMode.Deny, postConditions: [ Pc.principal(params.senderAddress) .willSendLte(params.amount) From 5bc00f021787c16502288672b21bc93ea47fd866 Mon Sep 17 00:00:00 2001 From: jayteemoney Date: Sun, 31 May 2026 14:39:31 +0100 Subject: [PATCH 3/3] test(contracts): fix flaky multi-cycle pause/resume invariant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "status returns to ACTIVE after each resume" property test assumed every pause/resume cycle succeeds. When a random walk pushes the block height onto or past end-block between pause and resume, the contract correctly rejects resume with ERR-STREAM-ENDED (by design, to avoid a zombie ACTIVE state), leaving the stream PAUSED — so the assertion intermittently failed on streams that elapse mid-cycle. Break out of the cycle loop once pause or resume is no longer ok; the invariant only applies while the stream is still live. The deployed contract is unaffected — this was an over-strict test assertion. --- tests/stream-manager.test.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/stream-manager.test.ts b/tests/stream-manager.test.ts index 9c84b58..364bd6a 100644 --- a/tests/stream-manager.test.ts +++ b/tests/stream-manager.test.ts @@ -1408,9 +1408,20 @@ describe("StackStream - Stream Manager Contract", () => { for (let cycle = 0; cycle < 5; cycle++) { simnet.mineEmptyBlocks(Math.max(2, Math.floor(Number(duration) * 0.04))); - simnet.callPublicFn(streamManagerContract, "pause-stream", [Cl.uint(streamId)], wallet1); + + // The contract intentionally rejects pause/resume once the stream has + // elapsed (ERR-STREAM-ENDED) to avoid a zombie ACTIVE state. The + // "returns to ACTIVE" invariant only applies while the stream is still + // live, so stop the cycle once a random walk pushes us past end-block — + // otherwise a resume that lands on/after end-block legitimately leaves + // the stream PAUSED and is not a contract bug. + const pause = simnet.callPublicFn(streamManagerContract, "pause-stream", [Cl.uint(streamId)], wallet1); + if ((pause.result as any).type !== "ok") break; + simnet.mineEmptyBlocks(2); - simnet.callPublicFn(streamManagerContract, "resume-stream", [Cl.uint(streamId)], wallet1); + + const resume = simnet.callPublicFn(streamManagerContract, "resume-stream", [Cl.uint(streamId)], wallet1); + if ((resume.result as any).type !== "ok") break; const status = (simnet.callReadOnlyFn(streamManagerContract, "get-stream-status", [Cl.uint(streamId)], deployer).result as any).value.value as bigint; expect(status).toBe(0n); // STATUS-ACTIVE