Documentation

Documentation

Everything you need to build next-generation Stacks blockchain applications. From initial setup to production deployment.

01Getting Started

Create Stacks App provides the fastest pipeline for scaffolding full-stack Stacks applications. A single command sets up your entire project with your choice of frontend framework, smart contract templates, and testing infrastructure powered by Clarinet and Vitest.

Terminal
npx @devvmichael/create-stacks-app my-dapp

The CLI will walk you through selecting a frontend framework, which contracts to include, whether to use TypeScript and Tailwind CSS, and your preferred package manager.

Non-interactive mode: Skip prompts with the -y flag to use all defaults (Next.js + Counter contract + TypeScript + Tailwind):

Quick Mode
npx @devvmichael/create-stacks-app my-dapp -y

02Prerequisites

Before getting started, make sure you have the following tools installed on your machine:

Node.js ≥ 18

Required for running the CLI and frontend dev server. Download from nodejs.org.

Clarinet

Hiro's development tool for Clarity smart contracts. Required for contract testing and deployment.

npm / pnpm / yarn

Any Node.js package manager. The CLI will let you choose during scaffolding. pnpm is recommended.

Git

Version control for your project. The CLI can optionally initialize a git repo during scaffolding.

💡 Clarinet Installation

Install Clarinet via Homebrew on macOS: brew install clarinet or follow the official installation guide.

03Quick Start

After scaffolding your project, follow these steps to start developing:

Step 1 — Navigate
cd my-dapp
Step 2 — Install Dependencies
npm install
Step 3 — Start Frontend
# Start the frontend dev server
cd frontend
npm run dev
Step 4 — Start Clarinet Devnet (separate terminal)
# From project root
cd contracts
clarinet devnet start

Your frontend will be available at http://localhost:3000 (Next.js) or http://localhost:5173 (React + Vite).

04Architecture Overview

Projects are scaffolded with a clean separation between your frontend framework and Clarity smart contracts. Each part can be developed, tested, and deployed independently.

Project Tree
my-dapp/
├── contracts/                # Clarity smart contracts
│   ├── contracts/            # .clar contract files
│   │   └── counter.clar      # Selected contract template(s)
│   ├── tests/                # Vitest + Clarinet SDK tests
│   │   └── counter.test.ts
│   ├── Clarinet.toml         # Clarinet configuration
│   ├── settings/
│   │   ├── Devnet.toml       # Devnet wallet/settings
│   │   └── Simnet.toml       # Simnet settings (for tests)
│   ├── deployments/
│   │   └── default.simnet-plan.yaml
│   └── package.json          # Test runner scripts
├── frontend/                 # Your chosen framework
│   ├── app/                  # Pages & routes (Next.js)
│   ├── components/           # UI components
│   ├── hooks/                # Stacks Connect v8 hooks
│   │   ├── use-stacks.ts     # Wallet connection
│   │   ├── use-contract-read.ts  # Read-only calls
│   │   └── use-contract-call.ts  # Public function calls
│   ├── lib/
│   │   ├── stacks.ts         # Core Stacks utilities
│   │   └── contracts.ts      # Contract config definitions
│   ├── .env                  # Network config (devnet default)
│   ├── .env.example          # Env template
│   └── package.json
├── .editorconfig
├── .prettierrc
├── .gitignore
└── README.md

05Smart Contracts

Smart contracts are written in Clarity, a decidable language for the Stacks blockchain. Each template includes a fully tested contract.

contracts/contracts/counter.clar
;; Counter Contract
;; A simple contract demonstrating state management in Clarity

;; Data variables
(define-data-var counter uint u0)
(define-data-var owner principal tx-sender)

;; Error codes
(define-constant ERR-NOT-OWNER (err u403))

;; Increment the counter by 1
(define-public (increment)
  (ok (var-set counter (+ (var-get counter) u1))))

;; Decrement the counter by 1
(define-public (decrement)
  (let ((current (var-get counter)))
    (asserts! (> current u0) (err u400))
    (ok (var-set counter (- current u1)))))

;; Reset the counter to 0 (owner only)
(define-public (reset)
  (begin
    (asserts! (is-eq tx-sender (var-get owner)) ERR-NOT-OWNER)
    (ok (var-set counter u0))))

;; Set the counter to a specific value (owner only)
(define-public (set-counter (value uint))
  (begin
    (asserts! (is-eq tx-sender (var-get owner)) ERR-NOT-OWNER)
    (ok (var-set counter value))))

;; Read-only functions
(define-read-only (get-counter)
  (ok (var-get counter)))

(define-read-only (get-owner)
  (ok (var-get owner)))

06Frontend Frameworks

Choose from two frontend frameworks during scaffolding. Both come pre-configured with @stacks/connect v8, TypeScript, and Tailwind CSS v4.

Next.js (Recommended)

Next.js 15 with App Router, React 19, server components. Includes dedicated hooks/ and lib/ directories with full Stacks integration.

React + Vite

React 19 SPA with Vite 6 and HMR. All Stacks logic is self-contained in App.tsx with inline connection management.

💡 The Next.js template extracts Stacks logic into reusable hooks ( useStacks, useContractRead, useContractCall) and a shared lib/stacks.ts utility library. The React template keeps everything inline for simplicity.

07Stacks Connect v8 Hooks

The Next.js template includes three pre-built hooks that wrap @stacks/connect v8 APIs. These hooks use a shared lib/stacks.ts utility library that handles wallet connection, read-only calls via the Hiro API, and contract calls via the request() method.

useStacks() — Wallet Connection

Manages wallet state using the v8 connect() API. Supports both Leather and Xverse wallets. The hook persists connection state in localStorage via isConnected() and getLocalStorage().

hooks/use-stacks.ts
"use client";

import { useState, useEffect, useCallback } from "react";
import {
  connectWallet,
  disconnectWallet,
  checkIsConnected,
  getConnectedAddress,
} from "@/lib/stacks";

export function useStacks() {
  const [isConnected, setIsConnected] = useState(false);
  const [address, setAddress] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);

  // Check connection on mount
  useEffect(() => {
    const connected = checkIsConnected();
    setIsConnected(connected);
    if (connected) {
      setAddress(getConnectedAddress());
    }
  }, []);

  const handleConnect = useCallback(async () => {
    setIsLoading(true);
    try {
      const result = await connectWallet();
      // v8 connect() returns { addresses: AddressEntry[] }
      const stxAddress =
        result.addresses?.find((a) => a.symbol === "STX")?.address ||
        result.addresses?.[0]?.address ||
        null;
      setAddress(stxAddress);
      setIsConnected(true);
    } catch (error) {
      console.error("Failed to connect wallet:", error);
    } finally {
      setIsLoading(false);
    }
  }, []);

  const handleDisconnect = useCallback(() => {
    disconnectWallet();
    setAddress(null);
    setIsConnected(false);
  }, []);

  return { isConnected, address, isLoading,
    connect: handleConnect, disconnect: handleDisconnect };
}

useContractRead() — Read-Only Calls

Reads contract state via the Hiro API (/v2/contracts/call-read/). No wallet connection required. Uses the ContractConfig type from lib/contracts.ts.

hooks/use-contract-read.ts
"use client";

import { useEffect, useState, useCallback } from "react";
import { callReadOnly } from "@/lib/stacks";
import type { ContractConfig } from "@/lib/contracts";

export function useContractRead(
  contract: ContractConfig,
  functionName: string,
  functionArgs: string[] = [],
) {
  const [data, setData] = useState<any>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = useCallback(async () => {
    setIsLoading(true);
    try {
      const result = await callReadOnly({
        contractAddress: contract.address,
        contractName: contract.name,
        functionName,
        functionArgs,
        senderAddress: contract.address,
      });
      setData(result);
    } catch (err) {
      setError(err as Error);
    } finally {
      setIsLoading(false);
    }
  }, [contract.address, contract.name, functionName]);

  useEffect(() => { fetchData(); }, [fetchData]);

  return { data, isLoading, error, refetch: fetchData };
}

useContractCall() — Public Function Calls

Calls public contract functions through the user's wallet using the v8 request("stx_callContract") method. Returns the transaction ID on success.

hooks/use-contract-call.ts
"use client";

import { useState } from "react";
import { callContract } from "@/lib/stacks";
import { useStacks } from "./use-stacks";
import type { ContractConfig } from "@/lib/contracts";

export function useContractCall(
  contract: ContractConfig,
  functionName: string,
) {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const [txId, setTxId] = useState<string | null>(null);
  const { address } = useStacks();

  const call = async (functionArgs: string[] = []) => {
    if (!address) throw new Error("Wallet not connected");
    setIsLoading(true);
    try {
      // Uses @stacks/connect v8 request("stx_callContract", params)
      const result = await callContract({
        contractAddress: contract.address,
        contractName: contract.name,
        functionName,
        functionArgs,
        network: contract.network,
      });
      if (result?.txid) {
        setTxId(result.txid);
      }
      return result;
    } catch (err) {
      setError(err as Error);
      throw err;
    } finally {
      setIsLoading(false);
    }
  };

  return { call, isLoading, error, txId };
}

sBTC Native Hooks (v0.2.43+)

v0.2.43 introduces native support for sBTC, the trustless Bitcoin-backed asset on Stacks. These hooks handle everything from balance lookups to complex Bitcoin peg-in (deposit) flows.

useSbtcBalance() — Token Balance

A specialized utility to fetch the sBTC balance for any Stacks address. It automatically targets the official sBTC contract on the selected network.

hooks/use-sbtc-balance.ts
const { balance, isLoading, error } = useSbtcBalance(address);

useSbtcDeposit() — Peg-in Flow

Provides a depositSbtc function that orchestrates the entire peg-in process: generating the P2TR address, signing the BTC transaction via the wallet, and notifying the sBTC signers.

hooks/use-sbtc-deposit.ts
const { depositSbtc, isLoading, txId } = useSbtcDeposit();

// Usage
await depositSbtc({
  amountSats: 100000, // 0.001 BTC
  stacksAddress: "ST123...",
});

08Contract Configuration

Contracts are configured in lib/contracts.ts using the ContractConfig interface. The address defaults to the Clarinet devnet deployer.

lib/contracts.ts
import { networkName } from "./stacks";

export interface ContractConfig {
  address: string;
  name: string;
  network: string;
}

const contractAddress =
  process.env.NEXT_PUBLIC_CONTRACT_ADDRESS ||
  "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM";

export const counterContract: ContractConfig = {
  address: contractAddress,
  name: "counter",
  network: networkName,
};

export const tokenContract: ContractConfig = {
  address: contractAddress,
  name: "token",
  network: networkName,
};

export const nftContract: ContractConfig = {
  address: contractAddress,
  name: "nft",
  network: networkName,
};

Use these configs with the hooks:

Example Usage
import { useContractRead } from "@/hooks/use-contract-read";
import { useContractCall } from "@/hooks/use-contract-call";
import { counterContract } from "@/lib/contracts";

// Read the counter value
const { data, isLoading } = useContractRead(counterContract, "get-counter");

// Call increment
const { call, txId } = useContractCall(counterContract, "increment");
await call(); // Opens wallet for signing

09Testing

Contracts are tested using Vitest with the Clarinet SDK. The global simnet object is injected by the Clarinet test runner and provides methods for simulating blockchain interactions.

Run Contract Tests
cd contracts
npm test
contracts/tests/counter.test.ts
import { describe, expect, it } from "vitest";
import { Cl } from "@stacks/transactions";

const accounts = simnet.getAccounts();
const deployer = accounts.get("deployer")!;
const wallet1 = accounts.get("wallet_1")!;

describe("Counter Contract", () => {
  it("should start at zero", () => {
    const result = simnet.callReadOnlyFn(
      "counter", "get-counter", [], deployer
    );
    expect(result.result).toBeOk(Cl.uint(0));
  });

  it("should increment counter by 1", () => {
    const block = simnet.callPublicFn(
      "counter", "increment", [], deployer
    );
    expect(block.result).toBeOk(Cl.bool(true));

    const result = simnet.callReadOnlyFn(
      "counter", "get-counter", [], deployer
    );
    expect(result.result).toBeOk(Cl.uint(1));
  });

  it("should prevent non-owner from resetting", () => {
    const block = simnet.callPublicFn(
      "counter", "reset", [], wallet1
    );
    expect(block.result).toBeErr(Cl.uint(403));
  });
});

10Deployment

There are two ways to deploy contracts: using the built-in CLI deploy command, or using Clarinet directly.

CLI Deploy Command

Deploy via CLI
# Deploy to testnet
npx @devvmichael/create-stacks-app deploy testnet

# Deploy to mainnet
npx @devvmichael/create-stacks-app deploy mainnet

# With a specific private key
npx @devvmichael/create-stacks-app deploy testnet --private-key <key>

Clarinet Devnet

Start Local Devnet
cd contracts
clarinet devnet start

11Network Configuration

The scaffolded frontend reads the network from environment variables. The lib/stacks.ts utility automatically resolves the correct API URL for each network.

Devnet

Local blockchain via Clarinet. API at localhost:3999. Free STX, instant blocks.

Testnet

Public test network via api.testnet.hiro.so. Free testnet STX available from faucet.

Mainnet

Production network via api.mainnet.hiro.so. Real STX required for transactions.

.env (Next.js)
# Network: devnet | testnet | mainnet
NEXT_PUBLIC_NETWORK=devnet

# Contract deployer address (default: Clarinet devnet deployer)
NEXT_PUBLIC_CONTRACT_ADDRESS=ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
.env (React + Vite)
# Network: devnet | testnet | mainnet
VITE_NETWORK=devnet

# Contract deployer address
VITE_CONTRACT_ADDRESS=ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM

12CLI Commands

The CLI provides three commands for the full development lifecycle.

create (default)

Scaffold a new project with an interactive wizard.

Create Command
npx @devvmichael/create-stacks-app [project-name] [options]

Options:
  -t, --template <name>      Frontend template (nextjs, react)
  -c, --contracts <list>     Contracts to include (counter,token,nft)
  --typescript / --no-typescript
  --tailwind / --no-tailwind
  --no-git                   Skip Git initialization
  --package-manager <pm>     npm, pnpm, or yarn
  --skip-install             Skip dependency installation
  -y, --yes                  Skip prompts, use defaults

add

Add a contract or component to an existing project.

Add Command
npx @devvmichael/create-stacks-app add <type> <name>

Options:
  --sip010        Create a SIP-010 fungible token contract
  --sip009        Create a SIP-009 NFT contract
  -t, --template  Use a specific template (marketplace, defi)

Examples:
  npx @devvmichael/create-stacks-app add contract my-token --sip010
  npx @devvmichael/create-stacks-app add contract my-nft --sip009

deploy

Deploy contracts to testnet or mainnet.

Deploy Command
npx @devvmichael/create-stacks-app deploy <network>

Options:
  --private-key <key>  Private key (or set STACKS_PRIVATE_KEY env var)

Examples:
  npx @devvmichael/create-stacks-app deploy testnet
  npx @devvmichael/create-stacks-app deploy mainnet --private-key <key>

13Available Templates

Choose from five contract templates during scaffolding. You can select multiple contracts at once.

Counter (default)

Simple state management with owner-gated reset. Demonstrates data variables, error handling, and access control.

SIP-010 Token

Full fungible token standard. Includes mint, burn, transfer, and balance queries following the SIP-010 specification.

SIP-009 NFT

Non-fungible token standard. Minting, metadata, transfer, and ownership queries following SIP-009.

Staking Pool (DeFi)

DeFi staking example with a SIP-010 trait dependency. Demonstrates multi-contract architecture.

NFT Marketplace

Trading marketplace with listing, buying, and selling. Includes NFT trait and a companion SIP-009 contract.

14Contributing

Want to contribute a new template or improve the CLI? Check out the contributing guide for instructions on how to add new contract templates, frontend configurations, or CLI features.

Ready to start building?

Scaffold your first Stacks dApp in seconds and join the growing ecosystem of Stacks developers.