Developer Quickstart

Your 0→1 guide for building a Clarity contract and app on Stacks.

Welcome to the Stacks Developer Quickstart Guide! This is your fast-track path for understanding what you'll need to become a Stacks developer. In this guide, you’ll build a real Clarity smart contract, wire up a functioning Stacks app, and pick up about 75% of the practical knowledge every Stacks builder needs. Whether you’re shipping your first project or leveling up your skills, this guide takes you from zero to deployed—quickly and confidently.

What you'll achieve

By the end of this quickstart, you’ll have built an onchain app by:

  • Building a Clarity smart contract with Clarinet

  • Utilize the 1:1 Bitcoin backed token, sBTC

  • Deploying your smart contract to Stacks' testnet

  • Interacting with your deployed contract from a frontend app

What You'll Build

The app you'll build will be a message board contract. Users can add a new message to store on-chain for a fee of 1 satoshi in sBTC. Other functionality to read data from the contract will also be handled. Besides sBTC, there will be other things that'll be introduced to you such as post-conditions, Bitcoin read access, unit testing, wallet connectivity, BNS, Hiro, and more. Hopefully all this will give you a good flavor of what you can expect in the Stacks builder ecosystem.

Let's start building on Bitcoin! 🟧

Prerequisites

  • Basic familiarity with web development

  • Basic familiarity with web3 concepts

  • A modern web browser

  • Node.js

  • Visual Studio Code or any other popular IDE

Set Up Your Developer Environment

1

Install Clarinet

Clarinet is the popular CLI tool to build, test, and deploy smart contracts on the Stacks blockchain.

Below are a few different ways to install Clarinet on your machine using your terminal. Refer to the dedicated installation guide in the 'Learn Clarinet' section for more information.

2

Install Clarity Extension

You'll also want to install the Clarity Extension for your code editor. The official one is 'Clarity - Stacks Labs' which is maintained by Stacks Labs.

What is Clarity?

Clarity is Stacks' smart contract language, designed for safety and predictability.

Clarity is inspired by LISP and uses a functional programming approach. Everything in Clarity is an expression wrapped in parentheses. This can be a bit overwhelming at first if you are used to languages like JavaScript or Solidity, but the learning curve is short and Clarity is a simple language to understand once you dive in and start using it.

Check out the Clarity Crash Course for a quick primer.

The 'Clarity - Stacks Labs' extension as it appears in Visual Studio Code.
3

Install a Stacks wallet

There are many Stacks supported wallets in the market. For this guide, we'll be using the Leather wallet. Leather supports Stacks, Bitcoin, and other Bitcoin related meta-protocols. Download and install its browser extension so you can interact with your smart contract later on in this guide. Make sure to switch to the Testnet network in your wallet settings. Later on, we'll show you how to get testnet STX and sBTC tokens that you'll use for contract interaction.

Create a Clarity smart contract

1

Create a new Clarinet project

Let's start by creating a new Clarinet project which will house our smart contract. The clarinet new command sets up everything you need for smart contract development, including a testing framework, deployment configurations, and a local development environment.

A Clarinet project will be scaffolded with the below:

2

Generate your contract

Now that we have our project structure, let's create a smart contract. Navigate into your project directory and use Clarinet's contract generator:

Clarinet automatically creates both your contract file and a corresponding test file.

Write your Clarity smart contract

1

Define constants

Open contracts/message-board.clar and remove its existing content. This is where we'll start writing our own Clarity smart contract.

Let's first define some constants:

  • contract owner to establish control access

  • custom error codes to handle errors in functions

You'll notice in the CONTRACT_OWNER constant that tx-sender is set in place as the value. When this contract is deployed, the Clarity VM will determine who the tx-sender is based on who deployed the contract. This allows the hardcoded tx-sender to always point to the principal that deployed the contract.

2

Define data storage

We'll then need to define some data storage:

  • A map to store key-value pairs of the message id and it's related metadata

  • A data variable to count the total number of messages added

3

Define an add message function

Next up is our main function of the contract. This function allows users to add a new message to the contract for a fee of 1 satoshi in sBTC. Invoking this function will change the state of our contract and update the data storage pieces we setup before.

There's quite a lot going on in this function above that covers in-contract post-conditions, calling the official sBTC token contract, reading Bitcoin state, emitting events, and etc. We'll break it down for you:

Define public function and params

By using the define-public function, we can literally create a public function where anyone can invoke.

  • (add-message ... ) : the custom name of the public function

  • (content (string-utf8 280)) : the custom paramater name and type

Create let variable binding for next message id

Creates a "local" variable that can be used inside the function body only. This id variable will be used to represent the new message id being added.

Transfer 1 satoshi of sBTC from user to the contract

This snippet calls the external .sbtc-token contract to transfer sBTC.

The restrict-assets? acts as an in-contract post-condition to protect user and contract funds when calling external contracts to transfer assets.

Store message data in mapping

The function map-set will allow the existing mapping of messages to add a new key-value pair consiting of the metadata of the new message.

We'll be using the current Bitcoin block height (via burn-block-height) as a way to capture the time of when this new message was added. Through burn-block-height , Clarity allows us to have read access into the Bitcoin state at anytime.

Update the message-count variable

Increments the existing data variable of message-count with the let id variable.

Emit an event to the network

The print function will allow us to emit a custom event to the Stacks network.

Emitting events on Stacks serves several critical purposes:

  1. Transparency: Events provide an on-chain record of actions and transactions, ensuring transparency.

  2. Notification: They serve as a signal mechanism for users and external applications, notifying them of specific occurrences on Stacks.

  3. State Tracking: Developers can use events to track changes in the state of smart contracts without querying the chain continuously.

  4. Efficient Data Handling: By emitting events, webhook services, such as Hiro's Chainhooks, can filter and handle relevant data efficiently, reducing the on-chain computation load.

Return final response

Public functions must return a ResponseType (using either ok or err). In this case, we'll return a response type with an inner value of the new message id.

4

Add sBTC contract requirements

Since we're working with sBTC in our local developer environment, we'll need to make sure Clarinet can recognize this. Clarinet can automatically wire up the official sBTC contracts so you can build and test sBTC flows locally.

In our case, all we'll need to do is add the .sbtc-deposit contract as a project requirement.

You'll notice in the add-message public function, we're making an external contract call to the .sbtc-token contract. This is the official sBTC token contract that contains the SIP-010 standard transfer function that we are invoking.

Check out the dedicated sBTC integration page to learn more.

5

Allow contract owner to withdraw funds

In the beginning of our contract, we defined a constant to store the Stacks principal of the contract owner. Having a contract owner allows for specific access control of the contract that is entitled to the owner. Let's allow the owner to be able to withdraw the accumulated sBTC fees that were sent by anyone who created a new message in the contract.

You'll notice in the highlighted line that the function performs an asserts! check to confirm that the tx-sender calling the contract is in fact the CONTRACT_OWNER . If it is in fact the owner of the contract, the function body proceeds with transferring the balance of sBTC to the owner or else it'll throw an error that we defined earlier.

The usage of tx-sender versus another Clarity keyword, contract-caller , is always a tricky concept because it determines who actually initiated the transaction versus who invoked the current function. Both of them can have certain implications on security based on the context of your code. Check out the dedicated blog, written by community dev Setzeus, to learn when you should use either or.

6

Implement read only functions

We'll round out our contract with important read only functions that will return us needed data from the contract.

You'll notice the usage of a at-block function in the highlighted line of code. The at-block function evaluates the inner expression as if it were evaluated at the end of a specific Stacks block.

7

Test your contract

Now with the actual writing of your contract complete, we now need to test its functionality. There's a few different ways we can go about iterating and testing the functionality of your contract.

We'll go with unit testing for now. In your tests folder, open up the related message-board.test.ts file and let's use the unit test written below.

You'll notice we have two it blocks setup to test out 2 different scenarios:

  1. Allows user to add a new message

  2. Allows owner to withdraw sBTC funds

Run the test via npm run test to confirm that the two scenarios are functioning as intended.

Great! Now that your contract is working as intended, let's deploy the contract to testnet.

Get testnet faucet tokens

1

Navigate to the Hiro Platform faucet

Hiro is a platform to build and scale Bitcoin apps, including custom data streams, onchain alerts, API key management, and more. Create an account and navigate to the top tab of 'Faucet'. On the Faucet page, you can request testnet STX and/or sBTC. We'll be needing both so fund your Leather wallet account with both.

Grab the testnet Stacks address from your Leather wallet and paste it in the recipient field.

2

Confirm testnet tokens in your wallet

Open up your Leather extension to confirm that you've received testnet STX and sBTC. You might need to enable the viewing of the sBTC token in your wallet under 'Manage tokens'.

With both testnet STX and sBTC, you're ready to deploy your contract and interact with it from a front-end client.

Deploy your Clarity smart contract

1

Generate testnet deployment plan

You'll first want to input a mnemonic seed phrase in the settings/Testnet.toml file and specify the account derivation path that you want to use for deploying the contract. The account should be the same one you used to request testnet STX to. This will be the account that actually deploys the contract and becomes the contract owner.

Then generate a deployment plan for the testnet network. Deployment plans are YAML files that describe how contracts are published or called.

2

Deploy contract to testnet

Once your deployment plan is generated and configured properly, go ahead and deploy the contract to testnet.

If the contract was successfully deployed, you should see the below confirmation:

A sample of the contract we just created above is already deployed to testnet here. Check out its contract page on the Stacks Explorer and directly interact with its functions.

Use stacks.js on the frontend

1

Connect wallet

Using stacks.js packages on the frontend will allow our frontend app to authenticate wallets, call our contract functions, and interact with the Stacks network.

We'll first want to connect and authenticate our Leather wallet extension with our frontend app. The stacks.js monorepo contains several underlying packages specific to different use cases. The package @stacks/connect is the main connectivity package used in Stacks.

In the snippet below, you'll notice we have 3 functions setup to handle connectWallet , disconnectWallet, and for getBns . All 3 functions will be integral in how we want to display the 'Connect' and 'Disconnect' button in the UI.

Retrieving a wallet account's associated BNS is a staple of Stacks and for web3 identity. Check out BNSv2 for more information and for availably public API endpoints you could use.

The connect() method comes with ability to configure how you want the wallet selector modal to appear for your app. You can decide which wallets to have only appear as an option or allow any wallet that follows the SIP-030 standard to appear as an available Stacks wallet.

The Stacks Connect wallet selector modal
2

Call `add-message` public function

Next, we'll setup a stx_callContract to invoke the add-message public function of our contract. This function will accept a string content to be passed into our contract call.

You'll notice in the transaction data object that we pass into our string literal method of stx_callContract, that we're setting up post-conditions. Post-Conditions for the frontend are declared to protect user assets. The Pc helper from @stacks/transactions helps us to declare post-condition statements for any type of asset and equality operator.

Invoking our addMessage function will prompt the user's connected wallet to prompt a transaction confirmation popup. This popup will display all of the relevant information of the transaction as well as the post-condition statements that we've declared.

3

Call read-only function

As how we've created a few read-only functions in our contract, we'll also want to call these from the frontend to retrieve certain contract data.

Let's setup a fetchCallReadOnlyFunction to invoke our contract's get-message-count-at-block read-only function. For this, we'll fetch the current Stacks block height from the Hiro API endpoint and pass that returned value into our read-only function.

For the complete set of available API endpoints for the Stacks network, check out the Hiro docs. But first create an API key from the Hiro Platform to determine your API rate plan.

And that's it, you've successfully created an sBTC powered Clarity smart contract which can be interacted with from a frontend app. There's obviously much more you can do to complete this but you've got some of the basics down pat now. Go ahead and finish creating the frontend functions to call on the other contract functions we have.


Further Improvements

This is just the beginning. There are many ways we can improve upon this app. Here are some suggestions for you to extend the functionality of this app:

  • Deploy to mainnet and share your project with the community

  • Use Chainhooks to index emitted events from the contract

  • Integrate the sbtc library so users can directly bridge their BTC to sBTC in-app

  • Utilize SIP-009 NFTs to uniquely identify each message for each author


Next Steps

Now that you have the basics down, here are some ways to continue your Stacks development journey:

Learn More About Clarity

Development Tools

Community Resources

Last updated

Was this helpful?