Skip to content
Merged
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
3 changes: 2 additions & 1 deletion frontend/src/components/landing/cta-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -20,7 +21,7 @@ export function CTASection() {
</h2>
<p className="mt-4 text-zinc-400 max-w-md mx-auto">
Set up your first payment stream and start sending funds in real time
on Stacks testnet.
on Stacks {NETWORK_LABEL.toLowerCase()}.
</p>
<div className="mt-8 flex flex-col sm:flex-row items-center justify-center gap-4">
<Link href="/dashboard/create">
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/landing/footer.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<svg
Expand Down Expand Up @@ -66,7 +66,7 @@ const footerLinks = [
},
{
label: "Hiro Explorer",
href: "https://explorer.hiro.so/?chain=testnet",
href: EXPLORER_BASE,
external: true,
},
],
Expand Down Expand Up @@ -176,12 +176,12 @@ export function Footer() {

<div className="flex items-center gap-4 text-xs text-zinc-600">
<a
href="https://explorer.hiro.so/?chain=testnet"
href={EXPLORER_BASE}
target="_blank"
rel="noopener noreferrer"
className="transition-colors hover:text-zinc-400"
>
Testnet
{NETWORK_LABEL}
</a>
<span className="text-zinc-800">·</span>
<span className="flex items-center gap-1.5">
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -158,7 +158,7 @@ export function Sidebar() {
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
</span>
<span>Testnet</span>
<span>{NETWORK_LABEL}</span>
<span className="ml-auto font-mono tabular-nums text-zinc-500">
#{blockHeight.toLocaleString()}
</span>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
// ============================================================================
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/lib/stacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
15 changes: 13 additions & 2 deletions tests/stream-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading