Skip to main content

Command Palette

Search for a command to run...

Building Trustless Crowdfunding with Clarity and Stacks

Published
5 min read

Crowdfunding is an amazing tool, but I've always felt that traditional platforms have a fundamental problem: they force you to trust a middleman. You're trusting them to hold the money, pay out the creator on time, and issue refunds correctly. That's why I started working on ClarityFund, a decentralized crowdfunding platform built on the Stacks blockchain.

Today, I want to walk you through the core of this project: the Clarity smart contract I wrote from scratch. This isn't just code, it's a set of unbreakable rules that allows creators and backers to interact directly and securely without needing to trust anyone but the logic itself.

The Core Idea: Code as Escrow

Before we get into the details, let me explain the simple concept I built this contract around. When a backer pledges funds to a campaign, their money doesn't go to the creator or a company. Instead, it goes into a digital escrow account managed directly by the smart contract.

From there, I programmed only two possible outcomes:

  1. Success: If the campaign hits its funding goal by the deadline, the contract releases the entire pool of funds to the creator.

  2. Failure: If the deadline passes and the goal isn't met, the contract allows every backer to withdraw their exact pledge.

That’s it. There are no other possibilities. The funds are locked by logic, and I designed it so that no one, not me, not the creator, not anyone else can access the money until one of those two conditions is met. It’s a truly trustless system.

A Tour of the Code I Wrote

Let's dive into the contract itself. I've added comments throughout my code, but here I want to explain the thinking behind my decisions.

Laying the Foundation: Constants and Data Maps

Every good program starts with clear definitions. The first thing I did was establish a set of constants for error codes.

;; Error codes
(define-constant ERR_UNAUTHORIZED (err u100))
(define-constant ERR_CAMPAIGN_NOT_FOUND (err u101))
(define-constant ERR_CAMPAIGN_ENDED (err u102))

Instead of just using numbers for errors, which can be cryptic, I gave them meaningful names. This makes the rest of the code much easier to read and debug down the line.

Next, I set up the data structures that act as the contract's memory. I used two main define-map structures:

  • campaigns: This is the master list of all funding campaigns. For each campaign, I store crucial details like the creator's wallet address (principal), the goal, the deadline, and the total pledged.

  • pledges: This map tracks who backed which project. It links a backer's address and a campaign ID to the amount they pledged. This ensures that backers can get their exact pledge back if a campaign fails.

Kicking Things Off: create-campaign

This is where it all begins. I wanted the process for a creator to be as straightforward as possible.

(define-public (create-campaign
  (goal uint)
  (duration uint)
  (title (string-ascii 100))
  (description (string-utf8 500))
)
  ;; ... function logic
)

When a creator calls this function, they just need to define their goal, duration (in blocks), and title/description. My code handles the rest. It automatically generates a unique ID, calculates the deadline based on the current block height, and logs all the information into the campaigns map. I also added some checks (asserts!) to make sure the goal and duration are valid, preventing someone from creating a campaign with a zero-STX goal.

Making a Pledge: The Heart of the Escrow

This function, pledge, is where the trustless magic happens. When a backer decides to support a project, they call this function.

(define-public (pledge (campaign-id uint) (amount uint))
  ;; ... function logic
)

First, the code checks that the campaign exists, is still active, and the pledge amount is valid. Once those checks pass, this single line does the heavy lifting:

(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))

This transfers the STX from the backer (tx-sender) directly to the contract itself (as-contract tx-sender). The funds are now officially in escrow. After that, both the pledges and campaigns maps are updated to reflect the new contribution.

The Payoff: claim-funds and claim-refund

These two functions control how money leaves the contract, and I designed them to be very strict.

The claim-funds function is for the creator. It’s only successful if the caller is the creator, the deadline has passed, and the goal was met. If all conditions are true, the contract executes a transfer of the total pledged amount to the creator.

(try! (as-contract (stx-transfer? total-amount tx-sender (get creator campaign))))

If the campaign doesn't meet its goal, the claim-refund function comes into play. This one is for backers. If the deadline has passed and the goal wasn't met, any backer can call it. The contract looks up how much they originally pledged and sends that exact amount back. It’s a direct and permissionless way for backers to reclaim their funds.

My Vision for ClarityFund

Writing this smart contract was the first and most crucial step in building ClarityFund. It's the foundational layer of trust or more accurately, the removal of the need for trust. With this structure, the platform can operate transparently and autonomously.

This code is more than just a project for me. It's a step toward a more open and equitable way of funding new ideas. My next step is to build a user-friendly web interface on top of this contract, allowing anyone to create or back a campaign with ease. The contract will quietly handle everything in the background, ensuring that every transaction is secure and every promise is kept by the code.