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.
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):
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:
cd my-dapp
npm install
# Start the frontend dev server cd frontend npm run dev
# 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.
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.
;; 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().
"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.
"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.
"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.
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.
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.
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:
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 signing09Testing
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.
cd contracts npm test
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 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
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.
# Network: devnet | testnet | mainnet NEXT_PUBLIC_NETWORK=devnet # Contract deployer address (default: Clarinet devnet deployer) NEXT_PUBLIC_CONTRACT_ADDRESS=ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
# 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.
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.
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.
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.