<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://basanta11subedi.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://basanta11subedi.github.io/" rel="alternate" type="text/html" /><updated>2025-07-08T07:08:54+00:00</updated><id>https://basanta11subedi.github.io/feed.xml</id><title type="html">Home</title><subtitle>Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description.</subtitle><author><name>Basanta Subedi</name></author><entry><title type="html">Semi Fungible token Example</title><link href="https://basanta11subedi.github.io/blog/post-sft-example/" rel="alternate" type="text/html" title="Semi Fungible token Example" /><published>2025-06-30T00:00:00+00:00</published><updated>2025-06-30T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-sft-example</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-sft-example/"><![CDATA[<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
;; Simple SIP-013 Semi-Fungible Token Implementation
;; Clarity Version 3

(impl-trait 'ST1ZK4MRVTQQJMVAAJQWBV2WPQ87QV2851YCTHD7X.sip-013-trait-sft-standard.sip-013-trait)

;; Define native token for post condition support
(define-fungible-token sft-token)
(define-non-fungible-token sft-token-id {token-id: uint, owner: principal})

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-INSUFFICIENT-BALANCE (err u1))
(define-constant ERR-SAME-SENDER-RECIPIENT (err u2))
(define-constant ERR-ZERO-AMOUNT (err u3))
(define-constant ERR-NOT-AUTHORIZED (err u4))
(define-constant ERR-TOKEN-NOT-FOUND (err u404))

;; Storage
(define-map token-balances {token-id: uint, owner: principal} uint)
(define-map token-supplies uint uint)
(define-map token-decimals uint uint)
(define-map token-uris uint (string-ascii 256))

;; Read-only functions

;; Get balance of a specific token for a principal
(define-read-only (get-balance (token-id uint) (who principal))
  (ok (default-to u0 (map-get? token-balances {token-id: token-id, owner: who})))
)

;; Get overall balance across all tokens for a principal
(define-read-only (get-overall-balance (who principal))
  (ok (ft-get-balance sft-token who))
)

;; Get total supply of a specific token
(define-read-only (get-total-supply (token-id uint))
  (ok (default-to u0 (map-get? token-supplies token-id)))
)

;; Get overall supply across all tokens
(define-read-only (get-overall-supply)
  (ok (ft-get-supply sft-token))
)

;; Get decimals for a token
(define-read-only (get-decimals (token-id uint))
  (ok (default-to u0 (map-get? token-decimals token-id)))
)

;; Get token URI
(define-read-only (get-token-uri (token-id uint))
  (ok (map-get? token-uris token-id))
)

;; Private helper functions

;; Tag NFT for post condition support
(define-private (tag-nft-token-id (nft-token-id {token-id: uint, owner: principal}))
  (begin
    (and
      (is-some (nft-get-owner? sft-token-id nft-token-id))
      (try! (nft-burn? sft-token-id nft-token-id (get owner nft-token-id)))
    )
    (nft-mint? sft-token-id nft-token-id (get owner nft-token-id))
  )
)

;; Update token balance
(define-private (set-balance (token-id uint) (owner principal) (new-balance uint))
  (if (&gt; new-balance u0)
    (map-set token-balances {token-id: token-id, owner: owner} new-balance)
    (map-delete token-balances {token-id: token-id, owner: owner})
  )
)

;; Public functions

;; Transfer tokens
(define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal))
  (let
    (
      (sender-balance (unwrap-panic (get-balance token-id sender)))
    )
    ;; Validate inputs
    (asserts! (&gt; amount u0) ERR-ZERO-AMOUNT)
    (asserts! (not (is-eq sender recipient)) ERR-SAME-SENDER-RECIPIENT)
    (asserts! (&gt;= sender-balance amount) ERR-INSUFFICIENT-BALANCE)
    (asserts! (or (is-eq sender tx-sender) (is-eq sender contract-caller)) ERR-NOT-AUTHORIZED)
    
    ;; Update balances
    (set-balance token-id sender (- sender-balance amount))
    (set-balance token-id recipient (+ (unwrap-panic (get-balance token-id recipient)) amount))
    
    ;; Transfer fungible tokens for post conditions
    (try! (ft-transfer? sft-token amount sender recipient))
    
    ;; Tag NFTs for post conditions
    (try! (tag-nft-token-id {token-id: token-id, owner: sender}))
    (try! (tag-nft-token-id {token-id: token-id, owner: recipient}))
    
    ;; Emit transfer event
    (print {type: "sft_transfer", token-id: token-id, amount: amount, sender: sender, recipient: recipient})
    
    (ok true)
  )
)

;; Transfer with memo
(define-public (transfer-memo (token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34)))
  (begin
    (try! (transfer token-id amount sender recipient))
    (print memo)
    (ok true)
  )
)

;; Admin functions

;; Mint new tokens (only owner)
(define-public (mint (token-id uint) (amount uint) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
    (asserts! (&gt; amount u0) ERR-ZERO-AMOUNT)
    
    ;; Update balance and supply
    (set-balance token-id recipient (+ (unwrap-panic (get-balance token-id recipient)) amount))
    (map-set token-supplies token-id (+ (unwrap-panic (get-total-supply token-id)) amount))
    
    ;; Mint fungible tokens for post conditions
    (try! (ft-mint? sft-token amount recipient))
    
    ;; Tag NFT for post conditions
    (try! (tag-nft-token-id {token-id: token-id, owner: recipient}))
    
    ;; Emit mint event
    (print {type: "sft_mint", token-id: token-id, amount: amount, recipient: recipient})
    
    (ok true)
  )
)

;; Burn tokens
(define-public (burn (token-id uint) (amount uint) (sender principal))
  (let
    (
      (sender-balance (unwrap-panic (get-balance token-id sender)))
    )
    (asserts! (&gt; amount u0) ERR-ZERO-AMOUNT)
    (asserts! (&gt;= sender-balance amount) ERR-INSUFFICIENT-BALANCE)
    (asserts! (or (is-eq sender tx-sender) (is-eq sender contract-caller)) ERR-NOT-AUTHORIZED)
    
    ;; Update balance and supply
    (set-balance token-id sender (- sender-balance amount))
    (map-set token-supplies token-id (- (unwrap-panic (get-total-supply token-id)) amount))
    
    ;; Burn fungible tokens for post conditions
    (try! (ft-burn? sft-token amount sender))
    
    ;; Tag NFT for post conditions
    (try! (tag-nft-token-id {token-id: token-id, owner: sender}))
    
    ;; Emit burn event
    (print {type: "sft_burn", token-id: token-id, amount: amount, sender: sender})
    
    (ok true)
  )
)

;; Set token metadata (only owner)
(define-public (set-token-uri (token-id uint) (uri (string-ascii 256)))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
    (map-set token-uris token-id uri)
    (ok true)
  )
)

;; Set token decimals (only owner)
(define-public (set-decimals (token-id uint) (decimals uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
    (map-set token-decimals token-id decimals)
    (ok true)
  )
)

</code></pre></div></div>
<h2 id="-contract-metadata-and-setup">🔧 <strong>Contract Metadata and Setup</strong></h2>

<h3 id="1-impl-trait-">1. <code class="language-plaintext highlighter-rouge">(impl-trait ...)</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(impl-trait 'ST1ZK4MRVTQQJMVAAJQWBV2WPQ87QV2851YCTHD7X.sip-013-trait-sft-standard.sip-013-trait)

</code></pre></div></div>

<blockquote>
  <p>Implements a trait (interface/standard) defined elsewhere. This ensures this contract complies with SIP-013, Stacks’ SFT standard.</p>

</blockquote>

<hr />

<h2 id="-token-definitions">🪙 <strong>Token Definitions</strong></h2>

<h3 id="2-define-fungible-token-and-define-non-fungible-token">2. <code class="language-plaintext highlighter-rouge">define-fungible-token</code> and <code class="language-plaintext highlighter-rouge">define-non-fungible-token</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-fungible-token sft-token)
(define-non-fungible-token sft-token-id {token-id: uint, owner: principal})

</code></pre></div></div>

<blockquote>
  <p>sft-token: A fungible token for total supply/balance tracking.sft-token-id: An NFT used for tagging balances for post-conditions.Structure: Each has a token-id and owner.</p>

</blockquote>

<hr />

<h2 id="️-constants">⚙️ <strong>Constants</strong></h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-constant CONTRACT-OWNER tx-sender)

</code></pre></div></div>

<blockquote>
  <p>Set the deployer as the contract owner.</p>

</blockquote>

<p>Other constants are <strong>error codes</strong>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-INSUFFICIENT-BALANCE (err u1))
...
(define-constant ERR-TOKEN-NOT-FOUND (err u404))

</code></pre></div></div>

<blockquote>
  <p>Used with asserts! to enforce logic rules.</p>

</blockquote>

<hr />

<h2 id="-storage-maps">🧠 <strong>Storage Maps</strong></h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-map token-balances {token-id: uint, owner: principal} uint)

</code></pre></div></div>

<blockquote>
  <p>Tracks how many units of a token a user owns.</p>

</blockquote>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-map token-supplies uint uint)

</code></pre></div></div>

<blockquote>
  <p>Total supply for each token ID.</p>

</blockquote>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-map token-decimals uint uint)

</code></pre></div></div>

<blockquote>
  <p>Number of decimal places for each token ID.</p>

</blockquote>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-map token-uris uint (string-ascii 256))

</code></pre></div></div>

<blockquote>
  <p>Metadata URI (e.g., image or metadata file) for each token.</p>

</blockquote>

<hr />

<h2 id="-read-only-functions">📖 <strong>Read-Only Functions</strong></h2>

<p>These functions <strong>don’t modify blockchain state</strong> and can be called without paying fees.</p>

<h3 id="get-balance"><code class="language-plaintext highlighter-rouge">get-balance</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(get-balance (token-id uint) (who principal))

</code></pre></div></div>

<blockquote>
  <p>Returns how much of token-id a user owns.</p>

</blockquote>

<h3 id="get-overall-balance"><code class="language-plaintext highlighter-rouge">get-overall-balance</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(get-overall-balance (who principal))

</code></pre></div></div>

<blockquote>
  <p>Returns balance of sft-token (total fungible tokens held by user).</p>

</blockquote>

<h3 id="get-total-supply--get-overall-supply"><code class="language-plaintext highlighter-rouge">get-total-supply</code> / <code class="language-plaintext highlighter-rouge">get-overall-supply</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(get-total-supply (token-id uint))
(get-overall-supply)

</code></pre></div></div>

<blockquote>
  <p>Total minted tokens per ID or across all.</p>

</blockquote>

<h3 id="get-decimals-get-token-uri"><code class="language-plaintext highlighter-rouge">get-decimals</code>, <code class="language-plaintext highlighter-rouge">get-token-uri</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(get-decimals (token-id uint))
(get-token-uri (token-id uint))

</code></pre></div></div>

<blockquote>
  <p>Get token metadata (decimals/URI).</p>

</blockquote>

<hr />

<h2 id="-private-helper-functions">🧰 <strong>Private Helper Functions</strong></h2>

<h3 id="tag-nft-token-id"><code class="language-plaintext highlighter-rouge">tag-nft-token-id</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(tag-nft-token-id {token-id: uint, owner: principal})

</code></pre></div></div>

<blockquote>
  <p>Used for post-condition support:</p>

  <ol>
    <li>Burn existing NFT if it exists.</li>
    <li>Mint new one with the same data.</li>
  </ol>
</blockquote>

<blockquote>
  <p>This enables tracking ownership changes on-chain.</p>

</blockquote>

<h3 id="set-balance"><code class="language-plaintext highlighter-rouge">set-balance</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(set-balance token-id owner new-balance)

</code></pre></div></div>

<blockquote>
  <p>Updates balance map:</p>

  <ul>
    <li>If balance &gt; 0 → store it.</li>
    <li>If balance = 0 → delete it.</li>
  </ul>
</blockquote>

<hr />

<h2 id="-public-functions">🔄 <strong>Public Functions</strong></h2>

<h3 id="-transfer">✅ <code class="language-plaintext highlighter-rouge">transfer</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(transfer token-id amount sender recipient)

</code></pre></div></div>

<blockquote>
  <p>Transfers amount of a token ID:</p>

  <ol>
    <li>Validates input:
      <ul>
        <li>Amount must be &gt; 0.</li>
        <li>Sender ≠ recipient.</li>
        <li>Sender has enough tokens.</li>
        <li>Sender is authorized (must be <code class="language-plaintext highlighter-rouge">tx-sender</code> or <code class="language-plaintext highlighter-rouge">contract-caller</code>).</li>
      </ul>
    </li>
    <li>Updates balances.</li>
    <li>Transfers associated fungible tokens.</li>
    <li>Tags NFTs.</li>
    <li>Logs event.</li>
  </ol>
</blockquote>

<h3 id="-transfer-memo">✅ <code class="language-plaintext highlighter-rouge">transfer-memo</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(transfer-memo token-id amount sender recipient memo)

</code></pre></div></div>

<blockquote>
  <p>Same as transfer but logs a memo (buffer of 34 bytes).</p>

</blockquote>

<hr />

<h2 id="️-admin-functions">🏗️ <strong>Admin Functions</strong></h2>

<p>Only the <strong>contract owner</strong> (deployer) can use these.</p>

<h3 id="-mint">🪙 <code class="language-plaintext highlighter-rouge">mint</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(mint token-id amount recipient)

</code></pre></div></div>

<blockquote>
  <p>Mints new SFT units:</p>

  <ol>
    <li>Only owner can mint.</li>
    <li>Increases balance and supply.</li>
    <li>Mints corresponding fungible tokens.</li>
    <li>Tags NFT.</li>
    <li>Logs event.</li>
  </ol>
</blockquote>

<h3 id="-burn">🔥 <code class="language-plaintext highlighter-rouge">burn</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(burn token-id amount sender)

</code></pre></div></div>

<blockquote>
  <p>Burns tokens:</p>

  <ol>
    <li>Validates ownership and amount.</li>
    <li>Decreases balance and supply.</li>
    <li>Burns corresponding fungible tokens.</li>
    <li>Tags NFT.</li>
    <li>Logs event.</li>
  </ol>
</blockquote>

<h3 id="-set-token-uri">🧾 <code class="language-plaintext highlighter-rouge">set-token-uri</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(set-token-uri token-id uri)

</code></pre></div></div>

<blockquote>
  <p>Set metadata URI (only by owner).</p>

</blockquote>

<h3 id="-set-decimals">🧮 <code class="language-plaintext highlighter-rouge">set-decimals</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(set-decimals token-id decimals)

</code></pre></div></div>

<blockquote>
  <p>Set number of decimal places (only by owner).</p>

</blockquote>

<hr />

<h3 id="1-what-is-sip-013"><strong>1. What is SIP-013?</strong></h3>

<p>A standard for <strong>Semi-Fungible Tokens (SFTs)</strong> that act like fungible tokens with distinct IDs.</p>

<h3 id="2-key-components-of-the-contract"><strong>2. Key Components of the Contract</strong></h3>

<ul>
  <li>Implements the SIP-013 trait.</li>
  <li>Uses both fungible and non-fungible logic.</li>
  <li>Stores balances, supplies, metadata.</li>
</ul>

<h3 id="3-smart-features"><strong>3. Smart Features</strong></h3>

<ul>
  <li>Fungible/NFT tagging for post-conditions.</li>
  <li>Error handling and balance cleanup.</li>
  <li>Built-in mint, transfer, burn, and metadata management.</li>
</ul>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[;; Simple SIP-013 Semi-Fungible Token Implementation ;; Clarity Version 3 (impl-trait 'ST1ZK4MRVTQQJMVAAJQWBV2WPQ87QV2851YCTHD7X.sip-013-trait-sft-standard.sip-013-trait) ;; Define native token for post condition support (define-fungible-token sft-token) (define-non-fungible-token sft-token-id {token-id: uint, owner: principal}) ;; Constants (define-constant CONTRACT-OWNER tx-sender) (define-constant ERR-OWNER-ONLY (err u100)) (define-constant ERR-INSUFFICIENT-BALANCE (err u1)) (define-constant ERR-SAME-SENDER-RECIPIENT (err u2)) (define-constant ERR-ZERO-AMOUNT (err u3)) (define-constant ERR-NOT-AUTHORIZED (err u4)) (define-constant ERR-TOKEN-NOT-FOUND (err u404)) ;; Storage (define-map token-balances {token-id: uint, owner: principal} uint) (define-map token-supplies uint uint) (define-map token-decimals uint uint) (define-map token-uris uint (string-ascii 256)) ;; Read-only functions ;; Get balance of a specific token for a principal (define-read-only (get-balance (token-id uint) (who principal)) (ok (default-to u0 (map-get? token-balances {token-id: token-id, owner: who}))) ) ;; Get overall balance across all tokens for a principal (define-read-only (get-overall-balance (who principal)) (ok (ft-get-balance sft-token who)) ) ;; Get total supply of a specific token (define-read-only (get-total-supply (token-id uint)) (ok (default-to u0 (map-get? token-supplies token-id))) ) ;; Get overall supply across all tokens (define-read-only (get-overall-supply) (ok (ft-get-supply sft-token)) ) ;; Get decimals for a token (define-read-only (get-decimals (token-id uint)) (ok (default-to u0 (map-get? token-decimals token-id))) ) ;; Get token URI (define-read-only (get-token-uri (token-id uint)) (ok (map-get? token-uris token-id)) ) ;; Private helper functions ;; Tag NFT for post condition support (define-private (tag-nft-token-id (nft-token-id {token-id: uint, owner: principal})) (begin (and (is-some (nft-get-owner? sft-token-id nft-token-id)) (try! (nft-burn? sft-token-id nft-token-id (get owner nft-token-id))) ) (nft-mint? sft-token-id nft-token-id (get owner nft-token-id)) ) ) ;; Update token balance (define-private (set-balance (token-id uint) (owner principal) (new-balance uint)) (if (&gt; new-balance u0) (map-set token-balances {token-id: token-id, owner: owner} new-balance) (map-delete token-balances {token-id: token-id, owner: owner}) ) ) ;; Public functions ;; Transfer tokens (define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal)) (let ( (sender-balance (unwrap-panic (get-balance token-id sender))) ) ;; Validate inputs (asserts! (&gt; amount u0) ERR-ZERO-AMOUNT) (asserts! (not (is-eq sender recipient)) ERR-SAME-SENDER-RECIPIENT) (asserts! (&gt;= sender-balance amount) ERR-INSUFFICIENT-BALANCE) (asserts! (or (is-eq sender tx-sender) (is-eq sender contract-caller)) ERR-NOT-AUTHORIZED) ;; Update balances (set-balance token-id sender (- sender-balance amount)) (set-balance token-id recipient (+ (unwrap-panic (get-balance token-id recipient)) amount)) ;; Transfer fungible tokens for post conditions (try! (ft-transfer? sft-token amount sender recipient)) ;; Tag NFTs for post conditions (try! (tag-nft-token-id {token-id: token-id, owner: sender})) (try! (tag-nft-token-id {token-id: token-id, owner: recipient})) ;; Emit transfer event (print {type: "sft_transfer", token-id: token-id, amount: amount, sender: sender, recipient: recipient}) (ok true) ) ) ;; Transfer with memo (define-public (transfer-memo (token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34))) (begin (try! (transfer token-id amount sender recipient)) (print memo) (ok true) ) ) ;; Admin functions ;; Mint new tokens (only owner) (define-public (mint (token-id uint) (amount uint) (recipient principal)) (begin (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY) (asserts! (&gt; amount u0) ERR-ZERO-AMOUNT) ;; Update balance and supply (set-balance token-id recipient (+ (unwrap-panic (get-balance token-id recipient)) amount)) (map-set token-supplies token-id (+ (unwrap-panic (get-total-supply token-id)) amount)) ;; Mint fungible tokens for post conditions (try! (ft-mint? sft-token amount recipient)) ;; Tag NFT for post conditions (try! (tag-nft-token-id {token-id: token-id, owner: recipient})) ;; Emit mint event (print {type: "sft_mint", token-id: token-id, amount: amount, recipient: recipient}) (ok true) ) ) ;; Burn tokens (define-public (burn (token-id uint) (amount uint) (sender principal)) (let ( (sender-balance (unwrap-panic (get-balance token-id sender))) ) (asserts! (&gt; amount u0) ERR-ZERO-AMOUNT) (asserts! (&gt;= sender-balance amount) ERR-INSUFFICIENT-BALANCE) (asserts! (or (is-eq sender tx-sender) (is-eq sender contract-caller)) ERR-NOT-AUTHORIZED) ;; Update balance and supply (set-balance token-id sender (- sender-balance amount)) (map-set token-supplies token-id (- (unwrap-panic (get-total-supply token-id)) amount)) ;; Burn fungible tokens for post conditions (try! (ft-burn? sft-token amount sender)) ;; Tag NFT for post conditions (try! (tag-nft-token-id {token-id: token-id, owner: sender})) ;; Emit burn event (print {type: "sft_burn", token-id: token-id, amount: amount, sender: sender}) (ok true) ) ) ;; Set token metadata (only owner) (define-public (set-token-uri (token-id uint) (uri (string-ascii 256))) (begin (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY) (map-set token-uris token-id uri) (ok true) ) ) ;; Set token decimals (only owner) (define-public (set-decimals (token-id uint) (decimals uint)) (begin (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY) (map-set token-decimals token-id decimals) (ok true) ) ) 🔧 Contract Metadata and Setup 1. (impl-trait ...) (impl-trait 'ST1ZK4MRVTQQJMVAAJQWBV2WPQ87QV2851YCTHD7X.sip-013-trait-sft-standard.sip-013-trait) Implements a trait (interface/standard) defined elsewhere. This ensures this contract complies with SIP-013, Stacks’ SFT standard. 🪙 Token Definitions 2. define-fungible-token and define-non-fungible-token (define-fungible-token sft-token) (define-non-fungible-token sft-token-id {token-id: uint, owner: principal}) sft-token: A fungible token for total supply/balance tracking.sft-token-id: An NFT used for tagging balances for post-conditions.Structure: Each has a token-id and owner. ⚙️ Constants (define-constant CONTRACT-OWNER tx-sender) Set the deployer as the contract owner. Other constants are error codes: (define-constant ERR-OWNER-ONLY (err u100)) (define-constant ERR-INSUFFICIENT-BALANCE (err u1)) ... (define-constant ERR-TOKEN-NOT-FOUND (err u404)) Used with asserts! to enforce logic rules. 🧠 Storage Maps (define-map token-balances {token-id: uint, owner: principal} uint) Tracks how many units of a token a user owns. (define-map token-supplies uint uint) Total supply for each token ID. (define-map token-decimals uint uint) Number of decimal places for each token ID. (define-map token-uris uint (string-ascii 256)) Metadata URI (e.g., image or metadata file) for each token. 📖 Read-Only Functions These functions don’t modify blockchain state and can be called without paying fees. get-balance (get-balance (token-id uint) (who principal)) Returns how much of token-id a user owns. get-overall-balance (get-overall-balance (who principal)) Returns balance of sft-token (total fungible tokens held by user). get-total-supply / get-overall-supply (get-total-supply (token-id uint)) (get-overall-supply) Total minted tokens per ID or across all. get-decimals, get-token-uri (get-decimals (token-id uint)) (get-token-uri (token-id uint)) Get token metadata (decimals/URI). 🧰 Private Helper Functions tag-nft-token-id (tag-nft-token-id {token-id: uint, owner: principal}) Used for post-condition support: Burn existing NFT if it exists. Mint new one with the same data. This enables tracking ownership changes on-chain. set-balance (set-balance token-id owner new-balance) Updates balance map: If balance &gt; 0 → store it. If balance = 0 → delete it. 🔄 Public Functions ✅ transfer (transfer token-id amount sender recipient) Transfers amount of a token ID: Validates input: Amount must be &gt; 0. Sender ≠ recipient. Sender has enough tokens. Sender is authorized (must be tx-sender or contract-caller). Updates balances. Transfers associated fungible tokens. Tags NFTs. Logs event. ✅ transfer-memo (transfer-memo token-id amount sender recipient memo) Same as transfer but logs a memo (buffer of 34 bytes). 🏗️ Admin Functions Only the contract owner (deployer) can use these. 🪙 mint (mint token-id amount recipient) Mints new SFT units: Only owner can mint. Increases balance and supply. Mints corresponding fungible tokens. Tags NFT. Logs event. 🔥 burn (burn token-id amount sender) Burns tokens: Validates ownership and amount. Decreases balance and supply. Burns corresponding fungible tokens. Tags NFT. Logs event. 🧾 set-token-uri (set-token-uri token-id uri) Set metadata URI (only by owner). 🧮 set-decimals (set-decimals token-id decimals) Set number of decimal places (only by owner). 1. What is SIP-013? A standard for Semi-Fungible Tokens (SFTs) that act like fungible tokens with distinct IDs. 2. Key Components of the Contract Implements the SIP-013 trait. Uses both fungible and non-fungible logic. Stores balances, supplies, metadata. 3. Smart Features Fungible/NFT tagging for post-conditions. Error handling and balance cleanup. Built-in mint, transfer, burn, and metadata management.]]></summary></entry><entry><title type="html">How to build NFT MarketPlace</title><link href="https://basanta11subedi.github.io/blog/post-NFT-Marketplace/" rel="alternate" type="text/html" title="How to build NFT MarketPlace" /><published>2025-06-02T00:00:00+00:00</published><updated>2025-06-02T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-NFT-Marketplace</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-NFT-Marketplace/"><![CDATA[<h2 id="1-introduction">1. Introduction</h2>

<p>NFTs—short for <em>non-fungible tokens</em>—are digital assets with unique identifiers. Non-fungible means that the tokens are not equivalent to one another. NFTs are created using <strong>Clarity smart contracts</strong>, a secure and predictable language designed for high-stakes blockchain applications. The most widely used NFT standard on Stacks is the <strong>SIP-009</strong> (Stacks Improvement Proposal 009), which defines the basic functionality and metadata structure required for NFTs on the network.</p>

<p>An <strong>NFT marketplace</strong> is a digital platform where users can mint, buy, sell, and trade NFTs. These marketplaces serve as hubs for creators and collectors to exchange unique digital assets.</p>

<h2 id="2-prerequisites">2. Prerequisites</h2>

<ol>
  <li>
    <p>Familiar with Clarity<br />
<a href="https://book.clarity-lang.org/">Clarity Book</a> or <a href="https://docs.hiro.so/stacks/clarity">Clarity Docs</a></p>
  </li>
  <li>
    <p>Familiar with Stacks<br />
<a href="https://docs.stacks.co/">Stacks Docs</a> or <a href="https://docs.hiro.so/stacks/stacks.js">Stacks.js Docs</a></p>
  </li>
  <li>
    <p>Familiar with NFTs and SIP-009<br />
<a href="https://book.clarity-lang.org/ch10-01-sip009-nft-standard.html">SIP-009 NFT Standard</a></p>
  </li>
</ol>

<h2 id="3-implementing-the-nft-marketplace">3. Implementing the NFT MarketPlace</h2>

<p>To implement the NFT marketplace, I wrote two smart contracts. One is for minting and transfer NFTs using the NFT trait, and the other is for the marketplace, where users can list, unlist, buy, and transfer NFTs between accounts. I will explain all the code in detail and provide the contract address link so you can review it.</p>

<h3 id="31-simplenftclar">3.1 SimpleNFT.clar</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; SimpleNFT - A basic NF contract for stacks blockchain
;; implements sip-009 nft standard
;; clarity version- 3
</code></pre></div></div>

<ul>
  <li>The contract name is SimpleNFt which implements sip-009 nft standard. The smart contract code is written in clarity version 3.</li>
</ul>

<p>🔗 <strong>Trait Implementation</strong></p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nx">impl</span><span class="o">-</span><span class="nx">trait</span> <span class="dl">'</span><span class="s1">ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-trait.nft-trait)

</span></code></pre></div></div>

<p><strong>The SIP009 NFT trait on testnet</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    
  (define-trait sip009-nft-trait (
		
    ;; Last token ID, limited to uint range
		(get-last-token-id () (response uint uint))
    
    ;; URI for metadata associated with the token
    (get-token-uri (uint) (response (optional (string-ascii 256)) uint))

    ;; Owner of a given token identifier
    (get-owner (uint) (response (optional principal) uint))

    ;; Transfer from the sender to a new principal
    (transfer (uint principal principal) (response bool uint))
  )
)

</code></pre></div></div>

<ul>
  <li><strong>Purpose</strong>: Imports and implements the <strong>SIP-009 NFT trait</strong> (interface).</li>
  <li>This ensures the contract conforms to the NFT standard (required functions and behavior).</li>
</ul>

<p>❗ <strong>Error Constants</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; errors
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-NOT-FOUND (err u101))
(define-constant ERR-METADATA-FROZEN (err u102))

</code></pre></div></div>

<ul>
  <li>Predefined <strong>error codes</strong> to use throughout the contract for clarity and consistency.</li>
</ul>

<p>📦 <strong>State Variables</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; Data variables
(define-data-var contract-owner principal tx-sender)
(define-data-var last-token-id uint u0)
(define-data-var metadata-frozen bool  false)
</code></pre></div></div>
<ul>
  <li><code class="language-plaintext highlighter-rouge">contract-owner</code>: Stores the address of the contract owner.</li>
  <li><code class="language-plaintext highlighter-rouge">last-token-id</code>: Keeps track of the latest minted token ID.</li>
  <li><code class="language-plaintext highlighter-rouge">metadata-frozen</code>: Prevents changes to metadata after it’s locked.</li>
</ul>

<p>🗺️ <strong>Data Maps</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; nft metadata mapping
(define-map token-uris uint (string-ascii 256))

;;token ownership
(define-map token-owners uint principal)
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">token-uris</code>: Maps token ID to its metadata URI .</li>
  <li><code class="language-plaintext highlighter-rouge">token-owners</code>: Maps token ID to the wallet address (principal) that owns it.</li>
</ul>

<h2 id="-read-only-functions">📖 <strong>Read-Only Functions</strong></h2>

<p>These functions <strong>don’t modify blockchain state</strong>, only return data.</p>

<h3 id="get-last-token-id"><code class="language-plaintext highlighter-rouge">get-last-token-id</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;get the last token id
(define-read-only (get-last-token-id)
    (ok (var-get last-token-id))
)
</code></pre></div></div>

<ul>
  <li>Returns the latest token ID minted.</li>
</ul>

<h3 id="get-token-uri"><code class="language-plaintext highlighter-rouge">get-token-uri</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;get token uri
(define-read-only (get-token-uri (token-id uint))
    (ok (map-get? token-uris token-id))
</code></pre></div></div>

<ul>
  <li>Returns the metadata URI for a specific token.</li>
</ul>

<h3 id="get-owner"><code class="language-plaintext highlighter-rouge">get-owner</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; get the owner of the specified token
(define-read-only (get-owner (token-id uint))
    (ok (map-get? token-owners token-id))
)
</code></pre></div></div>

<ul>
  <li>Returns the owner of a specific token.</li>
</ul>

<h3 id="get-total-supply"><code class="language-plaintext highlighter-rouge">get-total-supply</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;get total supply
(define-read-only (get-total-supply)
  (ok (var-get last-token-id))
)
</code></pre></div></div>

<ul>
  <li>Since token IDs are sequential, this returns the total NFTs minted.</li>
</ul>

<h2 id="-public-functions">🚀 <strong>Public Functions</strong></h2>

<p>These <strong>change state</strong> on the blockchain.</p>

<h3 id="mint"><code class="language-plaintext highlighter-rouge">mint</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; mint nft
(define-public (mint (metadata-uri (string-ascii 256)))
  (let (
      (token-id (+ (var-get last-token-id) u1))
      (owner tx-sender)
    )
    (var-set last-token-id token-id)
    (map-set token-owners token-id owner)
    (map-set token-uris token-id metadata-uri)
    ;; (try! (nft-mint? asset-name asset-identifier recipient))
    (ok token-id)
  )
)
</code></pre></div></div>

<ul>
  <li><strong>Mints a new NFT</strong>:
    <ul>
      <li>Increments token ID.</li>
      <li>Sets the sender as the owner.</li>
      <li>Stores the metadata URI.</li>
      <li>Returns the new token ID.</li>
    </ul>
  </li>
</ul>

<h3 id="transfer"><code class="language-plaintext highlighter-rouge">transfer</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (transfer (token-id uint) (sender principal) (recipient principal))
  (begin
    (asserts! (is-eq sender tx-sender) ERR-NOT-AUTHORIZED)
    (asserts! (is-some (map-get? token-owners token-id)) ERR-NOT-FOUND)
    (asserts! (is-eq (some sender) (map-get? token-owners token-id)) ERR-NOT-AUTHORIZED)
    
    (ok (map-set token-owners token-id recipient))
    
  )
)
</code></pre></div></div>

<ul>
  <li>Transfers a token from one user to another.</li>
  <li>Includes checks to ensure:
    <ul>
      <li>Caller is the <code class="language-plaintext highlighter-rouge">sender</code>.</li>
      <li>Token exists.</li>
      <li><code class="language-plaintext highlighter-rouge">sender</code> is actually the owner.</li>
    </ul>
  </li>
</ul>

<h3 id="set-token-uri"><code class="language-plaintext highlighter-rouge">set-token-uri</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; Metadata Management Functions
(define-public (set-token-uri (token-id uint) (new-uri (string-ascii 256)))
  (begin
    (asserts! (not (var-get metadata-frozen)) ERR-METADATA-FROZEN)
    (asserts! (is-some (map-get? token-owners token-id)) ERR-NOT-FOUND)
    (asserts! (is-eq (some tx-sender) (map-get? token-owners token-id)) ERR-NOT-AUTHORIZED)
    
    (map-set token-uris token-id new-uri)
    (ok true)
  )
)
</code></pre></div></div>

<ul>
  <li>Allows the owner of a token to update its metadata URI.</li>
  <li>Only works <strong>if metadata is not frozen</strong>.</li>
</ul>

<h3 id="freeze-metadata"><code class="language-plaintext highlighter-rouge">freeze-metadata</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (freeze-metadata)
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner tx-sender)) ERR-NOT-AUTHORIZED) ;; 
    (var-set metadata-frozen true)
    (ok true)
  )
)
</code></pre></div></div>

<ul>
  <li>Freezes metadata updates globally.</li>
  <li>Prevents future <code class="language-plaintext highlighter-rouge">set-token-uri</code> calls.</li>
</ul>

<h2 id="simplenft">SimpleNFT</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; SimpleNFT - A basic NF contract for stacks blockchain
;; implements sip-009 nft standard
;; clarity version- 3

(impl-trait 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-trait.nft-trait)

;; errors
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-NOT-FOUND (err u101))
(define-constant ERR-METADATA-FROZEN (err u102))

;; Data variables
(define-data-var last-token-id uint u0)
(define-data-var metadata-frozen bool  false)

;; nft metadata mapping
(define-map token-uris uint (string-ascii 256))

;;token ownership
(define-map token-owners uint principal)

;;SIP-009 functions
;; read only functions
;;get the last token id
(define-read-only (get-last-token-id)
    (ok (var-get last-token-id))
)

;;get token uri
(define-read-only (get-token-uri (token-id uint))
    (ok (map-get? token-uris token-id))
)

;; get the owner of the specified token
(define-read-only (get-owner (token-id uint))
    (ok (map-get? token-owners token-id))
)

;;get total supply
(define-read-only (get-total-supply)
  (ok (var-get last-token-id))
)

;;public functions

;; mint nft
(define-public (mint (metadata-uri (string-ascii 256)))
  (let (
      (token-id (+ (var-get last-token-id) u1))
      (owner tx-sender)
    )
    (var-set last-token-id token-id)
    (map-set token-owners token-id owner)
    (map-set token-uris token-id metadata-uri)
    ;; (try! (nft-mint? asset-name asset-identifier recipient))
    (ok token-id)
  )
)

(define-public (transfer (token-id uint) (sender principal) (recipient principal))
  (begin
    (asserts! (is-eq sender tx-sender) ERR-NOT-AUTHORIZED)
    (asserts! (is-some (map-get? token-owners token-id)) ERR-NOT-FOUND)
    (asserts! (is-eq (some sender) (map-get? token-owners token-id)) ERR-NOT-AUTHORIZED)
    
    (ok (map-set token-owners token-id recipient))
    
  )
)

;; Metadata Management Functions
(define-public (set-token-uri (token-id uint) (new-uri (string-ascii 256)))
  (begin
    (asserts! (not (var-get metadata-frozen)) ERR-METADATA-FROZEN)
    (asserts! (is-some (map-get? token-owners token-id)) ERR-NOT-FOUND)
    (asserts! (is-eq (some tx-sender) (map-get? token-owners token-id)) ERR-NOT-AUTHORIZED)
    
    (map-set token-uris token-id new-uri)
    (ok true)
  )
)

(define-public (freeze-metadata)
  (begin
    (asserts! (is-eq tx-sender tx-sender) ERR-NOT-AUTHORIZED) ;; Replace with your owner check if needed
    (var-set metadata-frozen true)
    (ok true)
  )
)
</code></pre></div></div>
<ul>
  <li>Contract address: ST390VFVZJA4WP7QSZN0RTSGQDAG2P9NPN3X1ATDX.SimpleNFT</li>
</ul>

<h3 id="32-marketplaceclar">3.2 MarketPlace.clar</h3>

<h3 id="-contract-level-constants">🔒 Contract-Level Constants</h3>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nx">define</span><span class="o">-</span><span class="nx">constant</span> <span class="nx">contract</span><span class="o">-</span><span class="nx">owner</span> <span class="nx">tx</span><span class="o">-</span><span class="nx">sender</span><span class="p">)</span>
<span class="p">(</span><span class="nx">define</span><span class="o">-</span><span class="nx">constant</span> <span class="nx">PLATFORM</span><span class="o">-</span><span class="nx">FEE</span><span class="o">-</span><span class="nx">BPS</span>  <span class="nx">u250</span><span class="p">)</span> <span class="p">;;</span><span class="mf">2.5</span><span class="o">%</span> <span class="nx">basic</span> <span class="nx">fee</span> <span class="k">for</span> <span class="nx">now</span>
</code></pre></div></div>

<ul>
  <li>Sets the <strong>contract owner</strong> at deployment time (you can replace with a specific address for real-world use).</li>
  <li>Used to restrict sensitive actions (like withdrawing fees).</li>
  <li>2.5% platform fee defined in <strong>basis points</strong> (BPS).</li>
  <li>250 BPS = 2.5% of the sale price.</li>
</ul>

<hr />

<h3 id="-error-constants">❗ Error Constants</h3>

<p>These are predefined <strong>error codes</strong> for common issues:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;ERROR
(define-constant ERR-NOT-AUTHORIZED (err u401))
(define-constant ERR-LISTING-EXPIRED (err u402))
(define-constant ERR-PRICE-MISMATCHED (err u403))
(define-constant ERR-ALREADY-LISTED (err u404))
(define-constant ERR-NFT-TRANSFER-FAILED (err u405))
(define-constant ERR-NOT-FOUND (err u406))
(define-constant ERR-OWNER-CANNOT-BUY (err u407))
(define-constant ERR-INSUFFICIENT-FUNDS (err u408))
</code></pre></div></div>

<hr />

<h3 id="️-data-structures">🗺️ Data Structures</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;Data structures
(define-map listings {token-id: uint} 
    {seller: principal, price: uint, expiry: uint, is-active: bool}
)
</code></pre></div></div>

<ul>
  <li>Tracks all <strong>NFT listings</strong>.</li>
  <li>Keys: <code class="language-plaintext highlighter-rouge">token-id</code>, Values: seller info, price, expiry block, and listing status.</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;Track paltform fee
(define-data-var total-platform-fees  uint u0)
</code></pre></div></div>

<ul>
  <li>Accumulates total platform fees collected from sales.</li>
</ul>

<hr />

<h2 id="-read-only-functions-1">🧠 Read-Only Functions</h2>

<h3 id="-is-listed">✅ <code class="language-plaintext highlighter-rouge">is-listed</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;check if token is listed
(define-read-only (is-listed (token-id uint)) 
    (match (map-get? listings { token-id: token-id})
        listing (and (get is-active listing)
            (&lt; stacks-block-height (get expiry listing)))
        false
    )
)
</code></pre></div></div>

<p><strong>Checks if a token is currently listed and hasn’t expired.</strong></p>

<p>Returns <code class="language-plaintext highlighter-rouge">true</code> if:</p>

<ul>
  <li>Token is in the map,</li>
  <li><code class="language-plaintext highlighter-rouge">is-active</code> is true,</li>
  <li><code class="language-plaintext highlighter-rouge">expiry</code> is greater than current block height.</li>
</ul>

<hr />

<h3 id="-get-listing">📜 <code class="language-plaintext highlighter-rouge">get-listing</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;get listing details if active
(define-read-only (get-listing (token-id uint))
    (map-get? listings {token-id: token-id})
)

</code></pre></div></div>

<p><strong>Fetches full listing info for a token if it exists.</strong></p>

<p>Returns:</p>

<ul>
  <li>Listing object if found (even if expired/inactive),</li>
  <li><code class="language-plaintext highlighter-rouge">none</code> otherwise.</li>
</ul>

<hr />

<h3 id="-calculate-platform-fee">💸 <code class="language-plaintext highlighter-rouge">calculate-platform-fee</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;calculate fees for the given amount
(define-read-only (calculate-platform-fee (amount uint)) 
    (/ (* amount PLATFORM-FEE-BPS) u10000)
)
</code></pre></div></div>

<ul>
  <li>Calculates platform fee from total price.</li>
  <li>Example: If <code class="language-plaintext highlighter-rouge">amount = 1000</code>, fee = 2.5% = 25.</li>
</ul>

<hr />

<h3 id="-get-total-platform-fees">💰 <code class="language-plaintext highlighter-rouge">get-total-platform-fees</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;get total platform fee of the platform
(define-read-only (get-total-platform-fees) 
    (var-get total-platform-fees)
)
</code></pre></div></div>

<ul>
  <li>Returns the accumulated fees collected by the platform so far.</li>
</ul>

<hr />

<h2 id="️-listing-functions">🛍️ Listing Functions</h2>

<hr />

<h3 id="-list-nft">📦 <code class="language-plaintext highlighter-rouge">list-nft</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; list nft for sale
(define-public (list-nft (token-id uint) (price uint) (expiry uint))
  (let (
    (nft-owner-opt (unwrap! (contract-call? .SimpleNFT get-owner token-id) (err u0)))
  )
    ;; Check if the owner is none
    (asserts! (is-some nft-owner-opt) ERR-NOT-AUTHORIZED)
    (let (
      (nft-owner (unwrap! nft-owner-opt (err u0)))
    )
      ;; Validations
      (asserts! (is-eq tx-sender nft-owner) ERR-NOT-AUTHORIZED)
      (asserts! (&gt; expiry stacks-block-height) ERR-LISTING-EXPIRED)
      (asserts! (&gt; price u0) ERR-PRICE-MISMATCHED)
      (asserts! (not (is-listed token-id)) ERR-ALREADY-LISTED)

      ;; Transfer NFT to this contract (escrow)
      (unwrap! (contract-call? .SimpleNFT transfer token-id tx-sender (as-contract tx-sender)) ERR-NFT-TRANSFER-FAILED)
      
      ;; Create the listing
      (map-set listings
        { token-id: token-id }
        { 
          seller: tx-sender, 
          price: price, 
          expiry: expiry, 
          is-active: true
        }
      )
      
      (ok true)
    )
  )
)
</code></pre></div></div>

<p><strong>Lists a token for sale and transfers NFT to escrow (Marketplace contract).</strong></p>

<p>Includes checks to ensure:</p>

<ul>
  <li>Token exists and caller is the owner (via <code class="language-plaintext highlighter-rouge">SimpleNFT.get-owner</code>).</li>
  <li>Price is positive.</li>
  <li>Expiry is in the future.</li>
  <li>Token isn’t already listed.</li>
  <li>NFT is transferred to this contract via <code class="language-plaintext highlighter-rouge">SimpleNFT.transfer</code>.</li>
</ul>

<p>Stores listing in the <code class="language-plaintext highlighter-rouge">listings</code> map as active.</p>

<hr />

<h3 id="-cancel-listing">❌ <code class="language-plaintext highlighter-rouge">cancel-listing</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;;cancel a listings
(define-public (cancel-listing (token-id uint))
    (let (
        (listing (unwrap! (map-get? listings { token-id: token-id}) ERR-NOT-FOUND))
    )   
    ;;only seller can cancel
    (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED)
    ;;ensure listing is active
    (asserts! (get is-active listing) ERR-NOT-FOUND)

    ;;Mark lisitng is inactive
    (map-set listings {token-id: token-id} (merge listing {is-active: false}))

    ;;Return nft to seller from escrow
    (as-contract (contract-call? .SimpleNFT transfer token-id tx-sender (get seller listing))))
)
</code></pre></div></div>

<p><strong>Cancels an active listing and returns NFT to seller.</strong></p>

<p>Includes:</p>

<ul>
  <li>Validation that caller is the original seller.</li>
  <li>Checks if listing is still active.</li>
  <li>Marks the listing inactive.</li>
  <li>Transfers NFT from contract back to seller using <code class="language-plaintext highlighter-rouge">SimpleNFT.transfer</code>.</li>
</ul>

<hr />

<h2 id="-purchase-functions">💵 Purchase Functions</h2>

<hr />

<h3 id="-buy-nft">🛒 <code class="language-plaintext highlighter-rouge">buy-nft</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; buy listed nft
(define-public (buy-nft (token-id uint))
  (let (
    (listing (unwrap! (map-get? listings { token-id: token-id }) ERR-NOT-FOUND))
    (price (get price listing))
    (seller (get seller listing))
    (platform-fee (calculate-platform-fee price))
    (seller-amount (- price platform-fee))
  )
    ;; Check conditions
    (asserts! (get is-active listing) ERR-NOT-FOUND)
    (asserts! (&lt; stacks-block-height (get expiry listing)) ERR-LISTING-EXPIRED)
    (asserts! (not (is-eq tx-sender seller)) ERR-OWNER-CANNOT-BUY)
    
    ;; Update platform fees
    (var-set total-platform-fees (+ (var-get total-platform-fees) platform-fee))
    
    ;; Mark listing as inactive
    (map-set listings
      { token-id: token-id }
      (merge listing { is-active: false })
    )
    
    ;; Pay seller
    (unwrap! (stx-transfer? seller-amount tx-sender seller) ERR-INSUFFICIENT-FUNDS)
    
    ;; Pay platform fee
    (unwrap! (stx-transfer? platform-fee tx-sender contract-owner) ERR-INSUFFICIENT-FUNDS)
    
    ;; Transfer NFT to buyer from escrow
    (as-contract 
      (contract-call? .SimpleNFT transfer token-id tx-sender tx-sender)
    )
  )
)
</code></pre></div></div>

<p><strong>Allows a buyer to purchase a listed NFT.</strong></p>

<p>Includes:</p>

<ul>
  <li>Checks that listing is active, not expired, and buyer isn’t seller.</li>
  <li>Calculates fee and seller amount.</li>
  <li>Adds fee to <code class="language-plaintext highlighter-rouge">total-platform-fees</code>.</li>
  <li>Transfers STX to seller and platform.</li>
  <li>Transfers NFT to buyer from contract using <code class="language-plaintext highlighter-rouge">SimpleNFT.transfer</code>.</li>
</ul>

<h3 id="-update-listing-price">💲 <code class="language-plaintext highlighter-rouge">update-listing-price</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; Update a listing's price
(define-public (update-listing-price (token-id uint) (new-price uint))
  (let (
    (listing (unwrap! (map-get? listings { token-id: token-id }) ERR-NOT-FOUND))
  )
    ;; Validations
    (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED)
    (asserts! (get is-active listing) ERR-NOT-FOUND)
    (asserts! (&lt; stacks-block-height (get expiry listing)) ERR-LISTING-EXPIRED)
    (asserts! (&gt; new-price u0) ERR-PRICE-MISMATCHED)
    
    ;; Update the listing price
    (map-set listings
      { token-id: token-id }
      (merge listing { price: new-price })
    )
    
    (ok true)
  )
)
</code></pre></div></div>

<p><strong>Updates the price of an active, unexpired listing.</strong></p>

<p>Validates:</p>

<ul>
  <li>Caller is the seller.</li>
  <li>Listing is still active and not expired.</li>
  <li>New price is positive.</li>
</ul>

<p>Then updates listing with the new price.</p>

<hr />

<h2 id="-admin-function">🏦 Admin Function</h2>

<hr />

<h3 id="-withdraw-platform-fee">💼 <code class="language-plaintext highlighter-rouge">withdraw-platform-fee</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; Administration function to withdraw paltform fee

(define-public (withdraw-platform-fee) 
 (let (
    (fee-amount (var-get total-platform-fees))
 )
 (asserts! (is-eq tx-sender contract-owner) ERR-NOT-AUTHORIZED)
 (asserts! (&gt; fee-amount u0) ERR-INSUFFICIENT-FUNDS)

 ;;Reset counter
 (var-set total-platform-fees u0)

 ;; transfer funds to contract owner
 (as-contract (stx-transfer? fee-amount tx-sender contract-owner))
 )
)
</code></pre></div></div>

<p><strong>Allows the contract owner to withdraw collected platform fees.</strong></p>

<p>Includes:</p>

<ul>
  <li>Validates caller is the contract owner.</li>
  <li>Fee amount is greater than zero.</li>
  <li>Transfers total fees to contract owner.</li>
  <li>Resets fee counter to zero.</li>
</ul>

<h2 id="marketplace-code">MarketPlace code</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; Makrketplace contract where users can list, unlist and can sell their nfts
;; clarity version 3

(define-constant contract-owner tx-sender)
(define-constant PLATFORM-FEE-BPS  u250) ;;2.5% basic fee for now

;;eRROR
(define-constant ERR-NOT-AUTHORIZED (err u401))
(define-constant ERR-LISTING-EXPIRED (err u402))
(define-constant ERR-PRICE-MISMATCHED (err u403))
(define-constant ERR-ALREADY-LISTED (err u404))
(define-constant ERR-NFT-TRANSFER-FAILED (err u405))
(define-constant ERR-NOT-FOUND (err u406))
(define-constant ERR-OWNER-CANNOT-BUY (err u407))
(define-constant ERR-INSUFFICIENT-FUNDS (err u408))

;;Data structures
(define-map listings {token-id: uint} 
    {seller: principal, price: uint, expiry: uint, is-active: bool}
)

;;Track paltform fee
(define-data-var total-platform-fees  uint u0)

;;check if token is listed
(define-read-only (is-listed (token-id uint)) 
    (match (map-get? listings { token-id: token-id})
        listing (and (get is-active listing)
            (&lt; stacks-block-height (get expiry listing)))
        false
    )
)

;;get listing details if active
(define-read-only (get-listing (token-id uint))
    (map-get? listings {token-id: token-id})
)

;;calculate fees for the given amount
(define-read-only (calculate-platform-fee (amount uint)) 
    (/ (* amount PLATFORM-FEE-BPS) u10000)
)
;;get total platform fee of the platform
(define-read-only (get-total-platform-fees) 
    (var-get total-platform-fees)
)

;;listing functions
;; list nft for sale
(define-public (list-nft (token-id uint) (price uint) (expiry uint))
  (let (
    (nft-owner-opt (unwrap! (contract-call? .SimpleNFT get-owner token-id) (err u0)))
  )
    ;; Check if the owner is none
    (asserts! (is-some nft-owner-opt) ERR-NOT-AUTHORIZED)
    (let (
      (nft-owner (unwrap! nft-owner-opt (err u0)))
    )
      ;; Validations
      (asserts! (is-eq tx-sender nft-owner) ERR-NOT-AUTHORIZED)
      (asserts! (&gt; expiry stacks-block-height) ERR-LISTING-EXPIRED)
      (asserts! (&gt; price u0) ERR-PRICE-MISMATCHED)
      (asserts! (not (is-listed token-id)) ERR-ALREADY-LISTED)

      ;; Transfer NFT to this contract (escrow)
      (unwrap! (contract-call? .SimpleNFT transfer token-id tx-sender (as-contract tx-sender)) ERR-NFT-TRANSFER-FAILED)
      
      ;; Create the listing
      (map-set listings
        { token-id: token-id }
        { 
          seller: tx-sender, 
          price: price, 
          expiry: expiry, 
          is-active: true
        }
      )
      
      (ok true)
    )
  )
)

;;cancel a listings
(define-public (cancel-listing (token-id uint))
    (let (
        (listing (unwrap! (map-get? listings { token-id: token-id}) ERR-NOT-FOUND))
    )   
    ;;only seller can cancel
    (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED)
    ;;ensure listing is active
    (asserts! (get is-active listing) ERR-NOT-FOUND)

    ;;Mark lisitng is inactive
    (map-set listings {token-id: token-id} (merge listing {is-active: false}))

    ;;Return nft to seller from escrow
    (as-contract (contract-call? .SimpleNFT transfer token-id tx-sender (get seller listing))))
)

;; Purchase functions

;; buy listed nft
(define-public (buy-nft (token-id uint))
  (let (
    (listing (unwrap! (map-get? listings { token-id: token-id }) ERR-NOT-FOUND))
    (price (get price listing))
    (seller (get seller listing))
    (platform-fee (calculate-platform-fee price))
    (seller-amount (- price platform-fee))
  )
    ;; Check conditions
    (asserts! (get is-active listing) ERR-NOT-FOUND)
    (asserts! (&lt; stacks-block-height (get expiry listing)) ERR-LISTING-EXPIRED)
    (asserts! (not (is-eq tx-sender seller)) ERR-OWNER-CANNOT-BUY)
    
    ;; Update platform fees
    (var-set total-platform-fees (+ (var-get total-platform-fees) platform-fee))
    
    ;; Mark listing as inactive
    (map-set listings
      { token-id: token-id }
      (merge listing { is-active: false })
    )
    
    ;; Pay seller
    (unwrap! (stx-transfer? seller-amount tx-sender seller) ERR-INSUFFICIENT-FUNDS)
    
    ;; Pay platform fee
    (unwrap! (stx-transfer? platform-fee tx-sender contract-owner) ERR-INSUFFICIENT-FUNDS)
    
    ;; Transfer NFT to buyer from escrow
    (as-contract 
      (contract-call? .SimpleNFT transfer token-id tx-sender tx-sender)
    )
  )
)

;; Update a listing's price
(define-public (update-listing-price (token-id uint) (new-price uint))
  (let (
    (listing (unwrap! (map-get? listings { token-id: token-id }) ERR-NOT-FOUND))
  )
    ;; Validations
    (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED)
    (asserts! (get is-active listing) ERR-NOT-FOUND)
    (asserts! (&lt; stacks-block-height (get expiry listing)) ERR-LISTING-EXPIRED)
    (asserts! (&gt; new-price u0) ERR-PRICE-MISMATCHED)
    
    ;; Update the listing price
    (map-set listings
      { token-id: token-id }
      (merge listing { price: new-price })
    )
    
    (ok true)
  )
)

;; Administration function to withdraw paltform fee

(define-public (withdraw-platform-fee) 
 (let (
    (fee-amount (var-get total-platform-fees))
 )
 (asserts! (is-eq tx-sender contract-owner) ERR-NOT-AUTHORIZED)
 (asserts! (&gt; fee-amount u0) ERR-INSUFFICIENT-FUNDS)

 ;;Reset counter
 (var-set total-platform-fees u0)

 ;; transfer funds to contract owner
 (as-contract (stx-transfer? fee-amount tx-sender contract-owner))
 )
)
</code></pre></div></div>
<ul>
  <li>Contract address: ST390VFVZJA4WP7QSZN0RTSGQDAG2P9NPN3X1ATDX.MarketPlace</li>
</ul>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[1. Introduction NFTs—short for non-fungible tokens—are digital assets with unique identifiers. Non-fungible means that the tokens are not equivalent to one another. NFTs are created using Clarity smart contracts, a secure and predictable language designed for high-stakes blockchain applications. The most widely used NFT standard on Stacks is the SIP-009 (Stacks Improvement Proposal 009), which defines the basic functionality and metadata structure required for NFTs on the network. An NFT marketplace is a digital platform where users can mint, buy, sell, and trade NFTs. These marketplaces serve as hubs for creators and collectors to exchange unique digital assets. 2. Prerequisites Familiar with Clarity Clarity Book or Clarity Docs Familiar with Stacks Stacks Docs or Stacks.js Docs Familiar with NFTs and SIP-009 SIP-009 NFT Standard 3. Implementing the NFT MarketPlace To implement the NFT marketplace, I wrote two smart contracts. One is for minting and transfer NFTs using the NFT trait, and the other is for the marketplace, where users can list, unlist, buy, and transfer NFTs between accounts. I will explain all the code in detail and provide the contract address link so you can review it. 3.1 SimpleNFT.clar ;; SimpleNFT - A basic NF contract for stacks blockchain ;; implements sip-009 nft standard ;; clarity version- 3 The contract name is SimpleNFt which implements sip-009 nft standard. The smart contract code is written in clarity version 3. 🔗 Trait Implementation (impl-trait 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-trait.nft-trait) The SIP009 NFT trait on testnet (define-trait sip009-nft-trait ( ;; Last token ID, limited to uint range (get-last-token-id () (response uint uint)) ;; URI for metadata associated with the token (get-token-uri (uint) (response (optional (string-ascii 256)) uint)) ;; Owner of a given token identifier (get-owner (uint) (response (optional principal) uint)) ;; Transfer from the sender to a new principal (transfer (uint principal principal) (response bool uint)) ) ) Purpose: Imports and implements the SIP-009 NFT trait (interface). This ensures the contract conforms to the NFT standard (required functions and behavior). ❗ Error Constants ;; errors (define-constant ERR-NOT-AUTHORIZED (err u100)) (define-constant ERR-NOT-FOUND (err u101)) (define-constant ERR-METADATA-FROZEN (err u102)) Predefined error codes to use throughout the contract for clarity and consistency. 📦 State Variables ;; Data variables (define-data-var contract-owner principal tx-sender) (define-data-var last-token-id uint u0) (define-data-var metadata-frozen bool false) contract-owner: Stores the address of the contract owner. last-token-id: Keeps track of the latest minted token ID. metadata-frozen: Prevents changes to metadata after it’s locked. 🗺️ Data Maps ;; nft metadata mapping (define-map token-uris uint (string-ascii 256)) ;;token ownership (define-map token-owners uint principal) token-uris: Maps token ID to its metadata URI . token-owners: Maps token ID to the wallet address (principal) that owns it. 📖 Read-Only Functions These functions don’t modify blockchain state, only return data. get-last-token-id ;;get the last token id (define-read-only (get-last-token-id) (ok (var-get last-token-id)) ) Returns the latest token ID minted. get-token-uri ;;get token uri (define-read-only (get-token-uri (token-id uint)) (ok (map-get? token-uris token-id)) Returns the metadata URI for a specific token. get-owner ;; get the owner of the specified token (define-read-only (get-owner (token-id uint)) (ok (map-get? token-owners token-id)) ) Returns the owner of a specific token. get-total-supply ;get total supply (define-read-only (get-total-supply) (ok (var-get last-token-id)) ) Since token IDs are sequential, this returns the total NFTs minted. 🚀 Public Functions These change state on the blockchain. mint ;; mint nft (define-public (mint (metadata-uri (string-ascii 256))) (let ( (token-id (+ (var-get last-token-id) u1)) (owner tx-sender) ) (var-set last-token-id token-id) (map-set token-owners token-id owner) (map-set token-uris token-id metadata-uri) ;; (try! (nft-mint? asset-name asset-identifier recipient)) (ok token-id) ) ) Mints a new NFT: Increments token ID. Sets the sender as the owner. Stores the metadata URI. Returns the new token ID. transfer (define-public (transfer (token-id uint) (sender principal) (recipient principal)) (begin (asserts! (is-eq sender tx-sender) ERR-NOT-AUTHORIZED) (asserts! (is-some (map-get? token-owners token-id)) ERR-NOT-FOUND) (asserts! (is-eq (some sender) (map-get? token-owners token-id)) ERR-NOT-AUTHORIZED) (ok (map-set token-owners token-id recipient)) ) ) Transfers a token from one user to another. Includes checks to ensure: Caller is the sender. Token exists. sender is actually the owner. set-token-uri ;; Metadata Management Functions (define-public (set-token-uri (token-id uint) (new-uri (string-ascii 256))) (begin (asserts! (not (var-get metadata-frozen)) ERR-METADATA-FROZEN) (asserts! (is-some (map-get? token-owners token-id)) ERR-NOT-FOUND) (asserts! (is-eq (some tx-sender) (map-get? token-owners token-id)) ERR-NOT-AUTHORIZED) (map-set token-uris token-id new-uri) (ok true) ) ) Allows the owner of a token to update its metadata URI. Only works if metadata is not frozen. freeze-metadata (define-public (freeze-metadata) (begin (asserts! (is-eq tx-sender (var-get contract-owner tx-sender)) ERR-NOT-AUTHORIZED) ;; (var-set metadata-frozen true) (ok true) ) ) Freezes metadata updates globally. Prevents future set-token-uri calls. SimpleNFT ;; SimpleNFT - A basic NF contract for stacks blockchain ;; implements sip-009 nft standard ;; clarity version- 3 (impl-trait 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.nft-trait.nft-trait) ;; errors (define-constant ERR-NOT-AUTHORIZED (err u100)) (define-constant ERR-NOT-FOUND (err u101)) (define-constant ERR-METADATA-FROZEN (err u102)) ;; Data variables (define-data-var last-token-id uint u0) (define-data-var metadata-frozen bool false) ;; nft metadata mapping (define-map token-uris uint (string-ascii 256)) ;;token ownership (define-map token-owners uint principal) ;;SIP-009 functions ;; read only functions ;;get the last token id (define-read-only (get-last-token-id) (ok (var-get last-token-id)) ) ;;get token uri (define-read-only (get-token-uri (token-id uint)) (ok (map-get? token-uris token-id)) ) ;; get the owner of the specified token (define-read-only (get-owner (token-id uint)) (ok (map-get? token-owners token-id)) ) ;;get total supply (define-read-only (get-total-supply) (ok (var-get last-token-id)) ) ;;public functions ;; mint nft (define-public (mint (metadata-uri (string-ascii 256))) (let ( (token-id (+ (var-get last-token-id) u1)) (owner tx-sender) ) (var-set last-token-id token-id) (map-set token-owners token-id owner) (map-set token-uris token-id metadata-uri) ;; (try! (nft-mint? asset-name asset-identifier recipient)) (ok token-id) ) ) (define-public (transfer (token-id uint) (sender principal) (recipient principal)) (begin (asserts! (is-eq sender tx-sender) ERR-NOT-AUTHORIZED) (asserts! (is-some (map-get? token-owners token-id)) ERR-NOT-FOUND) (asserts! (is-eq (some sender) (map-get? token-owners token-id)) ERR-NOT-AUTHORIZED) (ok (map-set token-owners token-id recipient)) ) ) ;; Metadata Management Functions (define-public (set-token-uri (token-id uint) (new-uri (string-ascii 256))) (begin (asserts! (not (var-get metadata-frozen)) ERR-METADATA-FROZEN) (asserts! (is-some (map-get? token-owners token-id)) ERR-NOT-FOUND) (asserts! (is-eq (some tx-sender) (map-get? token-owners token-id)) ERR-NOT-AUTHORIZED) (map-set token-uris token-id new-uri) (ok true) ) ) (define-public (freeze-metadata) (begin (asserts! (is-eq tx-sender tx-sender) ERR-NOT-AUTHORIZED) ;; Replace with your owner check if needed (var-set metadata-frozen true) (ok true) ) ) Contract address: ST390VFVZJA4WP7QSZN0RTSGQDAG2P9NPN3X1ATDX.SimpleNFT 3.2 MarketPlace.clar 🔒 Contract-Level Constants (define-constant contract-owner tx-sender) (define-constant PLATFORM-FEE-BPS u250) ;;2.5% basic fee for now Sets the contract owner at deployment time (you can replace with a specific address for real-world use). Used to restrict sensitive actions (like withdrawing fees). 2.5% platform fee defined in basis points (BPS). 250 BPS = 2.5% of the sale price. ❗ Error Constants These are predefined error codes for common issues: ;;ERROR (define-constant ERR-NOT-AUTHORIZED (err u401)) (define-constant ERR-LISTING-EXPIRED (err u402)) (define-constant ERR-PRICE-MISMATCHED (err u403)) (define-constant ERR-ALREADY-LISTED (err u404)) (define-constant ERR-NFT-TRANSFER-FAILED (err u405)) (define-constant ERR-NOT-FOUND (err u406)) (define-constant ERR-OWNER-CANNOT-BUY (err u407)) (define-constant ERR-INSUFFICIENT-FUNDS (err u408)) 🗺️ Data Structures ;;Data structures (define-map listings {token-id: uint} {seller: principal, price: uint, expiry: uint, is-active: bool} ) Tracks all NFT listings. Keys: token-id, Values: seller info, price, expiry block, and listing status. ;;Track paltform fee (define-data-var total-platform-fees uint u0) Accumulates total platform fees collected from sales. 🧠 Read-Only Functions ✅ is-listed ;check if token is listed (define-read-only (is-listed (token-id uint)) (match (map-get? listings { token-id: token-id}) listing (and (get is-active listing) (&lt; stacks-block-height (get expiry listing))) false ) ) Checks if a token is currently listed and hasn’t expired. Returns true if: Token is in the map, is-active is true, expiry is greater than current block height. 📜 get-listing ;;get listing details if active (define-read-only (get-listing (token-id uint)) (map-get? listings {token-id: token-id}) ) Fetches full listing info for a token if it exists. Returns: Listing object if found (even if expired/inactive), none otherwise. 💸 calculate-platform-fee ;;calculate fees for the given amount (define-read-only (calculate-platform-fee (amount uint)) (/ (* amount PLATFORM-FEE-BPS) u10000) ) Calculates platform fee from total price. Example: If amount = 1000, fee = 2.5% = 25. 💰 get-total-platform-fees ;;get total platform fee of the platform (define-read-only (get-total-platform-fees) (var-get total-platform-fees) ) Returns the accumulated fees collected by the platform so far. 🛍️ Listing Functions 📦 list-nft ;; list nft for sale (define-public (list-nft (token-id uint) (price uint) (expiry uint)) (let ( (nft-owner-opt (unwrap! (contract-call? .SimpleNFT get-owner token-id) (err u0))) ) ;; Check if the owner is none (asserts! (is-some nft-owner-opt) ERR-NOT-AUTHORIZED) (let ( (nft-owner (unwrap! nft-owner-opt (err u0))) ) ;; Validations (asserts! (is-eq tx-sender nft-owner) ERR-NOT-AUTHORIZED) (asserts! (&gt; expiry stacks-block-height) ERR-LISTING-EXPIRED) (asserts! (&gt; price u0) ERR-PRICE-MISMATCHED) (asserts! (not (is-listed token-id)) ERR-ALREADY-LISTED) ;; Transfer NFT to this contract (escrow) (unwrap! (contract-call? .SimpleNFT transfer token-id tx-sender (as-contract tx-sender)) ERR-NFT-TRANSFER-FAILED) ;; Create the listing (map-set listings { token-id: token-id } { seller: tx-sender, price: price, expiry: expiry, is-active: true } ) (ok true) ) ) ) Lists a token for sale and transfers NFT to escrow (Marketplace contract). Includes checks to ensure: Token exists and caller is the owner (via SimpleNFT.get-owner). Price is positive. Expiry is in the future. Token isn’t already listed. NFT is transferred to this contract via SimpleNFT.transfer. Stores listing in the listings map as active. ❌ cancel-listing ;;cancel a listings (define-public (cancel-listing (token-id uint)) (let ( (listing (unwrap! (map-get? listings { token-id: token-id}) ERR-NOT-FOUND)) ) ;;only seller can cancel (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED) ;;ensure listing is active (asserts! (get is-active listing) ERR-NOT-FOUND) ;;Mark lisitng is inactive (map-set listings {token-id: token-id} (merge listing {is-active: false})) ;;Return nft to seller from escrow (as-contract (contract-call? .SimpleNFT transfer token-id tx-sender (get seller listing)))) ) Cancels an active listing and returns NFT to seller. Includes: Validation that caller is the original seller. Checks if listing is still active. Marks the listing inactive. Transfers NFT from contract back to seller using SimpleNFT.transfer. 💵 Purchase Functions 🛒 buy-nft ;; buy listed nft (define-public (buy-nft (token-id uint)) (let ( (listing (unwrap! (map-get? listings { token-id: token-id }) ERR-NOT-FOUND)) (price (get price listing)) (seller (get seller listing)) (platform-fee (calculate-platform-fee price)) (seller-amount (- price platform-fee)) ) ;; Check conditions (asserts! (get is-active listing) ERR-NOT-FOUND) (asserts! (&lt; stacks-block-height (get expiry listing)) ERR-LISTING-EXPIRED) (asserts! (not (is-eq tx-sender seller)) ERR-OWNER-CANNOT-BUY) ;; Update platform fees (var-set total-platform-fees (+ (var-get total-platform-fees) platform-fee)) ;; Mark listing as inactive (map-set listings { token-id: token-id } (merge listing { is-active: false }) ) ;; Pay seller (unwrap! (stx-transfer? seller-amount tx-sender seller) ERR-INSUFFICIENT-FUNDS) ;; Pay platform fee (unwrap! (stx-transfer? platform-fee tx-sender contract-owner) ERR-INSUFFICIENT-FUNDS) ;; Transfer NFT to buyer from escrow (as-contract (contract-call? .SimpleNFT transfer token-id tx-sender tx-sender) ) ) ) Allows a buyer to purchase a listed NFT. Includes: Checks that listing is active, not expired, and buyer isn’t seller. Calculates fee and seller amount. Adds fee to total-platform-fees. Transfers STX to seller and platform. Transfers NFT to buyer from contract using SimpleNFT.transfer. 💲 update-listing-price ;; Update a listing's price (define-public (update-listing-price (token-id uint) (new-price uint)) (let ( (listing (unwrap! (map-get? listings { token-id: token-id }) ERR-NOT-FOUND)) ) ;; Validations (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED) (asserts! (get is-active listing) ERR-NOT-FOUND) (asserts! (&lt; stacks-block-height (get expiry listing)) ERR-LISTING-EXPIRED) (asserts! (&gt; new-price u0) ERR-PRICE-MISMATCHED) ;; Update the listing price (map-set listings { token-id: token-id } (merge listing { price: new-price }) ) (ok true) ) ) Updates the price of an active, unexpired listing. Validates: Caller is the seller. Listing is still active and not expired. New price is positive. Then updates listing with the new price. 🏦 Admin Function 💼 withdraw-platform-fee ;; Administration function to withdraw paltform fee (define-public (withdraw-platform-fee) (let ( (fee-amount (var-get total-platform-fees)) ) (asserts! (is-eq tx-sender contract-owner) ERR-NOT-AUTHORIZED) (asserts! (&gt; fee-amount u0) ERR-INSUFFICIENT-FUNDS) ;;Reset counter (var-set total-platform-fees u0) ;; transfer funds to contract owner (as-contract (stx-transfer? fee-amount tx-sender contract-owner)) ) ) Allows the contract owner to withdraw collected platform fees. Includes: Validates caller is the contract owner. Fee amount is greater than zero. Transfers total fees to contract owner. Resets fee counter to zero. MarketPlace code ;; Makrketplace contract where users can list, unlist and can sell their nfts ;; clarity version 3 (define-constant contract-owner tx-sender) (define-constant PLATFORM-FEE-BPS u250) ;;2.5% basic fee for now ;;eRROR (define-constant ERR-NOT-AUTHORIZED (err u401)) (define-constant ERR-LISTING-EXPIRED (err u402)) (define-constant ERR-PRICE-MISMATCHED (err u403)) (define-constant ERR-ALREADY-LISTED (err u404)) (define-constant ERR-NFT-TRANSFER-FAILED (err u405)) (define-constant ERR-NOT-FOUND (err u406)) (define-constant ERR-OWNER-CANNOT-BUY (err u407)) (define-constant ERR-INSUFFICIENT-FUNDS (err u408)) ;;Data structures (define-map listings {token-id: uint} {seller: principal, price: uint, expiry: uint, is-active: bool} ) ;;Track paltform fee (define-data-var total-platform-fees uint u0) ;;check if token is listed (define-read-only (is-listed (token-id uint)) (match (map-get? listings { token-id: token-id}) listing (and (get is-active listing) (&lt; stacks-block-height (get expiry listing))) false ) ) ;;get listing details if active (define-read-only (get-listing (token-id uint)) (map-get? listings {token-id: token-id}) ) ;;calculate fees for the given amount (define-read-only (calculate-platform-fee (amount uint)) (/ (* amount PLATFORM-FEE-BPS) u10000) ) ;;get total platform fee of the platform (define-read-only (get-total-platform-fees) (var-get total-platform-fees) ) ;;listing functions ;; list nft for sale (define-public (list-nft (token-id uint) (price uint) (expiry uint)) (let ( (nft-owner-opt (unwrap! (contract-call? .SimpleNFT get-owner token-id) (err u0))) ) ;; Check if the owner is none (asserts! (is-some nft-owner-opt) ERR-NOT-AUTHORIZED) (let ( (nft-owner (unwrap! nft-owner-opt (err u0))) ) ;; Validations (asserts! (is-eq tx-sender nft-owner) ERR-NOT-AUTHORIZED) (asserts! (&gt; expiry stacks-block-height) ERR-LISTING-EXPIRED) (asserts! (&gt; price u0) ERR-PRICE-MISMATCHED) (asserts! (not (is-listed token-id)) ERR-ALREADY-LISTED) ;; Transfer NFT to this contract (escrow) (unwrap! (contract-call? .SimpleNFT transfer token-id tx-sender (as-contract tx-sender)) ERR-NFT-TRANSFER-FAILED) ;; Create the listing (map-set listings { token-id: token-id } { seller: tx-sender, price: price, expiry: expiry, is-active: true } ) (ok true) ) ) ) ;;cancel a listings (define-public (cancel-listing (token-id uint)) (let ( (listing (unwrap! (map-get? listings { token-id: token-id}) ERR-NOT-FOUND)) ) ;;only seller can cancel (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED) ;;ensure listing is active (asserts! (get is-active listing) ERR-NOT-FOUND) ;;Mark lisitng is inactive (map-set listings {token-id: token-id} (merge listing {is-active: false})) ;;Return nft to seller from escrow (as-contract (contract-call? .SimpleNFT transfer token-id tx-sender (get seller listing)))) ) ;; Purchase functions ;; buy listed nft (define-public (buy-nft (token-id uint)) (let ( (listing (unwrap! (map-get? listings { token-id: token-id }) ERR-NOT-FOUND)) (price (get price listing)) (seller (get seller listing)) (platform-fee (calculate-platform-fee price)) (seller-amount (- price platform-fee)) ) ;; Check conditions (asserts! (get is-active listing) ERR-NOT-FOUND) (asserts! (&lt; stacks-block-height (get expiry listing)) ERR-LISTING-EXPIRED) (asserts! (not (is-eq tx-sender seller)) ERR-OWNER-CANNOT-BUY) ;; Update platform fees (var-set total-platform-fees (+ (var-get total-platform-fees) platform-fee)) ;; Mark listing as inactive (map-set listings { token-id: token-id } (merge listing { is-active: false }) ) ;; Pay seller (unwrap! (stx-transfer? seller-amount tx-sender seller) ERR-INSUFFICIENT-FUNDS) ;; Pay platform fee (unwrap! (stx-transfer? platform-fee tx-sender contract-owner) ERR-INSUFFICIENT-FUNDS) ;; Transfer NFT to buyer from escrow (as-contract (contract-call? .SimpleNFT transfer token-id tx-sender tx-sender) ) ) ) ;; Update a listing's price (define-public (update-listing-price (token-id uint) (new-price uint)) (let ( (listing (unwrap! (map-get? listings { token-id: token-id }) ERR-NOT-FOUND)) ) ;; Validations (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED) (asserts! (get is-active listing) ERR-NOT-FOUND) (asserts! (&lt; stacks-block-height (get expiry listing)) ERR-LISTING-EXPIRED) (asserts! (&gt; new-price u0) ERR-PRICE-MISMATCHED) ;; Update the listing price (map-set listings { token-id: token-id } (merge listing { price: new-price }) ) (ok true) ) ) ;; Administration function to withdraw paltform fee (define-public (withdraw-platform-fee) (let ( (fee-amount (var-get total-platform-fees)) ) (asserts! (is-eq tx-sender contract-owner) ERR-NOT-AUTHORIZED) (asserts! (&gt; fee-amount u0) ERR-INSUFFICIENT-FUNDS) ;;Reset counter (var-set total-platform-fees u0) ;; transfer funds to contract owner (as-contract (stx-transfer? fee-amount tx-sender contract-owner)) ) ) Contract address: ST390VFVZJA4WP7QSZN0RTSGQDAG2P9NPN3X1ATDX.MarketPlace]]></summary></entry><entry><title type="html">Semi Fungible token (SFT)</title><link href="https://basanta11subedi.github.io/blog/post-SFT/" rel="alternate" type="text/html" title="Semi Fungible token (SFT)" /><published>2025-05-28T00:00:00+00:00</published><updated>2025-05-28T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-SFT</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-SFT/"><![CDATA[<p>Semi-Fungible Tokens, or SFTs, are digital assets that sit between fungible and
non-fungible tokens. Fungible tokens are directly interchangeable, can be
received, sent, and divided. Non-fungible tokens each have a unique identifier
that distinguishes them from each other. Semi-fungible tokens have both an
identifier and an amount. This SIP describes the SFT trait and provides a
reference implementation.</p>

<h1 id="introduction">Introduction</h1>

<p>Digital assets commonly fall in one of two categories; namely, they are either
fungible or non-fungible. Fungible tokens are assets like the native Stacks
Token (STX), stablecoins, and so on. Non-Fungible Tokens (NFTs) are tokens
expressed as digital artwork and other use-cases that demand them to be globally
unique. However, not all asset classes can be represented as either exclusively
fungible or non-fungible tokens. This is where semi-fungible tokens come in.</p>

<p>Semi-fungible tokens are a combination of the aforementioned digital asset types
in that they have both an identifier and an amount. A single semi-fungible token
class can therefore represent a multitude of digital assets within a single
contract. A user may own ten tokens of ID 1 and twenty tokens of ID 2, for
example. It effectively means that one contract can represent any combination of
fungible and non-fungible tokens.</p>

<p>Some real-world examples can highlight the value and use-cases of semi-fungible
tokens. People who collect trading cards or postage stamps will know that not
all of them are of equal value, although there may be more than one of a
specific kind. Video games can feature in-game items that have different
economic values compared to others. There are many more such parallels to be
found.</p>

<p>Semi-fungible tokens give operators the ability to create new token classes at
will. They no longer need to deploy a new contract every time new token type is
introduced. It greatly simplifies the flow for applications that require many
new tokens and token types to come into existence.</p>

<p>Benefits of using semi-fungible tokens:</p>

<ul>
  <li>Art NFTs can have series and be grouped in collections.</li>
  <li>Games can have their in-game currencies and items easily represented.</li>
  <li>DeFi protocols can leverage SFTs to transfer many tokens and settle multiple
orders at once.</li>
  <li>Easy bulk trades and transfers in a single contract call, saving on
transaction fees.</li>
</ul>

<h1 id="specification">Specification</h1>

<p>The Semi-Fungible Token trait, <code class="language-plaintext highlighter-rouge">sip013-semi-fungible-token-trait</code>, has 8
functions:</p>

<h2 id="trait-functions">Trait functions</h2>

<h3 id="balance">Balance</h3>

<p><code class="language-plaintext highlighter-rouge">(get-balance ((token-id uint) (who principal)) (response uint uint))</code></p>

<p>Returns the token type balance <code class="language-plaintext highlighter-rouge">token-id</code> of a specific principal <code class="language-plaintext highlighter-rouge">who</code> as an
unsigned integer wrapped in an <code class="language-plaintext highlighter-rouge">ok</code> response. It has to respond with <code class="language-plaintext highlighter-rouge">u0</code> if the
principal does not have a balance of the specified token or if no token with
<code class="language-plaintext highlighter-rouge">token-id</code> exists. The function should never return an <code class="language-plaintext highlighter-rouge">err</code> response and is
recommended to be defined as read-only.</p>

<h3 id="overall-balance">Overall balance</h3>

<p><code class="language-plaintext highlighter-rouge">(get-overall-balance ((who principal)) (response uint uint))</code></p>

<p>Returns the overall SFT balance of a specific principal <code class="language-plaintext highlighter-rouge">who</code>. This is the sum
of all the token type balances of that principal. The function has to respond
with a zero value of <code class="language-plaintext highlighter-rouge">u0</code> if the principal does not have any balance. It should
never return an <code class="language-plaintext highlighter-rouge">err</code> response and is recommended to be defined as read-only.</p>

<h3 id="total-supply">Total supply</h3>

<p><code class="language-plaintext highlighter-rouge">(get-total-supply ((token-id uint)) (response uint uint))</code></p>

<p>Returns the total supply of a token type. If the token type has no supply or
does not exist, the function should respond with <code class="language-plaintext highlighter-rouge">u0</code>. It should never return an
<code class="language-plaintext highlighter-rouge">err</code> response and is recommended to be defined as read-only.</p>

<h3 id="overall-supply">Overall supply</h3>

<p><code class="language-plaintext highlighter-rouge">(get-overall-supply () (response uint uint))</code></p>

<p>Returns the overall supply of the SFT. This is the sum of all token type
supplies. The function should never return an <code class="language-plaintext highlighter-rouge">err</code> response and is recommended
to be defined as read-only.</p>

<h3 id="decimals">Decimals</h3>

<p><code class="language-plaintext highlighter-rouge">(get-decimals ((token-id uint)) (response uint uint))</code></p>

<p>Returns the decimal places of a token type. This is purely for display reasons,
where external applications may read this value to provide a better user
experience. The ability to specify decimals for a token type can be useful for
applications that represent different kinds of assets using one SFT. For
example, a game may have an in-game currency with two decimals and a fuel
commodity expressed in litres with four decimals.</p>

<h3 id="token-uri">Token URI</h3>

<p><code class="language-plaintext highlighter-rouge">(get-token-uri ((token-id uint)) (response (optional (string-ascii 256)) uint))</code></p>

<p>Returns an optional ASCII string that is a valid URI which resolves to this
token type’s metadata. These files can provide off-chain metadata about that
particular token type, like descriptions, imagery, or any other information. The
exact structure of the metadata is out of scope for this SIP. However, the
metadata file should be in JSON format and should include a <code class="language-plaintext highlighter-rouge">sip</code> property
containing a number:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
	"sip": 16
	// ... any other properties
}

</code></pre></div></div>

<p>Applications consuming these metadata files can base display capabilities on the
<code class="language-plaintext highlighter-rouge">sip</code> value. It should refer to a SIP number describing a JSON metadata
standard.</p>

<h3 id="transfer">Transfer</h3>

<p><code class="language-plaintext highlighter-rouge">(transfer ((token-id uint) (amount uint) (sender principal) (recipient principal)) (response bool uint))</code></p>

<p>Transfer a token from the sender to the recipient. It is recommended to leverage
Clarity primitives like <code class="language-plaintext highlighter-rouge">ft-transfer?</code> to help safeguard users. The function
should return <code class="language-plaintext highlighter-rouge">(ok true)</code> on success or an <code class="language-plaintext highlighter-rouge">err</code> response containing an unsigned
integer on failure. The failure codes follow the existing conventions of
<code class="language-plaintext highlighter-rouge">stx-transfer?</code> and <code class="language-plaintext highlighter-rouge">ft-transfer?</code>.</p>

<table>
  <thead>
    <tr>
      <th>Error code</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u1</code></td>
      <td>The sender has insufficient balance.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u2</code></td>
      <td>The sender and recipient are the same principal.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u3</code></td>
      <td>Amount is <code class="language-plaintext highlighter-rouge">u0</code>.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u4</code></td>
      <td>The sender is not authorised to transfer tokens.</td>
    </tr>
  </tbody>
</table>

<p>Error code <code class="language-plaintext highlighter-rouge">u4</code> is broad and may be returned under different cirumstances. For
example, a token  contract with an allowance mechanism can return <code class="language-plaintext highlighter-rouge">(err u4)</code>
when the <code class="language-plaintext highlighter-rouge">sender</code> parameter has no allowance for the specified token amount or
if the sender is not equal to <code class="language-plaintext highlighter-rouge">tx-sender</code> or <code class="language-plaintext highlighter-rouge">contract-owner</code>. A token contract
without an allowance mechanism can return <code class="language-plaintext highlighter-rouge">(err u4)</code> simply when the <code class="language-plaintext highlighter-rouge">sender</code> is
not what is expected.</p>

<p>Since it is possible for smart contracts to own tokens, it is recommended to
check for both <code class="language-plaintext highlighter-rouge">tx-sender</code> and <code class="language-plaintext highlighter-rouge">contract-caller</code> as it allows smart contracts to
transfer tokens it owns without having to resort to using <code class="language-plaintext highlighter-rouge">as-contract</code>. Such a
guard can be constructed as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(asserts! (or (is-eq sender tx-sender) (is-eq sender contract-caller)) (err u4))

</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">transfer</code> function should emit a special transfer event, as detailed in the
Events section of this document.</p>

<h3 id="transfer-with-memo">Transfer with memo</h3>

<p><code class="language-plaintext highlighter-rouge">(transfer-memo ((token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34))) (response bool uint))</code></p>

<p>Transfer a token from the sender to the recipient and emit a memo. This function
follows the exact same procedure as <code class="language-plaintext highlighter-rouge">transfer</code> but emits the provided memo via
<code class="language-plaintext highlighter-rouge">(print memo)</code>. The memo event should be the final event emitted by the
contract. (See also the events section of this document below.)</p>

<h2 id="trait-definition">Trait definition</h2>

<p>A definition of the trait is provided below.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-trait sip013-semi-fungible-token-trait
	(
		;; Get a token type balance of the passed principal.
		(get-balance (uint principal) (response uint uint))

		;; Get the total SFT balance of the passed principal.
		(get-overall-balance (principal) (response uint uint))

		;; Get the current total supply of a token type.
		(get-total-supply (uint) (response uint uint))

		;; Get the overall SFT supply.
		(get-overall-supply () (response uint uint))

		;; Get the number of decimal places of a token type.
		(get-decimals (uint) (response uint uint))

		;; Get an optional token URI that represents metadata for a specific token.
		(get-token-uri (uint) (response (optional (string-ascii 256)) uint))

		;; Transfer from one principal to another.
		(transfer (uint uint principal principal) (response bool uint))

		;; Transfer from one principal to another with a memo.
		(transfer-memo (uint uint principal principal (buff 34)) (response bool uint))
	)
)

</code></pre></div></div>

<h2 id="events">Events</h2>

<p>Semi-fungible token contracts should emit custom events in certain situations
via <code class="language-plaintext highlighter-rouge">print</code>. These events should be emitted after any built-in token events
(such as those emitted by <code class="language-plaintext highlighter-rouge">ft-transfer?</code>) and before the memo in the case of
<code class="language-plaintext highlighter-rouge">transfer-memo</code> and <code class="language-plaintext highlighter-rouge">transfer-many-memo</code>.</p>

<table>
  <thead>
    <tr>
      <th>Event name</th>
      <th>Tuple structure</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sft_transfer</code></td>
      <td><code class="language-plaintext highlighter-rouge">{type: "sft_transfer", token-id: uint, amount: uint, sender: principal, recipient: principal}</code></td>
      <td>Emitted when tokens are transferred.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sft_mint</code></td>
      <td><code class="language-plaintext highlighter-rouge">{type: "sft_mint", token-id: uint, amount: uint, recipient: principal}</code></td>
      <td>Emitted when new tokens are minted.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">sft_burn</code></td>
      <td><code class="language-plaintext highlighter-rouge">{type: "sft_burn", token-id: uint, amount: uint, sender: principal}</code></td>
      <td>Emitted when tokens are burned.</td>
    </tr>
  </tbody>
</table>

<h2 id="use-of-native-asset-functions">Use of native asset functions</h2>

<p>Contract implementers should always use the built-in native assets that are
provided as Clarity primitives whenever possible. This allows clients to use
Post Conditions (explained below) and takes advantage of other benefits like
native events and asset balances. However, there are no language primitives
specific to semi-fungible tokens. The reference implementation included in this
SIP therefore leverages the primitives to the extent that Clarity allows for.</p>

<p>The recommended native asset primitives to use:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">define-fungible-token</code></li>
  <li><code class="language-plaintext highlighter-rouge">ft-burn?</code></li>
  <li><code class="language-plaintext highlighter-rouge">ft-get-balance</code></li>
  <li><code class="language-plaintext highlighter-rouge">ft-get-supply</code></li>
  <li><code class="language-plaintext highlighter-rouge">ft-mint?</code></li>
  <li><code class="language-plaintext highlighter-rouge">ft-transfer?</code></li>
  <li><code class="language-plaintext highlighter-rouge">define-non-fungible-token</code></li>
  <li><code class="language-plaintext highlighter-rouge">nft-burn?</code></li>
  <li><code class="language-plaintext highlighter-rouge">nft-mint?</code></li>
  <li><code class="language-plaintext highlighter-rouge">nft-transfer?</code></li>
</ul>

<h2 id="implementing-in-wallets-and-other-applications">Implementing in wallets and other applications</h2>

<p>Applications that interact with semi-fungible token contracts should validate if
those contracts implement the SFT trait. If they do, then the application can
use the interface described in this SIP for making transfers and getting other
token information.</p>

<p>All of the functions in this trait return the <code class="language-plaintext highlighter-rouge">response</code> type, which is a
requirement of trait definitions in Clarity. However, some of these functions
should be “fail-proof”, in the sense that they should never return an error. The
“fail-proof” functions are those that have been recommended as read-only. If a
contract that implements this trait returns an error for these functions, it may
be an indication of a faulty contract, and consumers of those contracts should
proceed with caution.</p>

<h2 id="use-of-post-conditions">Use of post conditions</h2>

<p>The Stacks blockchain includes a feature known as Post Conditions. By defining
post conditions, users can create transactions that include pre-defined
guarantees about what might happen in a contract. These post conditions can also
be used to provide guarantees for custom fungible and non-fungible tokens that
were defined using built-in Clarity primitives.</p>

<p>There are no Clarity primitive counterparts for semi-fungible tokens, but
contract developers can leverage a combination of post conditions to achieve the
same result.</p>

<p>There are two factors that should be checked by post conditions:</p>

<ol>
  <li>The amount of semi-fungible tokens being transferred.</li>
  <li>The token ID of the semi-fungible token being transferred.</li>
</ol>

<p>To that end, it is recommended that developers use both Clarity primitives in
their design. Semi-fungible token contracts can achieve complete post condition
coverage by using both <code class="language-plaintext highlighter-rouge">define-fungible-token</code> and <code class="language-plaintext highlighter-rouge">define-non-fungible-token</code>.</p>

<p>A minimal and sufficient strategy that provides full post condition coverage is
to create a “burn-and-mint” mechanism for token creation and transfers. Such an
SFT contract tracks quantities using an internal fungible token and token IDs
using an internal non-fungible token. Since token identifiers for assets defined
by <code class="language-plaintext highlighter-rouge">define-non-fungible-token</code> need to be unique, an additional component is
added to ensure token IDs can be expressed per owner. (As SFTs may have a
quantity of a certain token ID that is larger than one.) The token ID type
identifier thus becomes <code class="language-plaintext highlighter-rouge">{token-id: uint, owner: principal}</code>. Wallet software
can then easily determine the post conditions for the amount as well as the
token ID.</p>

<p>An example of a burn-and-mint mechanism is provided below. The reference
implementation at the end of the document features a full SFT contract that
includes burn-and-mint.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-fungible-token semi-fungible-token)
(define-non-fungible-token semi-fungible-token-id {token-id: uint, owner: principal})

(define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal))
	(begin
		;; &lt;guards&gt;
		;; &lt;token transfer logic&gt;
		(try! (tag-nft-token-id {token-id: token-id, owner: sender}))
		(try! (tag-nft-token-id {token-id: token-id, owner: recipient}))
		;; &lt;balance updates&gt;
		(print {type: "sft_transfer", token-id: token-id, amount: amount, sender: sender, recipient: recipient})
		(ok true)
	)
)

(define-private (tag-nft-token-id (nft-token-id {token-id: uint, owner: principal}))
	(begin
		(and
			(is-some (nft-get-owner? semi-fungible-token-id nft-token-id))
			(try! (nft-burn? semi-fungible-token-id nft-token-id (get owner nft-token-id)))
		)
		(nft-mint? semi-fungible-token-id nft-token-id (get owner nft-token-id))
	)
)

</code></pre></div></div>

<h2 id="post-condition-strategies">Post Condition strategies</h2>

<p>For strategies on how to best guard a semi-fungible token contract with post
conditions, see the reference implementation included with SIP (contained in
<a href="https://www.notion.so/SIP-013-001.tar.gz">SIP-013-001.tar.gz</a>), or by following the link at the end
of this document.</p>

<h1 id="optional-send-many-specification">Optional send-many specification</h1>

<p>SIP013 Semi-fungible tokens can also optionally implement the trait
<code class="language-plaintext highlighter-rouge">sip013-send-many-trait</code> to offer a built-in “send-many” features for bulk token
transfers. Adding this to the token contract itself may have runtime cost
benefits as of Stacks 2.0. The send-many trait contains 2 additional functions.</p>

<h2 id="send-many-functions">Send-many functions</h2>

<h3 id="bulk-transfers">Bulk transfers</h3>

<p><code class="language-plaintext highlighter-rouge">(transfer-many ((transfers (list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal}))) (response bool uint))</code></p>

<p>Transfer many tokens in one contract call. Each transfer should follow the exact
same procedure as if it were an individual <code class="language-plaintext highlighter-rouge">transfer</code> call. The whole function
call should fail with an <code class="language-plaintext highlighter-rouge">err</code> response if one of the transfers fails.</p>

<h3 id="bulk-transfers-with-memos">Bulk transfers with memos</h3>

<p><code class="language-plaintext highlighter-rouge">(transfer-many-memo ((transfers (list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)}))) (response bool uint))</code></p>

<p>Transfer many tokens in one contract call and emit a memo for each. This
function follows the same procedure as <code class="language-plaintext highlighter-rouge">transfer-many</code> but will emit the memo
contained in the tuple after each transfer. The whole function call should fail
with an <code class="language-plaintext highlighter-rouge">err</code> response if one of the transfers fails.</p>

<h2 id="send-many-trait-definition">Send-many trait definition</h2>

<p>A definition of the optional send-many trait is provided below.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-trait sip013-transfer-many-trait
	(
		;; Transfer many tokens at once.
		(transfer-many ((list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal})) (response bool uint))

		;; Transfer many tokens at once with memos.
		(transfer-many-memo ((list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)})) (response bool uint))
	)
)

</code></pre></div></div>

<h1 id="related-work">Related work</h1>

<h2 id="ethereum-erc1155">Ethereum ERC1155</h2>

<p>This Semi-Fungible Token standard is similar to the
<a href="https://eips.ethereum.org/EIPS/eip-1155">EIP-1155</a> standard found in the
Ethereum/EVM space. An ERC1155 token is a semi-fungible token that admits both a
token ID as well as a supply per token ID, just like SIP013. They differ in that
the ERC1155 standard describes an approval mechanism as well as “safe transfer”
functions that are specific to Ethereum/EVM. Although the biggest difference is
the requirement of post condition support, a mechanism that does not exist in
Ethereum.</p>

<h1 id="trait-deployments">Trait deployments</h1>

<h2 id="mainnet">Mainnet</h2>

<ul>
  <li>Token trait: <a href="https://explorer.stacks.co/txid/0x7e9d8bac5157ab0366089d00a40a2a83926314ab08807ab3efa87ebc96d9e20a?chain=mainnet">SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-semi-fungible-token-trait</a></li>
  <li>Send-many trait: <a href="https://explorer.stacks.co/txid/0x88457278a61b7e59c8a19704932eebb7b46817e0bbd3235436a1d72c956db19c?chain=mainnet">SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-transfer-many-trait</a></li>
</ul>

<h2 id="testnet">Testnet</h2>

<ul>
  <li>Token trait: <a href="https://explorer.stacks.co/txid/0x37e846cce0d31f34be06d969efbb6ff413308066eefffa0bf1a8669bd4be0a05?chain=testnet">STDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTJTTH7YB.sip013-semi-fungible-token-trait</a></li>
  <li>Send-many trait: <a href="https://explorer.stacks.co/txid/0x81ec048b187137ade2fb9519375d22ec96c271d114e79c2d44018434e9009911?chain=testnet">STDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTJTTH7YB.sip013-transfer-many-trait</a></li>
</ul>

<h1 id="activation">Activation</h1>

<p>These traits will be considered activated when they are deployed to mainnet
and 3 different implementations of the main trait have been deployed to mainnet,
no later than Bitcoin block 769,950. Additionally, no revisions to the traits
were made after Bitcoin block 756,810.</p>

<h1 id="reference">Reference</h1>

<ul>
  <li>https://github.com/MarvinJanssen/stx-semi-fungible-token</li>
</ul>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[Semi-Fungible Tokens, or SFTs, are digital assets that sit between fungible and non-fungible tokens. Fungible tokens are directly interchangeable, can be received, sent, and divided. Non-fungible tokens each have a unique identifier that distinguishes them from each other. Semi-fungible tokens have both an identifier and an amount. This SIP describes the SFT trait and provides a reference implementation. Introduction Digital assets commonly fall in one of two categories; namely, they are either fungible or non-fungible. Fungible tokens are assets like the native Stacks Token (STX), stablecoins, and so on. Non-Fungible Tokens (NFTs) are tokens expressed as digital artwork and other use-cases that demand them to be globally unique. However, not all asset classes can be represented as either exclusively fungible or non-fungible tokens. This is where semi-fungible tokens come in. Semi-fungible tokens are a combination of the aforementioned digital asset types in that they have both an identifier and an amount. A single semi-fungible token class can therefore represent a multitude of digital assets within a single contract. A user may own ten tokens of ID 1 and twenty tokens of ID 2, for example. It effectively means that one contract can represent any combination of fungible and non-fungible tokens. Some real-world examples can highlight the value and use-cases of semi-fungible tokens. People who collect trading cards or postage stamps will know that not all of them are of equal value, although there may be more than one of a specific kind. Video games can feature in-game items that have different economic values compared to others. There are many more such parallels to be found. Semi-fungible tokens give operators the ability to create new token classes at will. They no longer need to deploy a new contract every time new token type is introduced. It greatly simplifies the flow for applications that require many new tokens and token types to come into existence. Benefits of using semi-fungible tokens: Art NFTs can have series and be grouped in collections. Games can have their in-game currencies and items easily represented. DeFi protocols can leverage SFTs to transfer many tokens and settle multiple orders at once. Easy bulk trades and transfers in a single contract call, saving on transaction fees. Specification The Semi-Fungible Token trait, sip013-semi-fungible-token-trait, has 8 functions: Trait functions Balance (get-balance ((token-id uint) (who principal)) (response uint uint)) Returns the token type balance token-id of a specific principal who as an unsigned integer wrapped in an ok response. It has to respond with u0 if the principal does not have a balance of the specified token or if no token with token-id exists. The function should never return an err response and is recommended to be defined as read-only. Overall balance (get-overall-balance ((who principal)) (response uint uint)) Returns the overall SFT balance of a specific principal who. This is the sum of all the token type balances of that principal. The function has to respond with a zero value of u0 if the principal does not have any balance. It should never return an err response and is recommended to be defined as read-only. Total supply (get-total-supply ((token-id uint)) (response uint uint)) Returns the total supply of a token type. If the token type has no supply or does not exist, the function should respond with u0. It should never return an err response and is recommended to be defined as read-only. Overall supply (get-overall-supply () (response uint uint)) Returns the overall supply of the SFT. This is the sum of all token type supplies. The function should never return an err response and is recommended to be defined as read-only. Decimals (get-decimals ((token-id uint)) (response uint uint)) Returns the decimal places of a token type. This is purely for display reasons, where external applications may read this value to provide a better user experience. The ability to specify decimals for a token type can be useful for applications that represent different kinds of assets using one SFT. For example, a game may have an in-game currency with two decimals and a fuel commodity expressed in litres with four decimals. Token URI (get-token-uri ((token-id uint)) (response (optional (string-ascii 256)) uint)) Returns an optional ASCII string that is a valid URI which resolves to this token type’s metadata. These files can provide off-chain metadata about that particular token type, like descriptions, imagery, or any other information. The exact structure of the metadata is out of scope for this SIP. However, the metadata file should be in JSON format and should include a sip property containing a number: { "sip": 16 // ... any other properties } Applications consuming these metadata files can base display capabilities on the sip value. It should refer to a SIP number describing a JSON metadata standard. Transfer (transfer ((token-id uint) (amount uint) (sender principal) (recipient principal)) (response bool uint)) Transfer a token from the sender to the recipient. It is recommended to leverage Clarity primitives like ft-transfer? to help safeguard users. The function should return (ok true) on success or an err response containing an unsigned integer on failure. The failure codes follow the existing conventions of stx-transfer? and ft-transfer?. Error code Description u1 The sender has insufficient balance. u2 The sender and recipient are the same principal. u3 Amount is u0. u4 The sender is not authorised to transfer tokens. Error code u4 is broad and may be returned under different cirumstances. For example, a token contract with an allowance mechanism can return (err u4) when the sender parameter has no allowance for the specified token amount or if the sender is not equal to tx-sender or contract-owner. A token contract without an allowance mechanism can return (err u4) simply when the sender is not what is expected. Since it is possible for smart contracts to own tokens, it is recommended to check for both tx-sender and contract-caller as it allows smart contracts to transfer tokens it owns without having to resort to using as-contract. Such a guard can be constructed as follows: (asserts! (or (is-eq sender tx-sender) (is-eq sender contract-caller)) (err u4)) The transfer function should emit a special transfer event, as detailed in the Events section of this document. Transfer with memo (transfer-memo ((token-id uint) (amount uint) (sender principal) (recipient principal) (memo (buff 34))) (response bool uint)) Transfer a token from the sender to the recipient and emit a memo. This function follows the exact same procedure as transfer but emits the provided memo via (print memo). The memo event should be the final event emitted by the contract. (See also the events section of this document below.) Trait definition A definition of the trait is provided below. (define-trait sip013-semi-fungible-token-trait ( ;; Get a token type balance of the passed principal. (get-balance (uint principal) (response uint uint)) ;; Get the total SFT balance of the passed principal. (get-overall-balance (principal) (response uint uint)) ;; Get the current total supply of a token type. (get-total-supply (uint) (response uint uint)) ;; Get the overall SFT supply. (get-overall-supply () (response uint uint)) ;; Get the number of decimal places of a token type. (get-decimals (uint) (response uint uint)) ;; Get an optional token URI that represents metadata for a specific token. (get-token-uri (uint) (response (optional (string-ascii 256)) uint)) ;; Transfer from one principal to another. (transfer (uint uint principal principal) (response bool uint)) ;; Transfer from one principal to another with a memo. (transfer-memo (uint uint principal principal (buff 34)) (response bool uint)) ) ) Events Semi-fungible token contracts should emit custom events in certain situations via print. These events should be emitted after any built-in token events (such as those emitted by ft-transfer?) and before the memo in the case of transfer-memo and transfer-many-memo. Event name Tuple structure Description sft_transfer {type: "sft_transfer", token-id: uint, amount: uint, sender: principal, recipient: principal} Emitted when tokens are transferred. sft_mint {type: "sft_mint", token-id: uint, amount: uint, recipient: principal} Emitted when new tokens are minted. sft_burn {type: "sft_burn", token-id: uint, amount: uint, sender: principal} Emitted when tokens are burned. Use of native asset functions Contract implementers should always use the built-in native assets that are provided as Clarity primitives whenever possible. This allows clients to use Post Conditions (explained below) and takes advantage of other benefits like native events and asset balances. However, there are no language primitives specific to semi-fungible tokens. The reference implementation included in this SIP therefore leverages the primitives to the extent that Clarity allows for. The recommended native asset primitives to use: define-fungible-token ft-burn? ft-get-balance ft-get-supply ft-mint? ft-transfer? define-non-fungible-token nft-burn? nft-mint? nft-transfer? Implementing in wallets and other applications Applications that interact with semi-fungible token contracts should validate if those contracts implement the SFT trait. If they do, then the application can use the interface described in this SIP for making transfers and getting other token information. All of the functions in this trait return the response type, which is a requirement of trait definitions in Clarity. However, some of these functions should be “fail-proof”, in the sense that they should never return an error. The “fail-proof” functions are those that have been recommended as read-only. If a contract that implements this trait returns an error for these functions, it may be an indication of a faulty contract, and consumers of those contracts should proceed with caution. Use of post conditions The Stacks blockchain includes a feature known as Post Conditions. By defining post conditions, users can create transactions that include pre-defined guarantees about what might happen in a contract. These post conditions can also be used to provide guarantees for custom fungible and non-fungible tokens that were defined using built-in Clarity primitives. There are no Clarity primitive counterparts for semi-fungible tokens, but contract developers can leverage a combination of post conditions to achieve the same result. There are two factors that should be checked by post conditions: The amount of semi-fungible tokens being transferred. The token ID of the semi-fungible token being transferred. To that end, it is recommended that developers use both Clarity primitives in their design. Semi-fungible token contracts can achieve complete post condition coverage by using both define-fungible-token and define-non-fungible-token. A minimal and sufficient strategy that provides full post condition coverage is to create a “burn-and-mint” mechanism for token creation and transfers. Such an SFT contract tracks quantities using an internal fungible token and token IDs using an internal non-fungible token. Since token identifiers for assets defined by define-non-fungible-token need to be unique, an additional component is added to ensure token IDs can be expressed per owner. (As SFTs may have a quantity of a certain token ID that is larger than one.) The token ID type identifier thus becomes {token-id: uint, owner: principal}. Wallet software can then easily determine the post conditions for the amount as well as the token ID. An example of a burn-and-mint mechanism is provided below. The reference implementation at the end of the document features a full SFT contract that includes burn-and-mint. (define-fungible-token semi-fungible-token) (define-non-fungible-token semi-fungible-token-id {token-id: uint, owner: principal}) (define-public (transfer (token-id uint) (amount uint) (sender principal) (recipient principal)) (begin ;; &lt;guards&gt; ;; &lt;token transfer logic&gt; (try! (tag-nft-token-id {token-id: token-id, owner: sender})) (try! (tag-nft-token-id {token-id: token-id, owner: recipient})) ;; &lt;balance updates&gt; (print {type: "sft_transfer", token-id: token-id, amount: amount, sender: sender, recipient: recipient}) (ok true) ) ) (define-private (tag-nft-token-id (nft-token-id {token-id: uint, owner: principal})) (begin (and (is-some (nft-get-owner? semi-fungible-token-id nft-token-id)) (try! (nft-burn? semi-fungible-token-id nft-token-id (get owner nft-token-id))) ) (nft-mint? semi-fungible-token-id nft-token-id (get owner nft-token-id)) ) ) Post Condition strategies For strategies on how to best guard a semi-fungible token contract with post conditions, see the reference implementation included with SIP (contained in SIP-013-001.tar.gz), or by following the link at the end of this document. Optional send-many specification SIP013 Semi-fungible tokens can also optionally implement the trait sip013-send-many-trait to offer a built-in “send-many” features for bulk token transfers. Adding this to the token contract itself may have runtime cost benefits as of Stacks 2.0. The send-many trait contains 2 additional functions. Send-many functions Bulk transfers (transfer-many ((transfers (list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal}))) (response bool uint)) Transfer many tokens in one contract call. Each transfer should follow the exact same procedure as if it were an individual transfer call. The whole function call should fail with an err response if one of the transfers fails. Bulk transfers with memos (transfer-many-memo ((transfers (list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)}))) (response bool uint)) Transfer many tokens in one contract call and emit a memo for each. This function follows the same procedure as transfer-many but will emit the memo contained in the tuple after each transfer. The whole function call should fail with an err response if one of the transfers fails. Send-many trait definition A definition of the optional send-many trait is provided below. (define-trait sip013-transfer-many-trait ( ;; Transfer many tokens at once. (transfer-many ((list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal})) (response bool uint)) ;; Transfer many tokens at once with memos. (transfer-many-memo ((list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)})) (response bool uint)) ) ) Related work Ethereum ERC1155 This Semi-Fungible Token standard is similar to the EIP-1155 standard found in the Ethereum/EVM space. An ERC1155 token is a semi-fungible token that admits both a token ID as well as a supply per token ID, just like SIP013. They differ in that the ERC1155 standard describes an approval mechanism as well as “safe transfer” functions that are specific to Ethereum/EVM. Although the biggest difference is the requirement of post condition support, a mechanism that does not exist in Ethereum. Trait deployments Mainnet Token trait: SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-semi-fungible-token-trait Send-many trait: SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-transfer-many-trait Testnet Token trait: STDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTJTTH7YB.sip013-semi-fungible-token-trait Send-many trait: STDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTJTTH7YB.sip013-transfer-many-trait Activation These traits will be considered activated when they are deployed to mainnet and 3 different implementations of the main trait have been deployed to mainnet, no later than Bitcoin block 769,950. Additionally, no revisions to the traits were made after Bitcoin block 756,810. Reference https://github.com/MarvinJanssen/stx-semi-fungible-token]]></summary></entry><entry><title type="html">Non Fungible token (NFT)</title><link href="https://basanta11subedi.github.io/blog/post-NFT/" rel="alternate" type="text/html" title="Non Fungible token (NFT)" /><published>2025-05-27T00:00:00+00:00</published><updated>2025-05-27T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-NFT</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-NFT/"><![CDATA[<p>Non-fungible tokens or NFTs are digital assets registered on blockchain with unique identifiers and properties that distinguish them from each other.
It should be possible to uniquely identify, own and transfer a non-fungible token. This SIP aims to provide a flexible and easy-to-implement standard that can be used by developers on the Stacks blockchain when creating their own NFTs. This standard only specifies a basic set of requirements, non-fungible tokens can have more features than what’s specified in this standard.</p>

<h1 id="introduction">Introduction</h1>

<p>Tokens are digital assets registered on blockchain through a smart contract. A non-fungible token (NFT) is a token that is globally unique and can be identified through its unique identifier.</p>

<p>In blockchains with smart contracts, including the Stacks blockchain, developers and users can use smart contracts to register and interact with non-fungible tokens.</p>

<p>The Stacks blockchain’s programming language for developing smart contracts, Clarity, has built-in language primitives to define and use non-fungible tokens. Although those primitives exists, there is value in defining a common interface (known in Clarity as a “trait”) that allows different smart contracts to interoperate with non-fungible token contracts in a reusable way. This SIP defines that trait.</p>

<p>Each NFT always belong to one smart contract. NFTs of a smart contract are enumerated starting at 1. The current last ID is provided by a smart contract function. The asset ID together with the contract ID defines a globally unique NFT.</p>

<h1 id="specification">Specification</h1>

<p>Every SIP-009 compliant smart contract in Stacks blockchain must implement the trait, <code class="language-plaintext highlighter-rouge">nft-trait</code>, defined in the <a href="https://www.notion.so/for-github-pages-21b2b4a8277f80a1bbc4facfec208c80?pvs=21">Trait</a> section and must meet the requirements for the following functions:</p>

<h3 id="last-token-id">Last token ID</h3>

<p><code class="language-plaintext highlighter-rouge">(get-last-token-id () (response uint uint))</code></p>

<p>Takes no arguments and returns the identifier for the last NFT registered using the contract. The returned ID can be used as the upper limit when iterating through all NFTs.</p>

<p>This function must never return an error response. It can be defined as read-only, i.e. <code class="language-plaintext highlighter-rouge">define-read-only</code>.</p>

<h3 id="token-uri">Token URI</h3>

<p><code class="language-plaintext highlighter-rouge">(get-token-uri (uint) (response (optional (string-ascii 256)) uint))</code></p>

<p>Takes an NFT identifier and returns a response containing a valid URI which resolves to the NFT’s metadata. The URI string must be wrapped in an <code class="language-plaintext highlighter-rouge">optional</code>. If the corresponding NFT doesn’t exist or the contract doesn’t maintain metadata, the response must be <code class="language-plaintext highlighter-rouge">(ok none)</code>. If a valid URI exists for the NFT, the response must be <code class="language-plaintext highlighter-rouge">(ok (some "&lt;URI&gt;"))</code>. The length of the returned URI is limited to 256 characters. The specification of the metadata should be covered in a separate SIP.</p>

<p>This function must never return an error response. It can be defined as read-only, i.e. <code class="language-plaintext highlighter-rouge">define-read-only</code>.</p>

<h3 id="owner">Owner</h3>

<p><code class="language-plaintext highlighter-rouge">(get-owner (uint) (response (optional principal) uint))</code></p>

<p>Takes an NFT identifier and returns a response containing the principal owning the NFT with the given identifier. The principal must be wrapped in an optional. If the corresponding NFT doesn’t exist, the response must be <code class="language-plaintext highlighter-rouge">(ok none)</code>. The owner can be a contract principal.</p>

<p>If a call to function <code class="language-plaintext highlighter-rouge">get-owner</code> returns some principal <code class="language-plaintext highlighter-rouge">A</code>, then it must return the same value until the <code class="language-plaintext highlighter-rouge">transfer</code> function is called with principal <code class="language-plaintext highlighter-rouge">A</code> as the sender.</p>

<p>For any call to <code class="language-plaintext highlighter-rouge">get-owner</code> with an ID greater than the last token ID returned by the <code class="language-plaintext highlighter-rouge">get-last-token-id</code> function, the call must return a response <code class="language-plaintext highlighter-rouge">(ok none)</code>.</p>

<p>This function must never return an error response. It can be defined as read-only, i.e. <code class="language-plaintext highlighter-rouge">define-read-only</code>.</p>

<h3 id="transfer">Transfer</h3>

<p><code class="language-plaintext highlighter-rouge">(transfer (uint principal principal) (response bool uint))</code></p>

<p>The function changes the ownership of the NFT for the given identifier from the sender principal to the recipient principal.</p>

<p>This function must be defined with define-public, as it alters state, and must be externally callable.</p>

<p>After a successful call to <code class="language-plaintext highlighter-rouge">transfer</code>, the function <code class="language-plaintext highlighter-rouge">get-owner</code> must return the recipient of the <code class="language-plaintext highlighter-rouge">transfer</code> call as the new owner.</p>

<p>For any call to <code class="language-plaintext highlighter-rouge">transfer</code> with an ID greater than the last token ID returned by the <code class="language-plaintext highlighter-rouge">get-last-token-id</code> function, the call must return an error response.</p>

<p>It is recommended to use error codes from standardized list of codes and implement the function for converting the error codes to messages function that are defined in a separate SIP.</p>

<h2 id="trait">Trait</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-trait nft-trait
  (
    ;; Last token ID, limited to uint range
    (get-last-token-id () (response uint uint))

    ;; URI for metadata associated with the token
    (get-token-uri (uint) (response (optional (string-ascii 256)) uint))

     ;; Owner of a given token identifier
    (get-owner (uint) (response (optional principal) uint))

    ;; Transfer from the sender to a new principal
    (transfer (uint principal principal) (response bool uint))
  )
)

</code></pre></div></div>

<h2 id="use-of-native-asset-functions">Use of native asset functions</h2>

<p>Although it is not possible to mandate in a Clarity trait, contract implementers must define at least one built-in native non-fungible <a href="https://app.sigle.io/friedger.id/FDwT_3yuMrHDQm-Ai1OVS">asset class</a> that are provided as Clarity primitives. This allows clients to use Post Conditions (explained below), and takes advantages of other benefits, like native support for these asset balances and transfers through <code class="language-plaintext highlighter-rouge">stacks-blockchain-api</code>. The reference implementations included in this SIP use the native asset primitives, and provide a good boilerplate for their usage.</p>

<p>The native asset functions include:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">define-non-fungible-token</code></li>
  <li><code class="language-plaintext highlighter-rouge">nft-burn?</code></li>
  <li><code class="language-plaintext highlighter-rouge">nft-get-owner?</code></li>
  <li><code class="language-plaintext highlighter-rouge">nft-mint?</code></li>
  <li><code class="language-plaintext highlighter-rouge">nft-transfer?</code></li>
</ul>

<p>The following requirements for using native asset functions are defined:</p>

<h3 id="transfer-1">Transfer</h3>

<p>If the <code class="language-plaintext highlighter-rouge">transfer</code> function is called from a client without a <a href="https://docs.blockstack.org/understand-stacks/transactions#post-conditions">post-condition</a> in deny mode or without any NFT condition about a changed owner, then the function call must fail with <code class="language-plaintext highlighter-rouge">abort_by_post_condition</code>.</p>

<h1 id="using-nfts-in-applications">Using NFTs in applications</h1>

<p>Developers who wish to use a non-fungible token contract in an application should first be provided, or keep track of, various different non-fungible token implementations. When validating a non-fungible token contract, they should fetch the interface and/or source code for that contract. If the contract implements the trait, then the application can use this standard’s contract interface for making transfers and getting other details defined in this standard.</p>

<p>All of the functions in this trait return the <code class="language-plaintext highlighter-rouge">response</code> type, which is a requirement of trait definitions in Clarity. However, some of these functions should be “fail-proof”, in the sense that they should never return an error. These “fail-proof” functions are those that have been recommended as read-only. If a contract that implements this trait returns an error for these functions, it may be an indication of a non-compliant contract, and consumers of those contracts should proceed with caution.</p>

<h2 id="use-of-post-conditions">Use of Post-Conditions</h2>

<p>The Stacks blockchain includes a feature known as “Post-Conditions” or “Constraints”. By defining post-conditions, users can create transactions that include pre-defined guarantees about what might happen in that contract.</p>

<p>For example, when applications call the <code class="language-plaintext highlighter-rouge">transfer</code> function, they should <em>always</em> use post conditions to specify that the new owner of the NFT is the recipient principal in the <code class="language-plaintext highlighter-rouge">transfer</code> function call.</p>

<h2 id="eip-721">EIP 721</h2>

<p>Ethereum has <a href="https://eips.ethereum.org/EIPS/eip-721">EIP 721</a> that defined non-fungible tokens on the Ethereum blockchain. Notable differences are that the transfer function in EIP 721 uses a different ordering of the arguments ending with the token id. The transfer function in this SIP uses the token ID as the first argument which is in line with the other native functions in Clarity. Furthermore, this SIP only defines a function for getting the URI pointing to the metadata of an NFT. The specifications for schema and other properties of the token metadata should be defined in a separate SIP.</p>

<h1 id="activation">Activation</h1>

<p>This SIP is activated if 5 contracts are deployed that use the same trait that follows this specification. This must happen before Bitcoin tip #700,000.</p>

<p>A trait that follows this specification is available on mainnet as <a href="https://explorer.stacks.co/txid/0x80eb693e5e2a9928094792080b7f6d69d66ea9cc881bc465e8d9c5c621bd4d07?chain=mainnet"><code class="language-plaintext highlighter-rouge">SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait</code></a>.</p>

<h2 id="source-code">Source code</h2>

<h3 id="friedgers-clarity-smart-contracts">Friedger’s clarity-smart-contracts</h3>

<p>https://github.com/friedger/clarity-smart-contracts/blob/master/contracts/sips/nft-trait.clar</p>

<h2 id="deployed-contracts">Deployed Contracts</h2>

<ul>
  <li>mainnet: <a href="https://explorer.stacks.co/txid/SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait?chain=mainnet">SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait</a></li>
  <li>testnet: <a href="https://explorer.stacks.co/txid/ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait?chain=testnet">ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait.nft-trait</a></li>
</ul>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[Non-fungible tokens or NFTs are digital assets registered on blockchain with unique identifiers and properties that distinguish them from each other. It should be possible to uniquely identify, own and transfer a non-fungible token. This SIP aims to provide a flexible and easy-to-implement standard that can be used by developers on the Stacks blockchain when creating their own NFTs. This standard only specifies a basic set of requirements, non-fungible tokens can have more features than what’s specified in this standard. Introduction Tokens are digital assets registered on blockchain through a smart contract. A non-fungible token (NFT) is a token that is globally unique and can be identified through its unique identifier. In blockchains with smart contracts, including the Stacks blockchain, developers and users can use smart contracts to register and interact with non-fungible tokens. The Stacks blockchain’s programming language for developing smart contracts, Clarity, has built-in language primitives to define and use non-fungible tokens. Although those primitives exists, there is value in defining a common interface (known in Clarity as a “trait”) that allows different smart contracts to interoperate with non-fungible token contracts in a reusable way. This SIP defines that trait. Each NFT always belong to one smart contract. NFTs of a smart contract are enumerated starting at 1. The current last ID is provided by a smart contract function. The asset ID together with the contract ID defines a globally unique NFT. Specification Every SIP-009 compliant smart contract in Stacks blockchain must implement the trait, nft-trait, defined in the Trait section and must meet the requirements for the following functions: Last token ID (get-last-token-id () (response uint uint)) Takes no arguments and returns the identifier for the last NFT registered using the contract. The returned ID can be used as the upper limit when iterating through all NFTs. This function must never return an error response. It can be defined as read-only, i.e. define-read-only. Token URI (get-token-uri (uint) (response (optional (string-ascii 256)) uint)) Takes an NFT identifier and returns a response containing a valid URI which resolves to the NFT’s metadata. The URI string must be wrapped in an optional. If the corresponding NFT doesn’t exist or the contract doesn’t maintain metadata, the response must be (ok none). If a valid URI exists for the NFT, the response must be (ok (some "&lt;URI&gt;")). The length of the returned URI is limited to 256 characters. The specification of the metadata should be covered in a separate SIP. This function must never return an error response. It can be defined as read-only, i.e. define-read-only. Owner (get-owner (uint) (response (optional principal) uint)) Takes an NFT identifier and returns a response containing the principal owning the NFT with the given identifier. The principal must be wrapped in an optional. If the corresponding NFT doesn’t exist, the response must be (ok none). The owner can be a contract principal. If a call to function get-owner returns some principal A, then it must return the same value until the transfer function is called with principal A as the sender. For any call to get-owner with an ID greater than the last token ID returned by the get-last-token-id function, the call must return a response (ok none). This function must never return an error response. It can be defined as read-only, i.e. define-read-only. Transfer (transfer (uint principal principal) (response bool uint)) The function changes the ownership of the NFT for the given identifier from the sender principal to the recipient principal. This function must be defined with define-public, as it alters state, and must be externally callable. After a successful call to transfer, the function get-owner must return the recipient of the transfer call as the new owner. For any call to transfer with an ID greater than the last token ID returned by the get-last-token-id function, the call must return an error response. It is recommended to use error codes from standardized list of codes and implement the function for converting the error codes to messages function that are defined in a separate SIP. Trait (define-trait nft-trait ( ;; Last token ID, limited to uint range (get-last-token-id () (response uint uint)) ;; URI for metadata associated with the token (get-token-uri (uint) (response (optional (string-ascii 256)) uint)) ;; Owner of a given token identifier (get-owner (uint) (response (optional principal) uint)) ;; Transfer from the sender to a new principal (transfer (uint principal principal) (response bool uint)) ) ) Use of native asset functions Although it is not possible to mandate in a Clarity trait, contract implementers must define at least one built-in native non-fungible asset class that are provided as Clarity primitives. This allows clients to use Post Conditions (explained below), and takes advantages of other benefits, like native support for these asset balances and transfers through stacks-blockchain-api. The reference implementations included in this SIP use the native asset primitives, and provide a good boilerplate for their usage. The native asset functions include: define-non-fungible-token nft-burn? nft-get-owner? nft-mint? nft-transfer? The following requirements for using native asset functions are defined: Transfer If the transfer function is called from a client without a post-condition in deny mode or without any NFT condition about a changed owner, then the function call must fail with abort_by_post_condition. Using NFTs in applications Developers who wish to use a non-fungible token contract in an application should first be provided, or keep track of, various different non-fungible token implementations. When validating a non-fungible token contract, they should fetch the interface and/or source code for that contract. If the contract implements the trait, then the application can use this standard’s contract interface for making transfers and getting other details defined in this standard. All of the functions in this trait return the response type, which is a requirement of trait definitions in Clarity. However, some of these functions should be “fail-proof”, in the sense that they should never return an error. These “fail-proof” functions are those that have been recommended as read-only. If a contract that implements this trait returns an error for these functions, it may be an indication of a non-compliant contract, and consumers of those contracts should proceed with caution. Use of Post-Conditions The Stacks blockchain includes a feature known as “Post-Conditions” or “Constraints”. By defining post-conditions, users can create transactions that include pre-defined guarantees about what might happen in that contract. For example, when applications call the transfer function, they should always use post conditions to specify that the new owner of the NFT is the recipient principal in the transfer function call. EIP 721 Ethereum has EIP 721 that defined non-fungible tokens on the Ethereum blockchain. Notable differences are that the transfer function in EIP 721 uses a different ordering of the arguments ending with the token id. The transfer function in this SIP uses the token ID as the first argument which is in line with the other native functions in Clarity. Furthermore, this SIP only defines a function for getting the URI pointing to the metadata of an NFT. The specifications for schema and other properties of the token metadata should be defined in a separate SIP. Activation This SIP is activated if 5 contracts are deployed that use the same trait that follows this specification. This must happen before Bitcoin tip #700,000. A trait that follows this specification is available on mainnet as SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait. Source code Friedger’s clarity-smart-contracts https://github.com/friedger/clarity-smart-contracts/blob/master/contracts/sips/nft-trait.clar Deployed Contracts mainnet: SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait testnet: ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait.nft-trait]]></summary></entry><entry><title type="html">Multiplayer Counter contract (clarity)</title><link href="https://basanta11subedi.github.io/blog/post-1st-project-in-clarity/" rel="alternate" type="text/html" title="Multiplayer Counter contract (clarity)" /><published>2025-05-26T00:00:00+00:00</published><updated>2025-05-26T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-1st-project-in-clarity</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-1st-project-in-clarity/"><![CDATA[<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
;; Multiplayer Counter contract

(define-map counters principal uint)

(define-read-only (get-count (who principal))
    (default-to u0 (map-get? counters who))
)

(define-public (count-up) 
    (ok (map-set counters tx-sender (+ (get-count tx-sender) u1))) 
)

</code></pre></div></div>

<h3 id="-brief-overview">🧠 Brief Overview</h3>

<ul>
  <li><strong>Purpose</strong>: Each user (principal) gets their own unique counter.</li>
  <li><strong>Data storage</strong>: A key-value map that stores each user’s counter.</li>
  <li><strong>Functions</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">get-count</code>: Read-only function to check a user’s count.</li>
      <li><code class="language-plaintext highlighter-rouge">count-up</code>: Public function to increase the sender’s count by 1</li>
    </ul>
  </li>
</ul>

<hr />

<h3 id="-code-breakdown">🔍 Code Breakdown</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
;; Multiplayer Counter contract

(define-map counters principal uint)

</code></pre></div></div>

<ul>
  <li>Declares a <strong>map</strong> named <code class="language-plaintext highlighter-rouge">counters</code>.</li>
  <li>Key: <code class="language-plaintext highlighter-rouge">principal</code> (the user’s blockchain address).</li>
  <li>Value: <code class="language-plaintext highlighter-rouge">uint</code> (unsigned integer — the counter value).</li>
</ul>

<hr />

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-read-only (get-count (who principal))
    (default-to u0 (map-get? counters who))
)

</code></pre></div></div>

<ul>
  <li>This is a <strong>read-only</strong> function. It does not modify blockchain state.</li>
  <li>Input: <code class="language-plaintext highlighter-rouge">who</code> — a user address.</li>
  <li>Action: Checks if the user has a counter.
    <ul>
      <li>If yes: returns the value.</li>
      <li>If no: returns <code class="language-plaintext highlighter-rouge">u0</code> (default value 0).</li>
    </ul>
  </li>
</ul>

<hr />

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-public (count-up)
    (ok (map-set counters tx-sender (+ (get-count tx-sender) u1)))
)

</code></pre></div></div>

<ul>
  <li>A <strong>public function</strong> (anyone can call it).</li>
  <li><code class="language-plaintext highlighter-rouge">tx-sender</code>: The address of the user calling this function.</li>
  <li>Gets the sender’s current count using <code class="language-plaintext highlighter-rouge">get-count</code>.</li>
  <li>Adds 1 to it.</li>
  <li>Updates the <code class="language-plaintext highlighter-rouge">counters</code> map with the new value.</li>
  <li>Wraps the result in an <code class="language-plaintext highlighter-rouge">ok</code> response.</li>
</ul>

<hr />

<h3 id="-step-by-step-how-this-contract-works">🧭 Step-by-Step: How This Contract Works</h3>

<p>Let’s walk through an example interaction:</p>

<h3 id="1--user-a-checks-their-counter">1. 🧍 User A checks their counter:</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(get-count tx-sender)

</code></pre></div></div>

<ul>
  <li>If User A is new, this returns <code class="language-plaintext highlighter-rouge">0</code> because there’s no entry yet.</li>
</ul>

<h3 id="2--user-a-calls-count-up">2. 🆙 User A calls <code class="language-plaintext highlighter-rouge">count-up</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(count-up)

</code></pre></div></div>

<ul>
  <li>Contract fetches User A’s current count (e.g., <code class="language-plaintext highlighter-rouge">0</code>).</li>
  <li>Adds 1 → new value = <code class="language-plaintext highlighter-rouge">1</code>.</li>
  <li>Stores it in the <code class="language-plaintext highlighter-rouge">counters</code> map: <code class="language-plaintext highlighter-rouge">counters[User A] = 1</code>.</li>
</ul>

<h3 id="3--user-a-calls-count-up-again">3. 🔁 User A calls <code class="language-plaintext highlighter-rouge">count-up</code> again</h3>

<ul>
  <li>Now it fetches <code class="language-plaintext highlighter-rouge">1</code>, adds 1 → value becomes <code class="language-plaintext highlighter-rouge">2</code>, and updates the map.</li>
</ul>

<h3 id="4--user-b-joins-and-starts-at-0">4. 🧑‍🤝‍🧑 User B joins and starts at 0</h3>

<ul>
  <li>Their <code class="language-plaintext highlighter-rouge">get-count</code> will return <code class="language-plaintext highlighter-rouge">0</code>.</li>
  <li>They can now increment their own counter without affecting User A’s.</li>
</ul>

<hr />

<h3 id="-summary">📘 Summary</h3>

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">counters</code></td>
      <td>Map</td>
      <td>Stores each user’s counter.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-count</code></td>
      <td>Read-only</td>
      <td>Returns current count or 0 if not found.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">count-up</code></td>
      <td>Public</td>
      <td>Increments the caller’s counter by 1.</td>
    </tr>
  </tbody>
</table>

<hr />

<h3 id="-real-world-analogy">🧩 Real-World Analogy</h3>

<p>Imagine a scoreboard where every player has their own slot. When a player hits a button, only their score increases. No one can touch your slot but you.</p>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[;; Multiplayer Counter contract (define-map counters principal uint) (define-read-only (get-count (who principal)) (default-to u0 (map-get? counters who)) ) (define-public (count-up) (ok (map-set counters tx-sender (+ (get-count tx-sender) u1))) ) 🧠 Brief Overview Purpose: Each user (principal) gets their own unique counter. Data storage: A key-value map that stores each user’s counter. Functions: get-count: Read-only function to check a user’s count. count-up: Public function to increase the sender’s count by 1 🔍 Code Breakdown ;; Multiplayer Counter contract (define-map counters principal uint) Declares a map named counters. Key: principal (the user’s blockchain address). Value: uint (unsigned integer — the counter value). (define-read-only (get-count (who principal)) (default-to u0 (map-get? counters who)) ) This is a read-only function. It does not modify blockchain state. Input: who — a user address. Action: Checks if the user has a counter. If yes: returns the value. If no: returns u0 (default value 0). (define-public (count-up) (ok (map-set counters tx-sender (+ (get-count tx-sender) u1))) ) A public function (anyone can call it). tx-sender: The address of the user calling this function. Gets the sender’s current count using get-count. Adds 1 to it. Updates the counters map with the new value. Wraps the result in an ok response. 🧭 Step-by-Step: How This Contract Works Let’s walk through an example interaction: 1. 🧍 User A checks their counter: (get-count tx-sender) If User A is new, this returns 0 because there’s no entry yet. 2. 🆙 User A calls count-up (count-up) Contract fetches User A’s current count (e.g., 0). Adds 1 → new value = 1. Stores it in the counters map: counters[User A] = 1. 3. 🔁 User A calls count-up again Now it fetches 1, adds 1 → value becomes 2, and updates the map. 4. 🧑‍🤝‍🧑 User B joins and starts at 0 Their get-count will return 0. They can now increment their own counter without affecting User A’s. 📘 Summary Function Type Description counters Map Stores each user’s counter. get-count Read-only Returns current count or 0 if not found. count-up Public Increments the caller’s counter by 1. 🧩 Real-World Analogy Imagine a scoreboard where every player has their own slot. When a player hits a button, only their score increases. No one can touch your slot but you.]]></summary></entry><entry><title type="html">Token Vault (clarity)</title><link href="https://basanta11subedi.github.io/blog/post-2nd-project-in-clarity/" rel="alternate" type="text/html" title="Token Vault (clarity)" /><published>2025-05-26T00:00:00+00:00</published><updated>2025-05-26T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-2nd-project-in-clarity</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-2nd-project-in-clarity/"><![CDATA[<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-map balances { account: principal } uint)

;; #[allow(unchecked_data)]
(define-public (deposit (amount uint))
  (let ((current (default-to u0 (map-get? balances { account: tx-sender }))))
    (begin
      (map-set balances { account: tx-sender } (+ current amount))
      (ok true)
    )
  )
)

(define-public (withdraw (amount uint))
  (let ((current (default-to u0 (map-get? balances { account: tx-sender }))))
    (if (&gt;= current amount)
        (begin
          (map-set balances { account: tx-sender } (- current amount))
          (ok true)
        )
        (err u402) ;; insufficient funds
    )
  )
)
</code></pre></div></div>

<h3 id="-brief-overview">🧠 Brief Overview</h3>

<ul>
  <li><strong>Purpose</strong>: Track deposits and withdrawals of users on-chain.</li>
  <li><strong>Data Storage</strong>: Each user’s balance is stored using a map.</li>
  <li><strong>Functions</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">deposit</code>: Add tokens to the sender’s balance.</li>
      <li><code class="language-plaintext highlighter-rouge">withdraw</code>: Subtract tokens if the sender has enough.</li>
    </ul>
  </li>
</ul>

<hr />

<h2 id="-code-breakdown">📄 Code Breakdown</h2>

<h3 id="️-1-define-the-balance-map">🗃️ 1. Define the Balance Map</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-map balances { account: principal } uint)

</code></pre></div></div>

<ul>
  <li>A <strong>map</strong> named <code class="language-plaintext highlighter-rouge">balances</code> stores user balances.</li>
  <li>Key: <code class="language-plaintext highlighter-rouge">{ account: principal }</code> — each user’s address.</li>
  <li>Value: <code class="language-plaintext highlighter-rouge">uint</code> — the unsigned integer balance.</li>
</ul>

<hr />

<h3 id="-2-deposit-function">💸 2. Deposit Function</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (deposit (amount uint))
  (let ((current (default-to u0 (map-get? balances { account: tx-sender }))))
    (begin
      (map-set balances { account: tx-sender } (+ current amount))
      (ok true)
    )
  )
)

</code></pre></div></div>

<ul>
  <li><strong>Public function</strong> — any user can call it.</li>
  <li>Accepts an <code class="language-plaintext highlighter-rouge">amount</code> to deposit.</li>
  <li>Fetches the current balance:
    <ul>
      <li>If none exists, defaults to <code class="language-plaintext highlighter-rouge">0</code>.</li>
    </ul>
  </li>
  <li>Adds the deposit amount to the current balance.</li>
  <li>Updates the map.</li>
  <li>Returns <code class="language-plaintext highlighter-rouge">(ok true)</code> to confirm success.</li>
</ul>

<hr />

<h3 id="-3-withdraw-function">🏧 3. Withdraw Function</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (withdraw (amount uint))
  (let ((current (default-to u0 (map-get? balances { account: tx-sender }))))
    (if (&gt;= current amount)
        (begin
          (map-set balances { account: tx-sender } (- current amount))
          (ok true)
        )
        (err u402) ;; insufficient funds
    )
  )
)

</code></pre></div></div>

<ul>
  <li>Also <strong>public</strong> — allows users to withdraw.</li>
  <li>Gets the user’s current balance.</li>
  <li>Checks: is the balance &gt;= withdrawal amount?
    <ul>
      <li>✅ If yes: subtracts and updates the balance.</li>
      <li>❌ If no: returns <code class="language-plaintext highlighter-rouge">(err u402)</code> — an error for insufficient funds.</li>
    </ul>
  </li>
</ul>

<hr />

<h2 id="-step-by-step-how-this-contract-works">🔄 Step-by-Step: How This Contract Works</h2>

<p>Let’s walk through a real-world scenario:</p>

<h3 id="-user-a-wants-to-deposit-100-tokens">🧍 User A wants to deposit 100 tokens:</h3>

<ol>
  <li>Calls:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(deposit u100)

</code></pre></div></div>

<ol>
  <li>Contract:
    <ul>
      <li>Reads balance (e.g., <code class="language-plaintext highlighter-rouge">u0</code> if new).</li>
      <li>Adds <code class="language-plaintext highlighter-rouge">100</code>.</li>
      <li>Updates the map: <code class="language-plaintext highlighter-rouge">{ account: UserA } → 100</code>.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="-now-user-a-wants-to-withdraw-30-tokens">🏧 Now User A wants to withdraw 30 tokens:</h3>

<ol>
  <li>Calls:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(withdraw u30)

</code></pre></div></div>

<ol>
  <li>Contract:
    <ul>
      <li>Checks balance: 100.</li>
      <li>100 ≥ 30 → allowed.</li>
      <li>Subtracts 30 → new balance = 70.</li>
      <li>Updates the map.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="-user-a-tries-to-withdraw-100-but-only-has-70">⛔ User A tries to withdraw 100 (but only has 70):</h3>

<ul>
  <li>Fails the check.</li>
  <li>Returns:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(err u402)

</code></pre></div></div>

<p>Which can be interpreted in your frontend as <strong>“Insufficient Funds”</strong>.</p>

<hr />

<h3 id="-real-world-analogy">🧩 Real-World Analogy</h3>

<p>Think of it like a <strong>digital piggy bank</strong> for each user:</p>

<ul>
  <li>You drop in coins (<code class="language-plaintext highlighter-rouge">deposit</code>).</li>
  <li>You can take out coins if there’s enough (<code class="language-plaintext highlighter-rouge">withdraw</code>).</li>
  <li>If it’s empty or has too little, you get an error.</li>
</ul>

<hr />

<h2 id="-summary-table">📘 Summary Table</h2>

<table>
  <thead>
    <tr>
      <th>Component</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">balances</code></td>
      <td>Map storing user balances</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">deposit</code></td>
      <td>Adds tokens to the user’s vault</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">withdraw</code></td>
      <td>Removes tokens if user has enough</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">err u402</code></td>
      <td>Error code for “Insufficient Funds”</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[(define-map balances { account: principal } uint) ;; #[allow(unchecked_data)] (define-public (deposit (amount uint)) (let ((current (default-to u0 (map-get? balances { account: tx-sender })))) (begin (map-set balances { account: tx-sender } (+ current amount)) (ok true) ) ) ) (define-public (withdraw (amount uint)) (let ((current (default-to u0 (map-get? balances { account: tx-sender })))) (if (&gt;= current amount) (begin (map-set balances { account: tx-sender } (- current amount)) (ok true) ) (err u402) ;; insufficient funds ) ) ) 🧠 Brief Overview Purpose: Track deposits and withdrawals of users on-chain. Data Storage: Each user’s balance is stored using a map. Functions: deposit: Add tokens to the sender’s balance. withdraw: Subtract tokens if the sender has enough. 📄 Code Breakdown 🗃️ 1. Define the Balance Map (define-map balances { account: principal } uint) A map named balances stores user balances. Key: { account: principal } — each user’s address. Value: uint — the unsigned integer balance. 💸 2. Deposit Function (define-public (deposit (amount uint)) (let ((current (default-to u0 (map-get? balances { account: tx-sender })))) (begin (map-set balances { account: tx-sender } (+ current amount)) (ok true) ) ) ) Public function — any user can call it. Accepts an amount to deposit. Fetches the current balance: If none exists, defaults to 0. Adds the deposit amount to the current balance. Updates the map. Returns (ok true) to confirm success. 🏧 3. Withdraw Function (define-public (withdraw (amount uint)) (let ((current (default-to u0 (map-get? balances { account: tx-sender })))) (if (&gt;= current amount) (begin (map-set balances { account: tx-sender } (- current amount)) (ok true) ) (err u402) ;; insufficient funds ) ) ) Also public — allows users to withdraw. Gets the user’s current balance. Checks: is the balance &gt;= withdrawal amount? ✅ If yes: subtracts and updates the balance. ❌ If no: returns (err u402) — an error for insufficient funds. 🔄 Step-by-Step: How This Contract Works Let’s walk through a real-world scenario: 🧍 User A wants to deposit 100 tokens: Calls: (deposit u100) Contract: Reads balance (e.g., u0 if new). Adds 100. Updates the map: { account: UserA } → 100. 🏧 Now User A wants to withdraw 30 tokens: Calls: (withdraw u30) Contract: Checks balance: 100. 100 ≥ 30 → allowed. Subtracts 30 → new balance = 70. Updates the map. ⛔ User A tries to withdraw 100 (but only has 70): Fails the check. Returns: (err u402) Which can be interpreted in your frontend as “Insufficient Funds”. 🧩 Real-World Analogy Think of it like a digital piggy bank for each user: You drop in coins (deposit). You can take out coins if there’s enough (withdraw). If it’s empty or has too little, you get an error. 📘 Summary Table Component Purpose balances Map storing user balances deposit Adds tokens to the user’s vault withdraw Removes tokens if user has enough err u402 Error code for “Insufficient Funds”]]></summary></entry><entry><title type="html">Message Board Contract (clarity)</title><link href="https://basanta11subedi.github.io/blog/post-3rd-project-in-clarity/" rel="alternate" type="text/html" title="Message Board Contract (clarity)" /><published>2025-05-26T00:00:00+00:00</published><updated>2025-05-26T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-3rd-project-in-clarity</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-3rd-project-in-clarity/"><![CDATA[<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-map messages { user: principal } {message: (string-utf8 100)})


(define-public (write-message (msg (string-utf8 100)))
  (begin
    (map-set messages { user: tx-sender } { message: msg })
    (ok true)
  )
)

(define-public (read-message (user principal))
  (match (map-get? messages { user: user })
    value (ok (get message value))
    (err u404)
  )
)

(define-public (read-message (user principal))
  (match (map-get? messages { user: user })
    value (ok (get message value))
    (err u404)
  )
)
</code></pre></div></div>

<p>This smart contract allows users to write a <strong>single message</strong> tied to their address (like a profile status), and allows others to <strong>read those messages</strong>. All data is stored <strong>on-chain</strong>, meaning it’s transparent and permanent until overwritten.</p>

<hr />

<h3 id="-brief-overview">🧠 Brief Overview</h3>

<ul>
  <li><strong>Purpose</strong>: Store one personal message per user on the blockchain.</li>
  <li><strong>Data Storage</strong>: A map that links user addresses to their messages.</li>
  <li><strong>Functions</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">write-message</code>: Save/update your message.</li>
      <li><code class="language-plaintext highlighter-rouge">read-message</code>: Read another user’s message.</li>
      <li><code class="language-plaintext highlighter-rouge">see-message</code>: Low-level view of a user’s message record (for dev/debug).</li>
    </ul>
  </li>
</ul>

<hr />

<h2 id="-code-breakdown">🔍 Code Breakdown</h2>

<h3 id="️-1-define-the-message-map">🗃️ 1. Define the Message Map</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
(define-map messages { user: principal } {message: (string-utf8 100)})

</code></pre></div></div>

<ul>
  <li>This map stores messages per user.</li>
  <li>Key: <code class="language-plaintext highlighter-rouge">{ user: principal }</code> — user’s blockchain address.</li>
  <li>Value: <code class="language-plaintext highlighter-rouge">{ message: (string-utf8 100) }</code> — a UTF-8 string up to 100 characters.</li>
</ul>

<hr />

<h3 id="️-2-write-a-message">✍️ 2. Write a Message</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (write-message (msg (string-utf8 100)))
  (begin
    (map-set messages { user: tx-sender } { message: msg })
    (ok true)
  )
)

</code></pre></div></div>

<ul>
  <li><strong>Public function</strong> that lets users write/update their message.</li>
  <li>Uses <code class="language-plaintext highlighter-rouge">tx-sender</code> to identify the writer.</li>
  <li>Stores <code class="language-plaintext highlighter-rouge">{ message: msg }</code> in the <code class="language-plaintext highlighter-rouge">messages</code> map.</li>
  <li>Returns <code class="language-plaintext highlighter-rouge">(ok true)</code> to confirm the update.</li>
</ul>

<hr />

<h3 id="-3-read-someones-message">📖 3. Read Someone’s Message</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (read-message (user principal))
  (match (map-get? messages { user: user })
    value (ok (get message value))
    (err u404)
  )
)

</code></pre></div></div>

<ul>
  <li>Takes a <code class="language-plaintext highlighter-rouge">user</code> (their address) as input.</li>
  <li>Tries to get their message from the map.</li>
  <li>If found: extracts the <code class="language-plaintext highlighter-rouge">message</code> and returns it with <code class="language-plaintext highlighter-rouge">ok</code>.</li>
  <li>If not: returns <code class="language-plaintext highlighter-rouge">(err u404)</code> (message not found).</li>
</ul>

<hr />

<h3 id="-4-developer-view-see-raw-message-record">🔍 4. Developer View: See Raw Message Record</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-read-only (see-message (recipient principal))
	(ok (map-get? messages recipient))
)

</code></pre></div></div>

<ul>
  <li>Read-only helper function.</li>
  <li>Returns the <strong>full record</strong> stored in the map, not just the message string.</li>
  <li>Useful for debugging or exploring the raw map value (including key/field names).</li>
</ul>

<hr />

<h2 id="-step-by-step-how-it-works">🧭 Step-by-Step: How It Works</h2>

<h3 id="-user-a-writes-a-message">🧍 User A writes a message</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(write-message "Hello, Stacks!")

</code></pre></div></div>

<ul>
  <li>
    <p>The contract saves:</p>

    <p><code class="language-plaintext highlighter-rouge">{ user: UserA } → { message: "Hello, Stacks!" }</code></p>
  </li>
</ul>

<hr />

<h3 id="-user-b-reads-user-as-message">🧑‍🤝‍🧑 User B reads User A’s message</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(read-message UserA)

</code></pre></div></div>

<ul>
  <li>If User A has written a message: returns <code class="language-plaintext highlighter-rouge">ok "Hello, Stacks!"</code>.</li>
  <li>If not: returns <code class="language-plaintext highlighter-rouge">err u404</code>.</li>
</ul>

<hr />

<h3 id="-developer-reads-raw-data">🧪 Developer reads raw data</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(see-message UserA)

</code></pre></div></div>

<ul>
  <li>
    <p>Output:</p>

    <p><code class="language-plaintext highlighter-rouge">ok (some { message: "Hello, Stacks!" })</code></p>

    <p>or <code class="language-plaintext highlighter-rouge">ok none</code> if there’s no message.</p>
  </li>
</ul>

<hr />

<h2 id="-summary-table">📘 Summary Table</h2>

<table>
  <thead>
    <tr>
      <th>Component</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">messages</code> map</td>
      <td>Stores one message per user</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">write-message</code></td>
      <td>Lets users post or update their message</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">read-message</code></td>
      <td>Lets anyone read another user’s message</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">see-message</code></td>
      <td>Returns the full map entry (debug-style)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">err u404</code></td>
      <td>Error if no message found for a user</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="-real-world-analogy">🧩 Real-World Analogy</h2>

<p>Imagine a global public board where:</p>

<ul>
  <li>Each user gets <strong>one sticky note</strong> to post a message.</li>
  <li>Anyone can read your note.</li>
  <li>You can update it any time.</li>
  <li>Others can’t change your note — only you can.</li>
</ul>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[(define-map messages { user: principal } {message: (string-utf8 100)}) (define-public (write-message (msg (string-utf8 100))) (begin (map-set messages { user: tx-sender } { message: msg }) (ok true) ) ) (define-public (read-message (user principal)) (match (map-get? messages { user: user }) value (ok (get message value)) (err u404) ) ) (define-public (read-message (user principal)) (match (map-get? messages { user: user }) value (ok (get message value)) (err u404) ) ) This smart contract allows users to write a single message tied to their address (like a profile status), and allows others to read those messages. All data is stored on-chain, meaning it’s transparent and permanent until overwritten. 🧠 Brief Overview Purpose: Store one personal message per user on the blockchain. Data Storage: A map that links user addresses to their messages. Functions: write-message: Save/update your message. read-message: Read another user’s message. see-message: Low-level view of a user’s message record (for dev/debug). 🔍 Code Breakdown 🗃️ 1. Define the Message Map (define-map messages { user: principal } {message: (string-utf8 100)}) This map stores messages per user. Key: { user: principal } — user’s blockchain address. Value: { message: (string-utf8 100) } — a UTF-8 string up to 100 characters. ✍️ 2. Write a Message (define-public (write-message (msg (string-utf8 100))) (begin (map-set messages { user: tx-sender } { message: msg }) (ok true) ) ) Public function that lets users write/update their message. Uses tx-sender to identify the writer. Stores { message: msg } in the messages map. Returns (ok true) to confirm the update. 📖 3. Read Someone’s Message (define-public (read-message (user principal)) (match (map-get? messages { user: user }) value (ok (get message value)) (err u404) ) ) Takes a user (their address) as input. Tries to get their message from the map. If found: extracts the message and returns it with ok. If not: returns (err u404) (message not found). 🔍 4. Developer View: See Raw Message Record (define-read-only (see-message (recipient principal)) (ok (map-get? messages recipient)) ) Read-only helper function. Returns the full record stored in the map, not just the message string. Useful for debugging or exploring the raw map value (including key/field names). 🧭 Step-by-Step: How It Works 🧍 User A writes a message (write-message "Hello, Stacks!") The contract saves: { user: UserA } → { message: "Hello, Stacks!" } 🧑‍🤝‍🧑 User B reads User A’s message (read-message UserA) If User A has written a message: returns ok "Hello, Stacks!". If not: returns err u404. 🧪 Developer reads raw data (see-message UserA) Output: ok (some { message: "Hello, Stacks!" }) or ok none if there’s no message. 📘 Summary Table Component Purpose messages map Stores one message per user write-message Lets users post or update their message read-message Lets anyone read another user’s message see-message Returns the full map entry (debug-style) err u404 Error if no message found for a user 🧩 Real-World Analogy Imagine a global public board where: Each user gets one sticky note to post a message. Anyone can read your note. You can update it any time. Others can’t change your note — only you can.]]></summary></entry><entry><title type="html">Simple Voting Contract (clarity)</title><link href="https://basanta11subedi.github.io/blog/post-4th-project-in-clarity/" rel="alternate" type="text/html" title="Simple Voting Contract (clarity)" /><published>2025-05-26T00:00:00+00:00</published><updated>2025-05-26T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-4th-project-in-clarity</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-4th-project-in-clarity/"><![CDATA[<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-map votes { candidate: (string-utf8 20) } uint)


;; #[allow(unchecked_data)]
(define-public (vote (candidate (string-utf8 20)))
  (let ((count (default-to u0 (map-get? votes { candidate: candidate }))))
    (begin
      (map-set votes { candidate: candidate } (+ count u1))
      (ok true)
    )
  )
)

(define-public (get-votes (candidate (string-utf8 20)))
  (ok (default-to u0 (map-get? votes { candidate: candidate })))
)
</code></pre></div></div>

<p>This contract allows users to <strong>vote for candidates</strong>, with votes stored <strong>on-chain</strong>. Anyone can cast a vote for a candidate by name, and anyone can check how many votes a candidate has received.</p>

<hr />

<h3 id="-brief-overview">🧠 Brief Overview</h3>

<ul>
  <li><strong>Purpose</strong>: Tally votes for candidates by name.</li>
  <li><strong>Storage</strong>: A map that links each candidate’s name to their vote count.</li>
  <li><strong>Functions</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">vote</code>: Increases vote count for a candidate.</li>
      <li><code class="language-plaintext highlighter-rouge">get-votes</code>: Returns the total votes for a candidate.</li>
    </ul>
  </li>
</ul>

<hr />

<h2 id="-code-breakdown">📄 Code Breakdown</h2>

<h3 id="️-1-define-the-votes-map">🗃️ 1. Define the Votes Map</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-map votes { candidate: (string-utf8 20) } uint)

</code></pre></div></div>

<ul>
  <li>A <strong>map</strong> called <code class="language-plaintext highlighter-rouge">votes</code> tracks vote totals.</li>
  <li>Key: <code class="language-plaintext highlighter-rouge">{ candidate: string-utf8(20) }</code> — candidate name (max 20 chars).</li>
  <li>Value: <code class="language-plaintext highlighter-rouge">uint</code> — the number of votes received.</li>
</ul>

<hr />

<h3 id="-2-cast-a-vote">✅ 2. Cast a Vote</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (vote (candidate (string-utf8 20)))
  (let ((count (default-to u0 (map-get? votes { candidate: candidate }))))
    (begin
      (map-set votes { candidate: candidate } (+ count u1))
      (ok true)
    )
  )
)

</code></pre></div></div>

<ul>
  <li><strong>Public function</strong> — anyone can call it to vote.</li>
  <li>Input: <code class="language-plaintext highlighter-rouge">candidate</code> — the name of the candidate being voted for.</li>
  <li>Reads current vote count using <code class="language-plaintext highlighter-rouge">map-get?</code>.
    <ul>
      <li>If none yet: defaults to <code class="language-plaintext highlighter-rouge">0</code> using <code class="language-plaintext highlighter-rouge">default-to</code>.</li>
    </ul>
  </li>
  <li>Adds 1 to the vote count.</li>
  <li>Stores the new count in the map.</li>
  <li>Returns <code class="language-plaintext highlighter-rouge">(ok true)</code> as confirmation.</li>
</ul>

<hr />

<h3 id="-3-get-vote-count">📊 3. Get Vote Count</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (get-votes (candidate (string-utf8 20)))
  (ok (default-to u0 (map-get? votes { candidate: candidate })))
)

</code></pre></div></div>

<ul>
  <li>Takes the <code class="language-plaintext highlighter-rouge">candidate</code> name as input.</li>
  <li>Fetches the number of votes from the map.</li>
  <li>If no votes exist yet, returns <code class="language-plaintext highlighter-rouge">0</code>.</li>
  <li>Always returns result wrapped in <code class="language-plaintext highlighter-rouge">ok</code>.</li>
</ul>

<hr />

<h2 id="-step-by-step-how-it-works">🧭 Step-by-Step: How It Works</h2>

<p>Let’s walk through an example:</p>

<h3 id="-user-a-votes-for-alice">🧑 User A votes for “Alice”:</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(vote "Alice")

</code></pre></div></div>

<ul>
  <li>The contract:
    <ul>
      <li>Checks if “Alice” has votes → returns <code class="language-plaintext highlighter-rouge">0</code> (if first vote).</li>
      <li>Adds 1 → total becomes <code class="language-plaintext highlighter-rouge">1</code>.</li>
      <li>Stores <code class="language-plaintext highlighter-rouge">{ candidate: "Alice" } → 1</code>.</li>
    </ul>
  </li>
</ul>

<hr />

<h3 id="-user-b-votes-for-bob">🧑‍🤝‍🧑 User B votes for “Bob”:</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(vote "Bob")

</code></pre></div></div>

<ul>
  <li>Same logic → <code class="language-plaintext highlighter-rouge">{ candidate: "Bob" } → 1</code>.</li>
</ul>

<hr />

<h3 id="-user-a-votes-for-alice-again">🧑 User A votes for “Alice” again:</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(vote "Alice")

</code></pre></div></div>

<ul>
  <li>Reads current count: <code class="language-plaintext highlighter-rouge">1</code>.</li>
  <li>Adds 1 → becomes <code class="language-plaintext highlighter-rouge">2</code>.</li>
  <li>Updates map.</li>
</ul>

<hr />

<h3 id="-check-how-many-votes-alice-has">📊 Check how many votes “Alice” has:</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(get-votes "Alice")

</code></pre></div></div>

<ul>
  <li>Returns:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(ok u2)

</code></pre></div></div>

<hr />

<h2 id="-summary-table">📘 Summary Table</h2>

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">votes</code> map</td>
      <td>Stores each candidate’s vote count</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">vote</code></td>
      <td>Increments a candidate’s vote count</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-votes</code></td>
      <td>Retrieves vote count for any candidate</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">default-to</code></td>
      <td>Ensures candidates with no votes return 0</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="-real-world-analogy">🧩 Real-World Analogy</h2>

<p>Think of a public election box:</p>

<ul>
  <li>Each candidate’s name is a folder.</li>
  <li>Every time someone votes, a ballot goes into that folder.</li>
  <li>Anyone can check how many ballots (votes) are in any folder.</li>
</ul>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[(define-map votes { candidate: (string-utf8 20) } uint) ;; #[allow(unchecked_data)] (define-public (vote (candidate (string-utf8 20))) (let ((count (default-to u0 (map-get? votes { candidate: candidate })))) (begin (map-set votes { candidate: candidate } (+ count u1)) (ok true) ) ) ) (define-public (get-votes (candidate (string-utf8 20))) (ok (default-to u0 (map-get? votes { candidate: candidate }))) ) This contract allows users to vote for candidates, with votes stored on-chain. Anyone can cast a vote for a candidate by name, and anyone can check how many votes a candidate has received. 🧠 Brief Overview Purpose: Tally votes for candidates by name. Storage: A map that links each candidate’s name to their vote count. Functions: vote: Increases vote count for a candidate. get-votes: Returns the total votes for a candidate. 📄 Code Breakdown 🗃️ 1. Define the Votes Map (define-map votes { candidate: (string-utf8 20) } uint) A map called votes tracks vote totals. Key: { candidate: string-utf8(20) } — candidate name (max 20 chars). Value: uint — the number of votes received. ✅ 2. Cast a Vote (define-public (vote (candidate (string-utf8 20))) (let ((count (default-to u0 (map-get? votes { candidate: candidate })))) (begin (map-set votes { candidate: candidate } (+ count u1)) (ok true) ) ) ) Public function — anyone can call it to vote. Input: candidate — the name of the candidate being voted for. Reads current vote count using map-get?. If none yet: defaults to 0 using default-to. Adds 1 to the vote count. Stores the new count in the map. Returns (ok true) as confirmation. 📊 3. Get Vote Count (define-public (get-votes (candidate (string-utf8 20))) (ok (default-to u0 (map-get? votes { candidate: candidate }))) ) Takes the candidate name as input. Fetches the number of votes from the map. If no votes exist yet, returns 0. Always returns result wrapped in ok. 🧭 Step-by-Step: How It Works Let’s walk through an example: 🧑 User A votes for “Alice”: (vote "Alice") The contract: Checks if “Alice” has votes → returns 0 (if first vote). Adds 1 → total becomes 1. Stores { candidate: "Alice" } → 1. 🧑‍🤝‍🧑 User B votes for “Bob”: (vote "Bob") Same logic → { candidate: "Bob" } → 1. 🧑 User A votes for “Alice” again: (vote "Alice") Reads current count: 1. Adds 1 → becomes 2. Updates map. 📊 Check how many votes “Alice” has: (get-votes "Alice") Returns: (ok u2) 📘 Summary Table Function Purpose votes map Stores each candidate’s vote count vote Increments a candidate’s vote count get-votes Retrieves vote count for any candidate default-to Ensures candidates with no votes return 0 🧩 Real-World Analogy Think of a public election box: Each candidate’s name is a folder. Every time someone votes, a ballot goes into that folder. Anyone can check how many ballots (votes) are in any folder.]]></summary></entry><entry><title type="html">Time-locked STX Vault (clarity)</title><link href="https://basanta11subedi.github.io/blog/post-5th-project-in-clarity/" rel="alternate" type="text/html" title="Time-locked STX Vault (clarity)" /><published>2025-05-26T00:00:00+00:00</published><updated>2025-05-26T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-5th-project-in-clarity</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-5th-project-in-clarity/"><![CDATA[<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
;; Time-Locked STX Vault - A contract that allows users to deposit STX and lock them for a specified period
;; Users can only withdraw once the specified block height is reached

;; Define storage for the vault deposits
;; Map: user-address -&gt; (amount, unlock-height)
(define-map vaults
  { owner: principal }
  {
    amount: uint,
    unlock-height: uint
  }
)

;; Public function to deposit STX and set a lock time
(define-public (deposit (amount uint) (lock-blocks uint))
  (let
    (
      (current-height stacks-block-height)
    )
    (asserts! (&gt; amount u0) (err u1))
    (asserts! (and (&gt;= lock-blocks u1) (&lt;= lock-blocks u52560)) (err u5))
    (asserts! (is-none (map-get? vaults {owner: tx-sender})) (err u2))
    (let 
      ((unlock-at (+ current-height lock-blocks)))
      (map-set vaults
        {owner: tx-sender}
        {
          amount: amount,
          unlock-height: unlock-at
        }
      )
      (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
      (ok unlock-at)
    )
  )
)


;; Public function to withdraw STX after lock period
(define-public (withdraw)
  (let
    (
      (vault-data (unwrap! (map-get? vaults {owner: tx-sender}) (err u3)))
      (amount (get amount vault-data))
      (unlock-height (get unlock-height vault-data))
    )
    (asserts! (&gt;= stacks-block-height unlock-height) (err u4))
    (map-delete vaults {owner: tx-sender})
    (try! (stx-transfer? amount (as-contract tx-sender) tx-sender))
    (ok amount) ;; Return the amount withdrawn
  )
)


;; Read-only function to get information about a user's vault
(define-read-only (get-vault-info (user principal))
  (map-get? vaults {owner: user})
)

;; Read-only function to check if withdrawal is possible
(define-read-only (can-withdraw (user principal))
  (match (map-get? vaults {owner: user})
    vault (&gt;= stacks-block-height (get unlock-height vault))
    false
  )
)

;; Read-only function to check how many blocks remain until withdrawal is possible
(define-read-only (blocks-until-unlock (user principal))
  (match (map-get? vaults {owner: user})
    vault (if (&gt;= stacks-block-height (get unlock-height vault))
            u0
            (- (get unlock-height vault) stacks-block-height))
    u0
  )
)

</code></pre></div></div>

<p>This smart contract enables users to <strong>deposit STX tokens</strong> and lock them for a certain number of <strong>block heights</strong>. Once the time has passed, they can withdraw their tokens.</p>

<hr />

<h3 id="-brief-overview">🧠 Brief Overview</h3>

<ul>
  <li><strong>Purpose</strong>: Lock STX tokens until a future block height.</li>
  <li><strong>Use Case</strong>: Save funds for later (delayed vesting, time-based release).</li>
  <li><strong>Key Features</strong>:
    <ul>
      <li>Users deposit STX and choose how many blocks to lock.</li>
      <li>Withdrawal is only allowed after the unlock time.</li>
      <li>Includes helpful read-only utilities for vault status.</li>
    </ul>
  </li>
</ul>

<hr />

<h2 id="-code-breakdown">📄 Code Breakdown</h2>

<h3 id="️-1-define-vault-storage">🗃️ 1. Define Vault Storage</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-map vaults
  { owner: principal }
  {
    amount: uint,
    unlock-height: uint
  }
)

</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">vaults</code> is a map that stores each user’s deposit and unlock time.</li>
  <li>Key: <code class="language-plaintext highlighter-rouge">{ owner: principal }</code> — user’s wallet address.</li>
  <li>Value: <code class="language-plaintext highlighter-rouge">{ amount, unlock-height }</code> — how much they deposited and when it unlocks.</li>
</ul>

<hr />

<h3 id="-2-deposit-stx-with-a-lock">💰 2. Deposit STX with a Lock</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (deposit (amount uint) (lock-blocks uint))

</code></pre></div></div>

<h3 id="what-it-does">What it does:</h3>

<ul>
  <li>Lets users deposit STX and lock them for a certain number of blocks.</li>
</ul>

<h3 id="how-it-works">How it works:</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(asserts! (&gt; amount u0) (err u1)) ;; Must deposit more than 0
(asserts! (and (&gt;= lock-blocks u1) (&lt;= lock-blocks u52560)) (err u5)) ;; Lock duration between 1 and ~1 year
(asserts! (is-none (map-get? vaults {owner: tx-sender})) (err u2)) ;; User can only have one active vault

</code></pre></div></div>

<ul>
  <li>It calculates the unlock time:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(let ((unlock-at (+ current-height lock-blocks)))

</code></pre></div></div>

<ul>
  <li>Stores the vault:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(map-set vaults {owner: tx-sender} { amount: amount, unlock-height: unlock-at })

</code></pre></div></div>

<ul>
  <li>Transfers STX from the user to the contract:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))

</code></pre></div></div>

<ul>
  <li>Returns the unlock block height:</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(ok unlock-at)

</code></pre></div></div>

<hr />

<h3 id="-3-withdraw-after-unlock">🏧 3. Withdraw After Unlock</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (withdraw)

</code></pre></div></div>

<h3 id="what-it-does-1">What it does:</h3>

<ul>
  <li>Allows users to withdraw <strong>only after the lock period ends</strong>.</li>
</ul>

<h3 id="steps">Steps:</h3>

<ol>
  <li>Load the vault:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(let ((vault-data (unwrap! (map-get? vaults {owner: tx-sender}) (err u3)))

</code></pre></div></div>

<ol>
  <li>Check unlock time:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(asserts! (&gt;= stacks-block-height unlock-height) (err u4))

</code></pre></div></div>

<ol>
  <li>Remove vault record:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(map-delete vaults {owner: tx-sender})

</code></pre></div></div>

<ol>
  <li>Send STX back:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(try! (stx-transfer? amount (as-contract tx-sender) tx-sender))

</code></pre></div></div>

<ol>
  <li>Return amount withdrawn:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(ok amount)

</code></pre></div></div>

<hr />

<h3 id="-4-read-only-functions">🧾 4. Read-Only Functions</h3>

<p>These don’t modify state and are used to <strong>check vault info</strong>:</p>

<h3 id="-get-vault-info">🔎 Get Vault Info</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-read-only (get-vault-info (user principal))
  (map-get? vaults {owner: user})
)

</code></pre></div></div>

<ul>
  <li>Returns a user’s vault data (amount + unlock-height), or <code class="language-plaintext highlighter-rouge">none</code>.</li>
</ul>

<hr />

<h3 id="-check-if-withdrawal-is-allowed">⌛ Check If Withdrawal Is Allowed</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-read-only (can-withdraw (user principal))
  (match (map-get? vaults {owner: user})
    vault (&gt;= stacks-block-height (get unlock-height vault))
    false
  )
)

</code></pre></div></div>

<ul>
  <li>Returns <code class="language-plaintext highlighter-rouge">true</code> if user’s lock time has passed.</li>
</ul>

<hr />

<h3 id="-check-remaining-time">🕒 Check Remaining Time</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-read-only (blocks-until-unlock (user principal))
  (match (map-get? vaults {owner: user})
    vault (if (&gt;= stacks-block-height (get unlock-height vault))
            u0
            (- (get unlock-height vault) stacks-block-height))
    u0
  )
)

</code></pre></div></div>

<ul>
  <li>Tells how many blocks are left until the user can withdraw.</li>
</ul>

<hr />

<h2 id="-step-by-step-example">🧭 Step-by-Step Example</h2>

<h3 id="-alice-deposits-100-stx-for-1-day-144-blocks">🧑 Alice Deposits 100 STX for 1 Day (~144 blocks)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(deposit u100 u144)

</code></pre></div></div>

<ul>
  <li>Current block: 1000</li>
  <li>Unlock block: 1000 + 144 = 1144</li>
  <li>STX is locked in contract.</li>
</ul>

<hr />

<h3 id="-alice-tries-to-withdraw-at-block-1100">🧑 Alice tries to withdraw at block 1100</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(withdraw)

</code></pre></div></div>

<ul>
  <li>Fails: <code class="language-plaintext highlighter-rouge">stacks-block-height &lt; unlock-height</code></li>
  <li>Returns error: <code class="language-plaintext highlighter-rouge">err u4</code> (Too early)</li>
</ul>

<hr />

<h3 id="-alice-tries-at-block-1145">✅ Alice tries at block 1145</h3>

<ul>
  <li>Succeeds!</li>
  <li>STX is sent back.</li>
  <li>Vault entry is deleted.</li>
</ul>

<hr />

<h2 id="-summary-table">📘 Summary Table</h2>

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">deposit</code></td>
      <td>Lock STX for a set number of blocks</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">withdraw</code></td>
      <td>Retrieve STX after unlock height</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-vault-info</code></td>
      <td>View stored amount and unlock time</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">can-withdraw</code></td>
      <td>Check if user can withdraw yet</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">blocks-until-unlock</code></td>
      <td>See how many blocks are left to wait</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">vaults</code> map</td>
      <td>Stores each user’s locked STX &amp; unlock time</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="-real-world-analogy">🧩 Real-World Analogy</h2>

<p>Imagine putting money in a <strong>timed safe</strong>:</p>

<ul>
  <li>You set the lock for 24 hours.</li>
  <li>Once the time is up, you can unlock it and take the money.</li>
  <li>You can’t change your mind or unlock early.</li>
</ul>

<h2 id="this-is-the-simple-frontend-code-that-i-have-written">This is the simple frontend code that I have written.</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { useState, useEffect } from 'react';
import { connect, disconnect, isConnected, getLocalStorage, request } from '@stacks/connect';
import { broadcastTransaction, Cl, fetchCallReadOnlyFunction, cvToValue } from '@stacks/transactions';

export default function App() {
  const [walletAddress, setWalletAddress] = useState('');
  const [amount, setAmount] = useState();
  const [lockBlocks, setLockBlocks] = useState();
  const [vaultInfo, setVaultInfo] = useState(null);
  const [blocksUntilUnlock, setBlocksUntilUnlock] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [success, setSuccess] = useState('');


  const CONTRACT_ADDRESS = 'ST2WD8TKH9C3VKX4C355RGPPWFGYRAA9WT29SFQZY.time-vault3';
  

  useEffect(() =&gt; {
    const checkConnection = async () =&gt; {
      try {
        if (isConnected()) {
          const data = getLocalStorage();
          const stxAddress = data?.addresses?.stx?.[0]?.address;
          setWalletAddress(stxAddress);
          console.log('hello',typeof stxAddress)
          console.log("address:", stxAddress);
          if (stxAddress) {
            try{
            await fetchVaultInfo(stxAddress);
            }catch (err) {
              console.error("fetchVault failed", err)
            }
          }
        }
      } catch (err) {
        console.error('Connection check failed:', err);
        setError('Failed to check wallet connection');
      }
    };
    checkConnection();
  }, [walletAddress]);

  const handleConnect = async () =&gt; {
    try {
      setLoading(true);
      setError('');
      await connect();
      const data = getLocalStorage();
      const stxAddress = data?.addresses?.stx?.[0]?.address || '';
     
      setWalletAddress(stxAddress);
      await fetchVaultInfo(stxAddress);
      setSuccess('Wallet connected successfully');
      setTimeout(() =&gt; setSuccess(''), 3000);
    } catch (error) {
      console.error('Connection failed:', error);
      setError('Wallet connection failed');
    } finally {
      setLoading(false);
    }
  };

  const handleDisconnect = () =&gt; {
    try {
      disconnect();
      setWalletAddress('');
      setVaultInfo(null);
      setBlocksUntilUnlock(null);
      setSuccess('Wallet disconnected');
      setTimeout(() =&gt; setSuccess(''), 3000);
    } catch (err) {
      console.error('Disconnect failed:', err);
      setError('Failed to disconnect wallet');
    }
  };

  const depositSTX = async () =&gt; {
    if (!amount || !lockBlocks || Number(amount) &lt;= 0 || Number(lockBlocks) &lt;= 0) {
      setError('Please enter valid amount and lock period');
      return;
    }

    try {
      setLoading(true);
      setError('');
      const amountMicroSTX = (amount * 1000000);
      const blocks = (lockBlocks);

      console.log("Depositing:", {
        amountSTX: amount,
        amountMicroSTX,
        lockBlocks: typeof blocks
      });

      const cvAmount = Cl.uint((amountMicroSTX)) // convert STX to micro-STX
      const cvBlock = Cl.uint((blocks))

      console.log("lala", {cvAmount, Block: cvBlock})

      console.log("amount:", amount * 1000000);
      const response = await request('stx_callContract', {
        contract: CONTRACT_ADDRESS,
        functionName: 'deposit',
        functionArgs: [
          cvAmount,
          cvBlock
        ],
        network: 'testnet',
        postConditionMode: 'allow'
      });
      console.log("amount:", amount * 1000000);
      console.log('Deposit success:', response);
      setSuccess('Deposit transaction submitted');
      setTimeout(() =&gt; setSuccess(''), 3000);
      // Wait a few seconds then refresh vault info
      setTimeout(() =&gt; fetchVaultInfo(walletAddress), 5000);
    } catch (error) {
      console.error('Deposit failed:', error);
      setError('Deposit failed: ' + (error.message || 'Unknown error'));
    } finally {
      setLoading(false);
    }
  };

  const withdrawSTX = async () =&gt; {
    try {
      setLoading(true);
      setError('');
      const response = await request('stx_callContract', {
        contract: CONTRACT_ADDRESS,
        functionName: 'withdraw',
        functionArgs: [],
        network: 'testnet'
      });
      console.log('Withdraw success:', response);
      setSuccess('Withdrawal transaction submitted');
      setTimeout(() =&gt; setSuccess(''), 3000);
      // Wait a few seconds then refresh vault info
      setTimeout(() =&gt; fetchVaultInfo(walletAddress), 5000);
    } catch (error) {
      console.error('Withdraw failed:', error);
      setError('Withdrawal failed: ' + (error.message || 'Unknown error'));
    } finally {
      setLoading(false);
    }
  };

  const fetchVaultInfo = async (address) =&gt; {
    try {
      setLoading(true);
      setError('');
      
      // Get vault inf0

      const VaultTxOptions = {
        contractName: 'time-vault3',
        contractAddress: 'ST2WD8TKH9C3VKX4C355RGPPWFGYRAA9WT29SFQZY',
        functionName: 'get-vault-info',
        functionArgs: [Cl.standardPrincipal(address)],
        senderAddress: address,
        network: 'testnet'
      }

      const vaultTransaction = await fetchCallReadOnlyFunction(VaultTxOptions);
      
      console.log(vaultTransaction);
      const readable = cvToValue(vaultTransaction)
      console.log(readable.value);
      console.log(readable.value['unlock-height'].value);
      


      
      
      // Get blocks until unlock
      const BlockTxOptions = {
        contractName: 'time-vault3',
        contractAddress: 'ST2WD8TKH9C3VKX4C355RGPPWFGYRAA9WT29SFQZY',
        functionName: 'blocks-until-unlock',
        functionArgs: [Cl.standardPrincipal(address)],
        senderAddress: address,
        network: 'testnet'
      }

      const blockTransaction = await fetchCallReadOnlyFunction(BlockTxOptions);
      
      console.log(blockTransaction);
      
      const blockreadable = cvToValue(blockTransaction);
      console.log(blockreadable);



      // Check if vault exists (response will be null if no vault)
      if (vaultTransaction === null) {
        setVaultInfo(null);
        setBlocksUntilUnlock(null);
      } else {
        setVaultInfo(readable);
        setBlocksUntilUnlock();
      }
    } catch (error) {
      console.error('Fetch Vault Info failed:', error);
      setError('No Vault');
    } finally {
      setLoading(false);
    }
  };

  const formatSTX = (microstx) =&gt; {
    return (microstx / 1000000).toFixed(6);
  };

  return (
    &lt;div className="min-h-screen bg-gray-100 flex flex-col items-center justify-center p-4"&gt;
      &lt;div className="max-w-md w-full bg-white shadow-lg rounded-lg p-6 space-y-6"&gt;
        &lt;h1 className="text-2xl font-bold text-center text-indigo-600"&gt;STX Time Vault&lt;/h1&gt;

        {/* Status messages */}
        {error &amp;&amp; (
          &lt;div className="p-3 bg-red-100 text-red-700 rounded"&gt;
            {error}
            &lt;button onClick={() =&gt; setError('')} className="float-right font-bold"&gt;
              ×
            &lt;/button&gt;
          &lt;/div&gt;
        )}
        {success &amp;&amp; (
          &lt;div className="p-3 bg-green-100 text-green-700 rounded"&gt;
            {success}
            &lt;button onClick={() =&gt; setSuccess('')} className="float-right font-bold"&gt;
              ×
            &lt;/button&gt;
          &lt;/div&gt;
        )}

        {loading &amp;&amp; (
          &lt;div className="text-center py-4"&gt;
            &lt;div className="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-indigo-600"&gt;&lt;/div&gt;
            &lt;p className="mt-2 text-gray-600"&gt;Processing...&lt;/p&gt;
          &lt;/div&gt;
        )}

        {!walletAddress ? (
          &lt;button
            onClick={handleConnect}
            disabled={loading}
            className="w-full bg-indigo-600 text-white py-2 rounded hover:bg-indigo-700 disabled:bg-indigo-300"
          &gt;
            Connect Wallet
          &lt;/button&gt;
        ) : (
          &lt;&gt;
            &lt;div className="space-y-2"&gt;
              &lt;p className="text-gray-700 text-sm break-words"&gt;Connected: {walletAddress}&lt;/p&gt;
              &lt;button
                onClick={handleDisconnect}
                disabled={loading}
                className="w-full bg-red-500 text-white py-2 rounded hover:bg-red-600 disabled:bg-red-300"
              &gt;
                Disconnect
              &lt;/button&gt;
            &lt;/div&gt;

            &lt;div className="space-y-4"&gt;
              &lt;h2 className="text-lg font-semibold text-gray-800"&gt;Deposit STX&lt;/h2&gt;
              &lt;input
                type="number"
                placeholder="Amount (STX)"
                className="w-full p-2 border rounded"
                value={amount}
                onChange={(e) =&gt; setAmount(e.target.value)}
                disabled={loading}
                min="0.000001"
                step="0.000001"
              /&gt;
              &lt;input
                type="number"
                placeholder="Lock Blocks (1 block ≈ 10 minutes)"
                className="w-full p-2 border rounded"
                value={lockBlocks}
                onChange={(e) =&gt; setLockBlocks(e.target.value)}
                disabled={loading}
                min="1"
              /&gt;
              &lt;button
                onClick={depositSTX}
                disabled={loading || !amount || !lockBlocks}
                className="w-full bg-green-500 text-white py-2 rounded hover:bg-green-600 disabled:bg-green-300"
              &gt;
                Deposit
              &lt;/button&gt;
            &lt;/div&gt;

            {vaultInfo ? (
              &lt;div className="space-y-2 p-4 bg-gray-50 rounded"&gt;
                &lt;h2 className="text-lg font-semibold text-gray-800"&gt;Vault Info&lt;/h2&gt;
                &lt;p&gt;Amount Locked: {formatSTX(vaultInfo.value.amount.value)} STX&lt;/p&gt;
                &lt;p&gt;Unlock Block Height: {vaultInfo.value['unlock-height'].value}&lt;/p&gt;
                {blocksUntilUnlock == 0 ? 
                &lt;&gt;
                &lt;p&gt;Blocks Remaining: {blocksUntilUnlock} b&lt;/p&gt;
                &lt;p className="text-sm text-gray-500"&gt;
                  Approx. {(blocksUntilUnlock * 10 / 60).toFixed(1)} hours remaining
                &lt;/p&gt;
                &lt;/&gt; : &lt;&gt;&lt;p&gt;You can withdraw your stx&lt;/p&gt;&lt;/&gt; }
              &lt;/div&gt;
            ) : (
              &lt;div className="p-4 bg-gray-50 rounded text-center"&gt;
                &lt;p className="text-gray-600"&gt;No active vault found for this address&lt;/p&gt;
              &lt;/div&gt;
            )}

            &lt;button
              onClick={withdrawSTX}
              disabled={loading || !vaultInfo}
              className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 disabled:bg-blue-300"
            &gt;
              Withdraw
            &lt;/button&gt;
          &lt;/&gt;
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre></div></div>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[;; Time-Locked STX Vault - A contract that allows users to deposit STX and lock them for a specified period ;; Users can only withdraw once the specified block height is reached ;; Define storage for the vault deposits ;; Map: user-address -&gt; (amount, unlock-height) (define-map vaults { owner: principal } { amount: uint, unlock-height: uint } ) ;; Public function to deposit STX and set a lock time (define-public (deposit (amount uint) (lock-blocks uint)) (let ( (current-height stacks-block-height) ) (asserts! (&gt; amount u0) (err u1)) (asserts! (and (&gt;= lock-blocks u1) (&lt;= lock-blocks u52560)) (err u5)) (asserts! (is-none (map-get? vaults {owner: tx-sender})) (err u2)) (let ((unlock-at (+ current-height lock-blocks))) (map-set vaults {owner: tx-sender} { amount: amount, unlock-height: unlock-at } ) (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) (ok unlock-at) ) ) ) ;; Public function to withdraw STX after lock period (define-public (withdraw) (let ( (vault-data (unwrap! (map-get? vaults {owner: tx-sender}) (err u3))) (amount (get amount vault-data)) (unlock-height (get unlock-height vault-data)) ) (asserts! (&gt;= stacks-block-height unlock-height) (err u4)) (map-delete vaults {owner: tx-sender}) (try! (stx-transfer? amount (as-contract tx-sender) tx-sender)) (ok amount) ;; Return the amount withdrawn ) ) ;; Read-only function to get information about a user's vault (define-read-only (get-vault-info (user principal)) (map-get? vaults {owner: user}) ) ;; Read-only function to check if withdrawal is possible (define-read-only (can-withdraw (user principal)) (match (map-get? vaults {owner: user}) vault (&gt;= stacks-block-height (get unlock-height vault)) false ) ) ;; Read-only function to check how many blocks remain until withdrawal is possible (define-read-only (blocks-until-unlock (user principal)) (match (map-get? vaults {owner: user}) vault (if (&gt;= stacks-block-height (get unlock-height vault)) u0 (- (get unlock-height vault) stacks-block-height)) u0 ) ) This smart contract enables users to deposit STX tokens and lock them for a certain number of block heights. Once the time has passed, they can withdraw their tokens. 🧠 Brief Overview Purpose: Lock STX tokens until a future block height. Use Case: Save funds for later (delayed vesting, time-based release). Key Features: Users deposit STX and choose how many blocks to lock. Withdrawal is only allowed after the unlock time. Includes helpful read-only utilities for vault status. 📄 Code Breakdown 🗃️ 1. Define Vault Storage (define-map vaults { owner: principal } { amount: uint, unlock-height: uint } ) vaults is a map that stores each user’s deposit and unlock time. Key: { owner: principal } — user’s wallet address. Value: { amount, unlock-height } — how much they deposited and when it unlocks. 💰 2. Deposit STX with a Lock (define-public (deposit (amount uint) (lock-blocks uint)) What it does: Lets users deposit STX and lock them for a certain number of blocks. How it works: (asserts! (&gt; amount u0) (err u1)) ;; Must deposit more than 0 (asserts! (and (&gt;= lock-blocks u1) (&lt;= lock-blocks u52560)) (err u5)) ;; Lock duration between 1 and ~1 year (asserts! (is-none (map-get? vaults {owner: tx-sender})) (err u2)) ;; User can only have one active vault It calculates the unlock time: (let ((unlock-at (+ current-height lock-blocks))) Stores the vault: (map-set vaults {owner: tx-sender} { amount: amount, unlock-height: unlock-at }) Transfers STX from the user to the contract: (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) Returns the unlock block height: (ok unlock-at) 🏧 3. Withdraw After Unlock (define-public (withdraw) What it does: Allows users to withdraw only after the lock period ends. Steps: Load the vault: (let ((vault-data (unwrap! (map-get? vaults {owner: tx-sender}) (err u3))) Check unlock time: (asserts! (&gt;= stacks-block-height unlock-height) (err u4)) Remove vault record: (map-delete vaults {owner: tx-sender}) Send STX back: (try! (stx-transfer? amount (as-contract tx-sender) tx-sender)) Return amount withdrawn: (ok amount) 🧾 4. Read-Only Functions These don’t modify state and are used to check vault info: 🔎 Get Vault Info (define-read-only (get-vault-info (user principal)) (map-get? vaults {owner: user}) ) Returns a user’s vault data (amount + unlock-height), or none. ⌛ Check If Withdrawal Is Allowed (define-read-only (can-withdraw (user principal)) (match (map-get? vaults {owner: user}) vault (&gt;= stacks-block-height (get unlock-height vault)) false ) ) Returns true if user’s lock time has passed. 🕒 Check Remaining Time (define-read-only (blocks-until-unlock (user principal)) (match (map-get? vaults {owner: user}) vault (if (&gt;= stacks-block-height (get unlock-height vault)) u0 (- (get unlock-height vault) stacks-block-height)) u0 ) ) Tells how many blocks are left until the user can withdraw. 🧭 Step-by-Step Example 🧑 Alice Deposits 100 STX for 1 Day (~144 blocks) (deposit u100 u144) Current block: 1000 Unlock block: 1000 + 144 = 1144 STX is locked in contract. 🧑 Alice tries to withdraw at block 1100 (withdraw) Fails: stacks-block-height &lt; unlock-height Returns error: err u4 (Too early) ✅ Alice tries at block 1145 Succeeds! STX is sent back. Vault entry is deleted. 📘 Summary Table Function Purpose deposit Lock STX for a set number of blocks withdraw Retrieve STX after unlock height get-vault-info View stored amount and unlock time can-withdraw Check if user can withdraw yet blocks-until-unlock See how many blocks are left to wait vaults map Stores each user’s locked STX &amp; unlock time 🧩 Real-World Analogy Imagine putting money in a timed safe: You set the lock for 24 hours. Once the time is up, you can unlock it and take the money. You can’t change your mind or unlock early. This is the simple frontend code that I have written. import { useState, useEffect } from 'react'; import { connect, disconnect, isConnected, getLocalStorage, request } from '@stacks/connect'; import { broadcastTransaction, Cl, fetchCallReadOnlyFunction, cvToValue } from '@stacks/transactions'; export default function App() { const [walletAddress, setWalletAddress] = useState(''); const [amount, setAmount] = useState(); const [lockBlocks, setLockBlocks] = useState(); const [vaultInfo, setVaultInfo] = useState(null); const [blocksUntilUnlock, setBlocksUntilUnlock] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const CONTRACT_ADDRESS = 'ST2WD8TKH9C3VKX4C355RGPPWFGYRAA9WT29SFQZY.time-vault3'; useEffect(() =&gt; { const checkConnection = async () =&gt; { try { if (isConnected()) { const data = getLocalStorage(); const stxAddress = data?.addresses?.stx?.[0]?.address; setWalletAddress(stxAddress); console.log('hello',typeof stxAddress) console.log("address:", stxAddress); if (stxAddress) { try{ await fetchVaultInfo(stxAddress); }catch (err) { console.error("fetchVault failed", err) } } } } catch (err) { console.error('Connection check failed:', err); setError('Failed to check wallet connection'); } }; checkConnection(); }, [walletAddress]); const handleConnect = async () =&gt; { try { setLoading(true); setError(''); await connect(); const data = getLocalStorage(); const stxAddress = data?.addresses?.stx?.[0]?.address || ''; setWalletAddress(stxAddress); await fetchVaultInfo(stxAddress); setSuccess('Wallet connected successfully'); setTimeout(() =&gt; setSuccess(''), 3000); } catch (error) { console.error('Connection failed:', error); setError('Wallet connection failed'); } finally { setLoading(false); } }; const handleDisconnect = () =&gt; { try { disconnect(); setWalletAddress(''); setVaultInfo(null); setBlocksUntilUnlock(null); setSuccess('Wallet disconnected'); setTimeout(() =&gt; setSuccess(''), 3000); } catch (err) { console.error('Disconnect failed:', err); setError('Failed to disconnect wallet'); } }; const depositSTX = async () =&gt; { if (!amount || !lockBlocks || Number(amount) &lt;= 0 || Number(lockBlocks) &lt;= 0) { setError('Please enter valid amount and lock period'); return; } try { setLoading(true); setError(''); const amountMicroSTX = (amount * 1000000); const blocks = (lockBlocks); console.log("Depositing:", { amountSTX: amount, amountMicroSTX, lockBlocks: typeof blocks }); const cvAmount = Cl.uint((amountMicroSTX)) // convert STX to micro-STX const cvBlock = Cl.uint((blocks)) console.log("lala", {cvAmount, Block: cvBlock}) console.log("amount:", amount * 1000000); const response = await request('stx_callContract', { contract: CONTRACT_ADDRESS, functionName: 'deposit', functionArgs: [ cvAmount, cvBlock ], network: 'testnet', postConditionMode: 'allow' }); console.log("amount:", amount * 1000000); console.log('Deposit success:', response); setSuccess('Deposit transaction submitted'); setTimeout(() =&gt; setSuccess(''), 3000); // Wait a few seconds then refresh vault info setTimeout(() =&gt; fetchVaultInfo(walletAddress), 5000); } catch (error) { console.error('Deposit failed:', error); setError('Deposit failed: ' + (error.message || 'Unknown error')); } finally { setLoading(false); } }; const withdrawSTX = async () =&gt; { try { setLoading(true); setError(''); const response = await request('stx_callContract', { contract: CONTRACT_ADDRESS, functionName: 'withdraw', functionArgs: [], network: 'testnet' }); console.log('Withdraw success:', response); setSuccess('Withdrawal transaction submitted'); setTimeout(() =&gt; setSuccess(''), 3000); // Wait a few seconds then refresh vault info setTimeout(() =&gt; fetchVaultInfo(walletAddress), 5000); } catch (error) { console.error('Withdraw failed:', error); setError('Withdrawal failed: ' + (error.message || 'Unknown error')); } finally { setLoading(false); } }; const fetchVaultInfo = async (address) =&gt; { try { setLoading(true); setError(''); // Get vault inf0 const VaultTxOptions = { contractName: 'time-vault3', contractAddress: 'ST2WD8TKH9C3VKX4C355RGPPWFGYRAA9WT29SFQZY', functionName: 'get-vault-info', functionArgs: [Cl.standardPrincipal(address)], senderAddress: address, network: 'testnet' } const vaultTransaction = await fetchCallReadOnlyFunction(VaultTxOptions); console.log(vaultTransaction); const readable = cvToValue(vaultTransaction) console.log(readable.value); console.log(readable.value['unlock-height'].value); // Get blocks until unlock const BlockTxOptions = { contractName: 'time-vault3', contractAddress: 'ST2WD8TKH9C3VKX4C355RGPPWFGYRAA9WT29SFQZY', functionName: 'blocks-until-unlock', functionArgs: [Cl.standardPrincipal(address)], senderAddress: address, network: 'testnet' } const blockTransaction = await fetchCallReadOnlyFunction(BlockTxOptions); console.log(blockTransaction); const blockreadable = cvToValue(blockTransaction); console.log(blockreadable); // Check if vault exists (response will be null if no vault) if (vaultTransaction === null) { setVaultInfo(null); setBlocksUntilUnlock(null); } else { setVaultInfo(readable); setBlocksUntilUnlock(); } } catch (error) { console.error('Fetch Vault Info failed:', error); setError('No Vault'); } finally { setLoading(false); } }; const formatSTX = (microstx) =&gt; { return (microstx / 1000000).toFixed(6); }; return ( &lt;div className="min-h-screen bg-gray-100 flex flex-col items-center justify-center p-4"&gt; &lt;div className="max-w-md w-full bg-white shadow-lg rounded-lg p-6 space-y-6"&gt; &lt;h1 className="text-2xl font-bold text-center text-indigo-600"&gt;STX Time Vault&lt;/h1&gt; {/* Status messages */} {error &amp;&amp; ( &lt;div className="p-3 bg-red-100 text-red-700 rounded"&gt; {error} &lt;button onClick={() =&gt; setError('')} className="float-right font-bold"&gt; × &lt;/button&gt; &lt;/div&gt; )} {success &amp;&amp; ( &lt;div className="p-3 bg-green-100 text-green-700 rounded"&gt; {success} &lt;button onClick={() =&gt; setSuccess('')} className="float-right font-bold"&gt; × &lt;/button&gt; &lt;/div&gt; )} {loading &amp;&amp; ( &lt;div className="text-center py-4"&gt; &lt;div className="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-indigo-600"&gt;&lt;/div&gt; &lt;p className="mt-2 text-gray-600"&gt;Processing...&lt;/p&gt; &lt;/div&gt; )} {!walletAddress ? ( &lt;button onClick={handleConnect} disabled={loading} className="w-full bg-indigo-600 text-white py-2 rounded hover:bg-indigo-700 disabled:bg-indigo-300" &gt; Connect Wallet &lt;/button&gt; ) : ( &lt;&gt; &lt;div className="space-y-2"&gt; &lt;p className="text-gray-700 text-sm break-words"&gt;Connected: {walletAddress}&lt;/p&gt; &lt;button onClick={handleDisconnect} disabled={loading} className="w-full bg-red-500 text-white py-2 rounded hover:bg-red-600 disabled:bg-red-300" &gt; Disconnect &lt;/button&gt; &lt;/div&gt; &lt;div className="space-y-4"&gt; &lt;h2 className="text-lg font-semibold text-gray-800"&gt;Deposit STX&lt;/h2&gt; &lt;input type="number" placeholder="Amount (STX)" className="w-full p-2 border rounded" value={amount} onChange={(e) =&gt; setAmount(e.target.value)} disabled={loading} min="0.000001" step="0.000001" /&gt; &lt;input type="number" placeholder="Lock Blocks (1 block ≈ 10 minutes)" className="w-full p-2 border rounded" value={lockBlocks} onChange={(e) =&gt; setLockBlocks(e.target.value)} disabled={loading} min="1" /&gt; &lt;button onClick={depositSTX} disabled={loading || !amount || !lockBlocks} className="w-full bg-green-500 text-white py-2 rounded hover:bg-green-600 disabled:bg-green-300" &gt; Deposit &lt;/button&gt; &lt;/div&gt; {vaultInfo ? ( &lt;div className="space-y-2 p-4 bg-gray-50 rounded"&gt; &lt;h2 className="text-lg font-semibold text-gray-800"&gt;Vault Info&lt;/h2&gt; &lt;p&gt;Amount Locked: {formatSTX(vaultInfo.value.amount.value)} STX&lt;/p&gt; &lt;p&gt;Unlock Block Height: {vaultInfo.value['unlock-height'].value}&lt;/p&gt; {blocksUntilUnlock == 0 ? &lt;&gt; &lt;p&gt;Blocks Remaining: {blocksUntilUnlock} b&lt;/p&gt; &lt;p className="text-sm text-gray-500"&gt; Approx. {(blocksUntilUnlock * 10 / 60).toFixed(1)} hours remaining &lt;/p&gt; &lt;/&gt; : &lt;&gt;&lt;p&gt;You can withdraw your stx&lt;/p&gt;&lt;/&gt; } &lt;/div&gt; ) : ( &lt;div className="p-4 bg-gray-50 rounded text-center"&gt; &lt;p className="text-gray-600"&gt;No active vault found for this address&lt;/p&gt; &lt;/div&gt; )} &lt;button onClick={withdrawSTX} disabled={loading || !vaultInfo} className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 disabled:bg-blue-300" &gt; Withdraw &lt;/button&gt; &lt;/&gt; )} &lt;/div&gt; &lt;/div&gt; ); }]]></summary></entry><entry><title type="html">My own token (Bscoin) in clarity</title><link href="https://basanta11subedi.github.io/blog/post-6th-project-in-clarity/" rel="alternate" type="text/html" title="My own token (Bscoin) in clarity" /><published>2025-05-26T00:00:00+00:00</published><updated>2025-05-26T00:00:00+00:00</updated><id>https://basanta11subedi.github.io/blog/post-6th-project-in-clarity</id><content type="html" xml:base="https://basanta11subedi.github.io/blog/post-6th-project-in-clarity/"><![CDATA[<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; title: token
;; version:2
;; summary:This is the fungible token for the code4stx project 

;; traits
(impl-trait 'ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard.sip-010-trait)

;; Token definitions
(define-fungible-token BScoin u1000000000000)

;; constants
(define-constant err-not-token-owner (err u101))
(define-constant err-insufficient-amount (err u102))
(define-constant err-owner-only (err u103))
(define-constant err-not-whitelisted (err u104))
(define-constant err-kyc-not-found (err u105))
(define-constant err-kyc-already-approved (err u106))
(define-constant err-kyc-not-pending (err u107))

;; data variables
(define-data-var contract-owner principal tx-sender)

;; Maps
(define-map whitelist principal bool)
(define-map kyc-submissions principal {ipfs-hash: (string-utf8 100), status: (string-utf8 20)})

;; Read only functions
(define-read-only (get-name) (ok "BScoin"))
(define-read-only (get-symbol) (ok "BSC"))
(define-read-only (get-decimals) (ok u6))
(define-read-only (get-balance (who principal)) (ok (ft-get-balance BScoin who)))
(define-read-only (get-token-uri) (ok none))
(define-read-only (get-total-supply) (ok (ft-get-supply BScoin)))
(define-read-only (get-contract-owner) (ok (var-get contract-owner)))
(define-read-only (get-kyc-status (user principal)) (ok (map-get? kyc-submissions user)))

;; Public functions

;; Function to transfer the token
(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
    (begin
       (asserts! (is-eq tx-sender sender) err-not-token-owner)
       (asserts! (default-to false (map-get? whitelist sender)) err-not-whitelisted)
       (asserts! (default-to false (map-get? whitelist recipient)) err-not-whitelisted)
       (asserts! (&gt; amount u0) err-insufficient-amount)
       (try! (ft-transfer? BScoin amount sender recipient))
       (print { event-type: "Transfer", amount: amount, sender: sender, recipient: recipient })
       (match memo to-print (print to-print) 0x)
       (ok true)
    )
)

;; Function to mint the tokens (only by owner)
(define-public (mint (amount uint) (recipient principal))
    (begin 
        (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only)
        (asserts! (default-to false (map-get? whitelist recipient)) err-not-whitelisted)
        (asserts! (&gt; amount u0) err-insufficient-amount)
        (try! (ft-mint? BScoin amount recipient)) 
        (print { event-type: "Mint", amount: amount, recipient: recipient})
        (ok true)
    )
)

;; Function to burn the tokens
(define-public (burn (amount uint))
    (begin 
        (asserts! (&gt; amount u0) err-insufficient-amount)
        (try! (ft-burn? BScoin amount tx-sender))
        (print { event-type: "burn", amount: amount, recipient: tx-sender})
        (ok true)
    )
)

;; Transfer ownership
(define-public (transfer-ownership (new-owner principal))
    (begin
        (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only)
        (var-set contract-owner new-owner)
        (ok true)
    )
)

;; Function to submit KYC documents (CID from IPFS)
(define-public (submit-kyc (ipfs-hash (string-utf8 100)))
  (begin
    (let ((empty-kyc (tuple (ipfs-hash u"")  (status u"none"))))
    (asserts! (is-eq (get status (default-to empty-kyc (map-get? kyc-submissions tx-sender))) u"none") err-kyc-already-approved))
    (map-set kyc-submissions tx-sender (tuple (ipfs-hash ipfs-hash) (status u"pending")))
    (print (tuple (event-type u"KYCSubmitted") (user tx-sender) (ipfs-hash ipfs-hash)))
    (ok true)
  )
)

;; Function to approve KYC (admin only)
(define-public (approve-kyc (user principal))
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only)
    (let ((kyc-data (unwrap! (map-get? kyc-submissions user) err-kyc-not-found)))
      (asserts! (is-eq (get status kyc-data) u"pending") err-kyc-not-pending)
      (map-set kyc-submissions user (tuple (ipfs-hash (get ipfs-hash kyc-data)) (status u"approved")))
      (map-set whitelist user true)
      (print {event-type: "KYCApproved", user: user, by: tx-sender})
      (ok true)
    )
  )
)

;; Function to reject KYC (admin only)
(define-public (reject-kyc (user principal) (reason (string-utf8 1000)))
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only)
    (let ((kyc-data (unwrap! (map-get? kyc-submissions user) err-kyc-not-found)))
      (asserts! (is-eq (get status kyc-data) u"pending") err-kyc-not-pending)
      (map-set kyc-submissions user (tuple (ipfs-hash (get ipfs-hash kyc-data)) (status u"rejected")))
      (print {event-type: "KYCRejected", user: user, by: tx-sender, reason: reason})
      (ok true)
    )
  )
)

;; Function to remove from whitelist (admin only)
(define-public (remove-from-whitelist (user principal))
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only)
    (map-delete whitelist user)
    (ok true)
  )
)
;; ;; Function to add to whitelist (admin only)
;; (define-public (add-to-whitelist (user principal))
;;   (begin
;;     (asserts! (is-eq tx-sender (var-get admin)) err-not-admin)
;;     (map-set whitelist user true)
;;     (ok true)
;;   )
;; )

;; ;; Function to add in the blacklist by the owner
;; (define-public (add-to-blacklist (user principal))
;;     (begin
;;         (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only)
;;         (map-set blacklist user true)
;;         (ok true)
;;     )
;; )

;; ;; Function to remove from blacklist by owner
;; (define-public (remove-from-blacklist (user principal))
;;     (begin
;;         (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only)
;;         (map-delete blacklist user)
;;         (ok true)
;;     )
;; )

</code></pre></div></div>

<p>This contract defines a SIP-010 compatible token (fungible token standard on Stacks), with the added layers of:</p>

<ul>
  <li>✅ <strong>Whitelisting</strong> (only approved users can interact)</li>
  <li>🔒 <strong>KYC Submissions</strong> via IPFS hashes</li>
  <li>👑 <strong>Owner-controlled minting, burning, and access</strong></li>
  <li>📦 Total supply: 1 trillion tokens (initial)</li>
</ul>

<hr />

<h3 id="-sip-010-trait-implementation">📄 SIP-010 Trait Implementation</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(impl-trait '...sip-010-trait)

</code></pre></div></div>

<p>This tells Clarity the contract follows the official <strong>fungible token standard</strong> (<code class="language-plaintext highlighter-rouge">SIP-010</code>), ensuring compatibility with wallets, DeFi protocols, and explorers.</p>

<hr />

<h2 id="-token-definition">🔐 Token Definition</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-fungible-token BScoin u1000000000000)

</code></pre></div></div>

<p>Defines a new token named <code class="language-plaintext highlighter-rouge">BScoin</code> with a total supply of <strong>1,000,000,000,000 units</strong> (1 trillion).</p>

<hr />

<h2 id="-constants--data">🧱 Constants &amp; Data</h2>

<h3 id="-constants">🔢 Constants</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-constant err-not-token-owner (err u101))

</code></pre></div></div>

<p>These are <strong>custom error codes</strong> for better debugging and consistent responses. Errors include:</p>

<table>
  <thead>
    <tr>
      <th>Error Code</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u101</code></td>
      <td>Sender is not the token owner</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u102</code></td>
      <td>Transfer amount is invalid (≤ 0)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u103</code></td>
      <td>Only contract owner can perform</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u104</code></td>
      <td>User is not whitelisted</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u105</code></td>
      <td>KYC not submitted</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u106</code></td>
      <td>KYC already submitted</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">u107</code></td>
      <td>KYC not in pending state</td>
    </tr>
  </tbody>
</table>

<h3 id="-variables--maps">🔧 Variables &amp; Maps</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-data-var contract-owner principal tx-sender)
(define-map whitelist principal bool)
(define-map kyc-submissions principal {
  ipfs-hash: (string-utf8 100),
  status: (string-utf8 20)
})

</code></pre></div></div>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">contract-owner</code></strong>: Controls privileged actions like minting, approving KYC, etc.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">whitelist</code></strong>: Tracks who can transfer or receive tokens.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">kyc-submissions</code></strong>: Stores IPFS hash + status (<code class="language-plaintext highlighter-rouge">none</code>, <code class="language-plaintext highlighter-rouge">pending</code>, <code class="language-plaintext highlighter-rouge">approved</code>, <code class="language-plaintext highlighter-rouge">rejected</code>).</li>
</ul>

<hr />

<h2 id="-read-only-functions">📖 Read-Only Functions</h2>

<p>These functions <strong>return data without modifying state</strong>:</p>

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-name</code></td>
      <td>Returns token name <code class="language-plaintext highlighter-rouge">BScoin</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-symbol</code></td>
      <td>Returns symbol <code class="language-plaintext highlighter-rouge">BSC</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-decimals</code></td>
      <td>Returns 6 decimals</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-balance</code></td>
      <td>Returns token balance of a user</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-total-supply</code></td>
      <td>Total token supply</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-contract-owner</code></td>
      <td>Owner address</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-token-uri</code></td>
      <td>Token URI (set to none)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get-kyc-status</code></td>
      <td>Returns a user’s KYC submission &amp; status</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="-core-public-functions">🚀 Core Public Functions</h2>

<h3 id="1--transfer">1. 🔄 <code class="language-plaintext highlighter-rouge">transfer</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (transfer amount sender recipient memo))

</code></pre></div></div>

<ul>
  <li>Checks sender = tx-sender</li>
  <li>Both sender and recipient <strong>must be whitelisted</strong></li>
  <li>Transfers token using <code class="language-plaintext highlighter-rouge">ft-transfer?</code></li>
  <li>Optionally logs a <code class="language-plaintext highlighter-rouge">memo</code> (e.g., transaction ID, reason)</li>
</ul>

<hr />

<h3 id="2--mint">2. 🧑‍🏭 <code class="language-plaintext highlighter-rouge">mint</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (mint amount recipient))

</code></pre></div></div>

<ul>
  <li>Only the <strong>contract owner</strong> can mint</li>
  <li>Recipient <strong>must be whitelisted</strong></li>
  <li>Mints tokens to recipient using <code class="language-plaintext highlighter-rouge">ft-mint?</code></li>
</ul>

<hr />

<h3 id="3--burn">3. 🔥 <code class="language-plaintext highlighter-rouge">burn</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (burn amount))

</code></pre></div></div>

<ul>
  <li>Any user can burn their own tokens</li>
  <li>Calls <code class="language-plaintext highlighter-rouge">ft-burn?</code></li>
</ul>

<hr />

<h3 id="4--transfer-ownership">4. 🔁 <code class="language-plaintext highlighter-rouge">transfer-ownership</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (transfer-ownership new-owner))

</code></pre></div></div>

<ul>
  <li>Only current owner can transfer control</li>
  <li>Updates <code class="language-plaintext highlighter-rouge">contract-owner</code></li>
</ul>

<hr />

<h2 id="-kyc-system">📝 KYC System</h2>

<h3 id="1--submit-kyc">1. 📤 <code class="language-plaintext highlighter-rouge">submit-kyc</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (submit-kyc ipfs-hash))

</code></pre></div></div>

<ul>
  <li>User submits KYC documents by <strong>providing an IPFS CID</strong></li>
  <li>Must not have already submitted</li>
  <li>Stores data as <code class="language-plaintext highlighter-rouge">(ipfs-hash, "pending")</code></li>
</ul>

<hr />

<h3 id="2--approve-kyc">2. ✅ <code class="language-plaintext highlighter-rouge">approve-kyc</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (approve-kyc user))

</code></pre></div></div>

<ul>
  <li>Only <strong>contract owner</strong> can call</li>
  <li>Approves a KYC request (<code class="language-plaintext highlighter-rouge">status = approved</code>)</li>
  <li>Also <strong>adds user to whitelist</strong></li>
</ul>

<hr />

<h3 id="3--reject-kyc">3. ❌ <code class="language-plaintext highlighter-rouge">reject-kyc</code></h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (reject-kyc user reason))

</code></pre></div></div>

<ul>
  <li>Sets KYC status to <code class="language-plaintext highlighter-rouge">"rejected"</code></li>
  <li>Logs the rejection reason</li>
</ul>

<hr />

<h2 id="-whitelist-management">✅ Whitelist Management</h2>

<h3 id="add--remove">Add / Remove</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(define-public (remove-from-whitelist user))

</code></pre></div></div>

<ul>
  <li>Admin-only removal from whitelist</li>
</ul>

<blockquote>
  <p>🔒 The add-to-whitelist function is commented out, and if restored, should include proper ownership checks.</p>

</blockquote>

<hr />

<h2 id="-optional-blacklist-commented-out">🧪 Optional: Blacklist (Commented Out)</h2>

<p>The contract <strong>includes stubs</strong> for blacklist features, but they are commented out:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; (define-public (add-to-blacklist user))
;; (define-public (remove-from-blacklist user))

</code></pre></div></div>

<p>If implemented, you can restrict transfers or minting to <strong>exclude blacklisted users</strong> — useful for sanctions compliance.</p>

<hr />

<h2 id="-summary">🧠 Summary</h2>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Included</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>SIP-010 Compliant</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>Minting &amp; Burning</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>KYC System (IPFS)</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>Whitelist Only Access</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>Ownership Control</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>Event Logging</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>Blacklist Support</td>
      <td>🟡 (commented)</td>
    </tr>
    <tr>
      <td>Token URI</td>
      <td>🚫 (returns <code class="language-plaintext highlighter-rouge">none</code>)</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Basanta Subedi</name></author><category term="Blog" /><category term="Post Formats" /><category term="readability" /><category term="standard" /><summary type="html"><![CDATA[;; title: token ;; version:2 ;; summary:This is the fungible token for the code4stx project ;; traits (impl-trait 'ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard.sip-010-trait) ;; Token definitions (define-fungible-token BScoin u1000000000000) ;; constants (define-constant err-not-token-owner (err u101)) (define-constant err-insufficient-amount (err u102)) (define-constant err-owner-only (err u103)) (define-constant err-not-whitelisted (err u104)) (define-constant err-kyc-not-found (err u105)) (define-constant err-kyc-already-approved (err u106)) (define-constant err-kyc-not-pending (err u107)) ;; data variables (define-data-var contract-owner principal tx-sender) ;; Maps (define-map whitelist principal bool) (define-map kyc-submissions principal {ipfs-hash: (string-utf8 100), status: (string-utf8 20)}) ;; Read only functions (define-read-only (get-name) (ok "BScoin")) (define-read-only (get-symbol) (ok "BSC")) (define-read-only (get-decimals) (ok u6)) (define-read-only (get-balance (who principal)) (ok (ft-get-balance BScoin who))) (define-read-only (get-token-uri) (ok none)) (define-read-only (get-total-supply) (ok (ft-get-supply BScoin))) (define-read-only (get-contract-owner) (ok (var-get contract-owner))) (define-read-only (get-kyc-status (user principal)) (ok (map-get? kyc-submissions user))) ;; Public functions ;; Function to transfer the token (define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34)))) (begin (asserts! (is-eq tx-sender sender) err-not-token-owner) (asserts! (default-to false (map-get? whitelist sender)) err-not-whitelisted) (asserts! (default-to false (map-get? whitelist recipient)) err-not-whitelisted) (asserts! (&gt; amount u0) err-insufficient-amount) (try! (ft-transfer? BScoin amount sender recipient)) (print { event-type: "Transfer", amount: amount, sender: sender, recipient: recipient }) (match memo to-print (print to-print) 0x) (ok true) ) ) ;; Function to mint the tokens (only by owner) (define-public (mint (amount uint) (recipient principal)) (begin (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only) (asserts! (default-to false (map-get? whitelist recipient)) err-not-whitelisted) (asserts! (&gt; amount u0) err-insufficient-amount) (try! (ft-mint? BScoin amount recipient)) (print { event-type: "Mint", amount: amount, recipient: recipient}) (ok true) ) ) ;; Function to burn the tokens (define-public (burn (amount uint)) (begin (asserts! (&gt; amount u0) err-insufficient-amount) (try! (ft-burn? BScoin amount tx-sender)) (print { event-type: "burn", amount: amount, recipient: tx-sender}) (ok true) ) ) ;; Transfer ownership (define-public (transfer-ownership (new-owner principal)) (begin (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only) (var-set contract-owner new-owner) (ok true) ) ) ;; Function to submit KYC documents (CID from IPFS) (define-public (submit-kyc (ipfs-hash (string-utf8 100))) (begin (let ((empty-kyc (tuple (ipfs-hash u"") (status u"none")))) (asserts! (is-eq (get status (default-to empty-kyc (map-get? kyc-submissions tx-sender))) u"none") err-kyc-already-approved)) (map-set kyc-submissions tx-sender (tuple (ipfs-hash ipfs-hash) (status u"pending"))) (print (tuple (event-type u"KYCSubmitted") (user tx-sender) (ipfs-hash ipfs-hash))) (ok true) ) ) ;; Function to approve KYC (admin only) (define-public (approve-kyc (user principal)) (begin (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only) (let ((kyc-data (unwrap! (map-get? kyc-submissions user) err-kyc-not-found))) (asserts! (is-eq (get status kyc-data) u"pending") err-kyc-not-pending) (map-set kyc-submissions user (tuple (ipfs-hash (get ipfs-hash kyc-data)) (status u"approved"))) (map-set whitelist user true) (print {event-type: "KYCApproved", user: user, by: tx-sender}) (ok true) ) ) ) ;; Function to reject KYC (admin only) (define-public (reject-kyc (user principal) (reason (string-utf8 1000))) (begin (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only) (let ((kyc-data (unwrap! (map-get? kyc-submissions user) err-kyc-not-found))) (asserts! (is-eq (get status kyc-data) u"pending") err-kyc-not-pending) (map-set kyc-submissions user (tuple (ipfs-hash (get ipfs-hash kyc-data)) (status u"rejected"))) (print {event-type: "KYCRejected", user: user, by: tx-sender, reason: reason}) (ok true) ) ) ) ;; Function to remove from whitelist (admin only) (define-public (remove-from-whitelist (user principal)) (begin (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only) (map-delete whitelist user) (ok true) ) ) ;; ;; Function to add to whitelist (admin only) ;; (define-public (add-to-whitelist (user principal)) ;; (begin ;; (asserts! (is-eq tx-sender (var-get admin)) err-not-admin) ;; (map-set whitelist user true) ;; (ok true) ;; ) ;; ) ;; ;; Function to add in the blacklist by the owner ;; (define-public (add-to-blacklist (user principal)) ;; (begin ;; (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only) ;; (map-set blacklist user true) ;; (ok true) ;; ) ;; ) ;; ;; Function to remove from blacklist by owner ;; (define-public (remove-from-blacklist (user principal)) ;; (begin ;; (asserts! (is-eq tx-sender (var-get contract-owner)) err-owner-only) ;; (map-delete blacklist user) ;; (ok true) ;; ) ;; ) This contract defines a SIP-010 compatible token (fungible token standard on Stacks), with the added layers of: ✅ Whitelisting (only approved users can interact) 🔒 KYC Submissions via IPFS hashes 👑 Owner-controlled minting, burning, and access 📦 Total supply: 1 trillion tokens (initial) 📄 SIP-010 Trait Implementation (impl-trait '...sip-010-trait) This tells Clarity the contract follows the official fungible token standard (SIP-010), ensuring compatibility with wallets, DeFi protocols, and explorers. 🔐 Token Definition (define-fungible-token BScoin u1000000000000) Defines a new token named BScoin with a total supply of 1,000,000,000,000 units (1 trillion). 🧱 Constants &amp; Data 🔢 Constants (define-constant err-not-token-owner (err u101)) These are custom error codes for better debugging and consistent responses. Errors include: Error Code Meaning u101 Sender is not the token owner u102 Transfer amount is invalid (≤ 0) u103 Only contract owner can perform u104 User is not whitelisted u105 KYC not submitted u106 KYC already submitted u107 KYC not in pending state 🔧 Variables &amp; Maps (define-data-var contract-owner principal tx-sender) (define-map whitelist principal bool) (define-map kyc-submissions principal { ipfs-hash: (string-utf8 100), status: (string-utf8 20) }) contract-owner: Controls privileged actions like minting, approving KYC, etc. whitelist: Tracks who can transfer or receive tokens. kyc-submissions: Stores IPFS hash + status (none, pending, approved, rejected). 📖 Read-Only Functions These functions return data without modifying state: Function Description get-name Returns token name BScoin get-symbol Returns symbol BSC get-decimals Returns 6 decimals get-balance Returns token balance of a user get-total-supply Total token supply get-contract-owner Owner address get-token-uri Token URI (set to none) get-kyc-status Returns a user’s KYC submission &amp; status 🚀 Core Public Functions 1. 🔄 transfer (define-public (transfer amount sender recipient memo)) Checks sender = tx-sender Both sender and recipient must be whitelisted Transfers token using ft-transfer? Optionally logs a memo (e.g., transaction ID, reason) 2. 🧑‍🏭 mint (define-public (mint amount recipient)) Only the contract owner can mint Recipient must be whitelisted Mints tokens to recipient using ft-mint? 3. 🔥 burn (define-public (burn amount)) Any user can burn their own tokens Calls ft-burn? 4. 🔁 transfer-ownership (define-public (transfer-ownership new-owner)) Only current owner can transfer control Updates contract-owner 📝 KYC System 1. 📤 submit-kyc (define-public (submit-kyc ipfs-hash)) User submits KYC documents by providing an IPFS CID Must not have already submitted Stores data as (ipfs-hash, "pending") 2. ✅ approve-kyc (define-public (approve-kyc user)) Only contract owner can call Approves a KYC request (status = approved) Also adds user to whitelist 3. ❌ reject-kyc (define-public (reject-kyc user reason)) Sets KYC status to "rejected" Logs the rejection reason ✅ Whitelist Management Add / Remove (define-public (remove-from-whitelist user)) Admin-only removal from whitelist 🔒 The add-to-whitelist function is commented out, and if restored, should include proper ownership checks. 🧪 Optional: Blacklist (Commented Out) The contract includes stubs for blacklist features, but they are commented out: ;; (define-public (add-to-blacklist user)) ;; (define-public (remove-from-blacklist user)) If implemented, you can restrict transfers or minting to exclude blacklisted users — useful for sanctions compliance. 🧠 Summary Feature Included SIP-010 Compliant ✅ Minting &amp; Burning ✅ KYC System (IPFS) ✅ Whitelist Only Access ✅ Ownership Control ✅ Event Logging ✅ Blacklist Support 🟡 (commented) Token URI 🚫 (returns none)]]></summary></entry></feed>