Hello Stacks - Your First Clarity 3.0 Smart Contract

Gbolahan AkandeGbolahan Akande
7 min read

Welcome to your 30-day journey mastering the Stacks ecosystem! Today, we're learning the fundamentals of Clarity 3.0 - the latest version of Stacks' smart contract language, and how it differs from previous versions.

What You'll Learn Today

By the end of this tutorial, you'll understand:

  • How to set up a modern Clarity 3.0 development environment

  • Key differences between Clarity 2.0 and 3.0 syntax

  • Why stacks-block-height replaced block-height

  • Basic smart contract structure and best practices

  • How to test and deploy your first contract

Before We Start

This tutorial assumes basic programming knowledge in any language. We're using the absolute latest versions of all tools as of 2025, so everything will be modern and follow current best practices.

Understanding Clarity 3.0 Changes

Before diving into code, let's understand why Clarity 3.0 exists and what changed.

Why Clarity 3.0?

With the Nakamoto upgrade, Stacks blocks now occur much more frequently. Many existing contracts relied on block-height to approximate time, assuming each block took ~10 minutes. Clarity 3.0 introduces new primitives to handle this change while preserving backward compatibility.

Key Changes in Clarity 3.0

Block Height Functions:

  • block-height (deprecated in Clarity 3.0)

  • stacks-block-height (fast Stacks blocks)

  • tenure-height (slower, ~10 minute intervals like old blocks)

Why This Matters:

;; Clarity 2.0 way (deprecated in 3.0)
(define-read-only (get-old-time)
  (* block-height u600)) ;; Won't work in Clarity 3.0!

;; Clarity 3.0 way
(define-read-only (get-approximate-time)
  (* tenure-height u600)) ;; Maintains ~10 min assumption

;; Or use fast blocks for precise block counting
(define-read-only (get-precise-block)
  stacks-block-height)

Setting Up Your Development Environment

Step 1: Install Clarinet (Latest Version)

The modern way to install Clarinet varies by platform. Current Clarinet versions default to Clarity 3.0 and Epoch 3.0.

For macOS:

brew install clarinet

For Windows:

# Using Winget (recommended for 2025)
winget install clarinet

Install from a pre-built binary

# note: you can change v3.2.0 with the latest version available on the releases page.
wget -nv https://github.com/hirosystems/clarinet/releases/download/v3.2.0/clarinet-linux-x64-glibc.tar.gz -O clarinet-linux-x64.tar.gz
tar -xf clarinet-linux-x64.tar.gz
chmod +x ./clarinet
mv ./clarinet /usr/local/bin

If you have an issue at all, go to the clarity book
Read the clarity book installation guide: https://book.clarity-lang.org/ch01-01-installing-tools.html

Verify your installation:

clarinet --version
# Should show 3.0 or newer

Step 2: Understanding Project Structure

Create a new project to see the modern structure:

clarinet new hello-stacks-tutorial
cd hello-stacks-tutorial

What you get:

hello-stacks-tutorial/
├── Clarinet.toml          # Project configuration
├── contracts/             # Your .clar smart contracts
├── tests/                 # TypeScript test files
└── settings/              # Network configurations (testnet, mainnet)

Key Change: Tests are now in TypeScript and run with npm run test, not clarinet test.

Writing Your First Clarity 3.0 Contract

Step 1: Create the Contract

clarinet contract new hello-world

This creates contracts/hello-world.clar and tests/hello-world_test.ts.

Step 2: Understanding Modern Clarity Structure

Let's build a simple but educational contract that demonstrates Clarity 3.0 features:

;; Hello World - Clarity 3.0 Example
;; This contract demonstrates modern Clarity patterns

;; Storage - where our data lives on the blockchain
(define-data-var greeting (string-ascii 50) "Hello, Clarity 3.0!")
(define-data-var owner principal tx-sender)

;; Constants - error codes are a best practice
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-TOO-LONG (err u101))

;; Read-only function - doesn't cost gas to call
(define-read-only (get-greeting)
  (var-get greeting)
)

;; Public function - costs gas and can modify state
(define-public (set-greeting (new-greeting (string-ascii 50)))
  (begin
    ;; Check if caller is authorized
    (asserts! (is-eq tx-sender (var-get owner)) ERR-NOT-AUTHORIZED)

    ;; Validate input length
    (asserts! (<= (len new-greeting) u50) ERR-TOO-LONG)

    ;; Update the greeting
    (var-set greeting new-greeting)

    ;; Return success with additional data
    (ok {
      message: "Greeting updated!",
      block: stacks-block-height,  ;; Clarity 3.0!
      tenure: tenure-height         ;; Clarity 3.0!
    })
  )
)

;; Demonstrating the difference between block types
(define-read-only (get-block-info)
  {
    stacks-blocks: stacks-block-height,  ;; Fast blocks
    tenure-blocks: tenure-height,        ;; ~10 min intervals
    estimated-time: (* tenure-height u600) ;; Approximate timestamp
  }
)

Step 3: Key Concepts Explained

Data Variables:

  • Store mutable data on the blockchain

  • Type-safe: once you declare (string-ascii 50), it can't be changed

  • Persist between function calls

Constants:

  • Immutable values defined at contract deployment

  • Use for error codes, limits, addresses

Assertions:

  • asserts! validates conditions and returns an error if false

  • Essential for security and user feedback

Return Values:

  • (ok ...) for success

  • (err ...) for errors

  • Functions must return consistent types

Testing Your Contract (Modern Way)

Step 1: Install Dependencies

In your project directory:

bash

npm install

This installs the testing framework that works with modern Clarinet.

Step 2: Understanding Test Structure

Look at tests/hello-world_test.ts. Modern tests use TypeScript:

import { describe, it, expect } from 'vitest';
import { Cl } from '@stacks/transactions';

// Get test accounts from simnet
const accounts = simnet.getAccounts();
const deployer = accounts.get('deployer')!;

describe('Hello World Contract', () => {
  // Test the default greeting message
  it('returns the default greeting', () => {
    const greeting = simnet.callReadOnlyFn(
      'hello-world',      // contract name
      'get-greeting',     // function to call
      [],                 // no arguments
      deployer            // caller identity
    );

    // Check that the result matches the expected string
    expect(greeting.result).toBeAscii('Hello, Clarity 3.0!');
  });

  // Test who the contract owner is
  it('returns the contract owner', () => {
    const owner = simnet.callReadOnlyFn(
      'hello-world',
      'get-owner',
      [],
      deployer
    );

    // Check that the owner is the deployer
    expect(owner.result).toBePrincipal(deployer);
  });
});

Step 3: Run Tests (Updated Command)

bash

# Modern way - NOT clarinet test
npm run test

Why the change?

  • Better integration with modern JavaScript tooling

  • Supports TypeScript out of the box

  • More familiar to web developers

  • Faster test execution

Deploying to Testnet

Step 1: Generate Configuration

bash

clarinet deployment generate --testnet

This creates deployment plans in your deployments/ folder.

Step 2: Get Test STX

Visit the Stacks Testnet Faucet and request test STX for your address.

Step 3: Deploy

bash

clarinet deployment apply --testnet

What happens:

  1. Clarinet reads your deployment plan

  2. Submits your contract to the testnet

  3. Returns a contract address like ST123...ABC.hello-world

Interacting with Your Contract

Using Clarinet Console

bash

clarinet console --testnet

Try these commands:

clarity

;; Read data (free)
(contract-call? .hello-world get-greeting)

;; See Clarity 3.0 block info
(contract-call? .hello-world get-block-info)

;; Try to update greeting (might fail if not owner)
(contract-call? .hello-world set-greeting "Hello, Testnet!")

Using Stacks Explorer

  1. Go to explorer.stacks.co

  2. Search for your contract address

  3. Explore the functions and call them through the UI

Key Takeaways

What You Learned:

  • Clarity 3.0 uses stacks-block-height and tenure-height instead of block-height

  • Modern testing uses npm run test, not clarinet test

  • Type safety is enforced throughout Clarity

  • Error handling uses constants and assertions

  • Smart contracts store data permanently on the blockchain

Modern Development Flow:

  1. clarinet new → Create project

  2. clarinet contract new → Add contracts

  3. npm run test → Test contracts

  4. clarinet deployment generate --testnet → Prepare deployment

  5. clarinet deployment apply --testnet → Deploy

Best Practices You Applied:

  • Used descriptive error constants

  • Validated inputs with assertions

  • Returned structured data from functions

  • Leveraged Clarity 3.0's new block height functions

Tomorrow's Preview

Ready to connect your smart contract to a beautiful web interface? Tomorrow we're building a modern React frontend using:

  • React 19 with latest hooks and features

  • Next.js 15 with App Router and TypeScript

  • Stacks.js 8.x with the new SIP-030 wallet connection standard

  • Tailwind CSS for modern, responsive styling

We'll create a web3 interface that lets users connect their wallets and interact with your Clarity 3.0 contract through an intuitive UI!

Join the Conversation

How did your first Clarity 3.0 contract deployment go? Share your contract address in the comments! If you encountered any issues with the updated commands or Clarity 3.0 syntax, let us know - that's how we all learn together.

Complete Implementation

Remember, all the working code for today's concepts is available in our GitHub repository. The tutorial teaches you why and how - the repo shows you the complete implementation details.

Next up: [Day 2 - Building a Modern React Frontend for Your Smart Contract]


This is Day 1 of our 30-day Clarity & Stacks.js tutorial series. Follow along daily as we progress from basics to building sophisticated decentralized applications.

Essential Links:

2
Subscribe to my newsletter

Read articles from Gbolahan Akande directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Gbolahan Akande
Gbolahan Akande