Building Role-Based NFTs for Healthcare on the Stacks Blockchain.


In this tutorial, we’ll build a role-based NFT smart contract using Clarity, the smart contract language on the Stacks blockchain. These NFTs will represent user roles in a decentralized healthcare system.
🔍 Overview
We'll walk through creating a SIP-009 compliant NFT called RoleNFT, which represents roles like Patient
, Medical Staff
, Executive
, and more. This NFT contract:
Assigns a unique role to each token
Stores a URI to metadata (like an IPFS link)
Implements all the required SIP-009 functions
📦 Prerequisites
Clarinet (installed via Stacks CLI)
Basic familiarity with Clarity and smart contracts
Install Clarinet if you haven't:
npm install -g @hirosystems/clarinet
Create a new project:
clarinet new role-nft
cd role-nft
🧠 Step 1: Define Roles
In contracts/role-nft.clar
,start by defining roles as constants:
(define-constant ROLE_PATIENT u0)
(define-constant ROLE_MEDICAL_STAFF u1)
(define-constant ROLE_ADMIN_STAFF u2)
(define-constant ROLE_EXECUTIVE u3)
(define-constant ROLE_NON_MEDICAL_STAFF u4)
These uint
s will represent different user roles on-chain.
🔗 Step 2: Set Up SIP-009 NFT
SIP-009 is the standard interface for NFTs on Stacks. Implement metadata URI storage, ownership tracking, and SIP-009-required functions like get-owner
, get-token-uri
, and transfer
.
Define the NFT and URI map:
(define-non-fungible-token role-nft uint)
(define-map token-uri ((id uint)) ((uri (string-utf8 256))))
(define-map token-roles ((id uint)) ((role uint)))
(define-data-var token-id-counter uint u0)
🧬 Step 3: Minting Role NFTs
The mint-role
function assigns a role and URI to a new token.
(define-public (mint-role (recipient principal) (role uint) (uri (string-utf8 256)))
(if (not (is-some (get role {
u0: true, u1: true, u2: true, u3: true, u4: true
})))
(err u400)
(let (
(id (+ (var-get token-id-counter) u1))
)
(var-set token-id-counter id)
(map-set token-uri ((id id)) ((uri uri)))
(map-set token-roles ((id id)) ((role role)))
(nft-mint? role-nft id recipient)
)
)
)
This function:
Increments the ID counter
Stores the URI and role
Mints the token to the recipient
🔍 Step 4: Read Token Role
(define-read-only (get-role (id uint))
(default-to u999 (get role (map-get? token-roles ((id id)))))
)
Returns the role of the NFT or u999
if not found.
🧾 Step 5: SIP-009 Read Functions
(define-read-only (get-owner (id uint))
(nft-get-owner? role-nft id)
)
(define-read-only (get-token-uri (id uint))
(match (map-get? token-uri ((id id)))
uri-data (ok (get uri uri-data))
(err u404)
)
)
These functions allow dApps to query token ownership and metadata.
🔁 Step 6: Transfer NFTs
(define-public (transfer (id uint) (sender principal) (recipient principal))
(nft-transfer? role-nft id sender recipient)
)
Allows token owners to transfer role NFTs.
🧪 Step 7: Testing with Clarinet
Run tests using:
clarinet test
You can create test files in tests/
to simulate minting, transferring, and reading role NFTs.
🧠 Bonus Ideas
Create a frontend in Next.js to visualize role NFTs
Integrate IPFS for decentralized metadata
Use Clarity’s
define-trait
to enforce interfaces
🧾 Conclusion
You’ve now built a complete role-based NFT contract on Stacks! This approach can be used in healthcare, education, or any field that requires identity + role binding on-chain.
Want the full code? Check the GitHub repo here: http://github.com/BiliqisO/MediVerse
🔗 Resources
Let me know in the comments if you want a frontend for this too!
Subscribe to my newsletter
Read articles from Biliqis Onikoyi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Biliqis Onikoyi
Biliqis Onikoyi
Web3 || FrontEnd Dev