Fungible Tokens

A guide to help you create your own fungible tokens

source: Hiro blog

Creating a fungible token on Stacks can happen a few different ways — using no-code launchpads or writing your own Clarity smart contract. This guide helps you pick the best path for your goals and gives you the implementation details to ship confidently, whether you’re deploying with clicks or code.

Custom Development

For developers who want full control over their token implementation, here’s how to create a custom SIP-010 token on Stacks using Clarity. But before you deploy the token contract, you must have your token contract conform to the SIP-010 trait standard.

1

Define SIP-010 fungible token trait

What is SIP-010?

SIP-010 is the standard for defining fungible tokens on Stacks. Defining a common interface (known in Clarity as a "trait") allows different smart contracts, apps, and wallets to interoperate with fungible token contracts in a reusable way.

Below is an implementation of the SIP-010 trait standard for fungible tokens. You can use the existing minimal standard SIP-010 trait or extend it by adding in your own custom traits. But the requirements of the SIP-010 traits are necessary to have at the minimum.

SIP-010 trait implementation
(define-trait sip-010-trait
  (
    ;; Transfer from the caller to a new principal
    (transfer (uint principal principal (optional (buff 34))) (response bool uint))

    ;; the human readable name of the token
    (get-name () (response (string-ascii 32) uint))

    ;; the ticker symbol, or empty if none
    (get-symbol () (response (string-ascii 32) uint))

    ;; the number of decimals used, e.g. 6 would mean 1_000_000 represents 1 token
    (get-decimals () (response uint uint))

    ;; the balance of the passed principal
    (get-balance (principal) (response uint uint))

    ;; the current total supply (which does not need to be a constant)
    (get-total-supply () (response uint uint))

    ;; an optional URI that represents metadata of this token
    (get-token-uri () (response (optional (string-utf8 256)) uint))
  )
)

All we are doing here is defining the function signatures for functions we'll need to implement in our token contract, which we can see a simple version of below.

2

Implement SIP-010 trait in token contract

Any token contract that wants to conform to the SIP-010 fungible token standard for Stacks needs to have this trait "implemented" in their token contract. See the below minimal token contract example of how this is done.

token-contract-clar
;; This contract implements the SIP-010 community-standard Fungible Token trait.
(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

;; Define the FT, with no maximum supply
(define-fungible-token clarity-coin)

;; Define errors
(define-constant ERR_OWNER_ONLY (err u100))
(define-constant ERR_NOT_TOKEN_OWNER (err u101))

;; Define constants for contract
(define-constant CONTRACT_OWNER tx-sender)
(define-constant TOKEN_NAME "Clarity Coin")
(define-constant TOKEN_SYMBOL "CC")
(define-constant TOKEN_DECIMALS u6) ;; 6 units displayed past decimal, e.g. 1.000_000 = 1 token

(define-data-var token_uri (string-utf8 256) u"https://hiro.so") ;; utf-8 string with token metadata host

;; SIP-010 function: Get the token balance of a specified principal
(define-read-only (get-balance (who principal))
  (ok (ft-get-balance clarity-coin who))
)

;; SIP-010 function: Returns the total supply of fungible token
(define-read-only (get-total-supply)
  (ok (ft-get-supply clarity-coin))
)

;; SIP-010 function: Returns the human-readable token name
(define-read-only (get-name)
  (ok TOKEN_NAME)
)

;; SIP-010 function: Returns the symbol or "ticker" for this token
(define-read-only (get-symbol)
  (ok TOKEN_SYMBOL)
)

;; SIP-010 function: Returns number of decimals to display
(define-read-only (get-decimals)
  (ok TOKEN_DECIMALS)
)

;; SIP-010 function: Returns the URI containing token metadata
(define-read-only (get-token-uri)
  (ok (some (var-get token_uri)))
)

;; Properly updates token URI by emitting a SIP-019 token metadata update notification
(define-public (set-token-uri (value (string-utf8 256)))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_OWNER_ONLY)
        (var-set token-uri value)
        (ok (print {
              notification: "token-metadata-update",
              payload: {
                contract-id: current-contract,
                token-class: "ft"
              }
            })
        )
    )
)

;; Mint new tokens and send them to a recipient.
;; Only the contract deployer can perform this operation.
(define-public (mint (amount uint) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_OWNER_ONLY)
    (ft-mint? clarity-coin amount recipient)
  )
)

;; SIP-010 function: Transfers tokens to a recipient
;; Sender must be the same as the caller to prevent principals from transferring tokens they do not own.
(define-public (transfer
  (amount uint)
  (sender principal)
  (recipient principal)
  (memo (optional (buff 34)))
)
  (begin
    ;; #[filter(amount, recipient)]
    (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) ERR_NOT_TOKEN_OWNER)
    (try! (ft-transfer? clarity-coin amount sender recipient))
    (match memo to-print (print to-print) 0x)
    (ok true)
  )
)

This is the Clarity code we need in order to create an fungible token, with one additional function, mint that allows us to actually create a new fungible tokens. This mint function is not needed to adhere to the trait.

The token contract example above is passing in an already deployed trait on mainnet into the impl-trait function. You can use this same deployed trait for your own token contract as well.

No-code Platforms

Use community built no-code platforms that can quickly help you deploy tokens.

STX.City

STX.CITY is a one-click platform for launching tokens on Stacks. The platform is a comprehensive toolkit for memecoin creators, enabling them to grow their communities through features like AMM listing support (such as on Alex, Velar, and Stackswap), airdrops, token donations, and burn mechanisms.

Check out this blog post by the founder of STX.City for more information.

Best Practices

Here are some things to consider when creating your token and after your token is launched.

How to format the token metadata?

Example token metadata taken from the sBTC token:

// https://ipfs.io/ipfs/bafkreibqnozdui4ntgoh3oo437lvhg7qrsccmbzhgumwwjf2smb3eegyqu

{
  "sip": 16,
  "name": "sBTC",
  "description": "BTC is a 1:1 Bitcoin-backed asset on the Stacks Bitcoin L2 that will allow developers to leverage the security, network effects, and .5T in latent capital of the Bitcoin network.",
  "image": "https://ipfs.io/ipfs/bafkreiffe46h5voimvulxm2s4ddszdm4uli4rwcvx34cgzz3xkfcc2hiwi",
  "properties": {
    "decimals": 8,
    "external_url": "https://sbtc.tech"
  }
}

Check out the SIP-016 standard for how you should define the schema of your metadata.

How would I properly update my token metadata?

If you plan on updating your token's metadata in the future, you should definitely implement a SIP-019 compliant token metadata update notification. Take a look at the example token contract above and you'll notice the set-token-uri function emits a SIP-019 compliant print event.

;; ...
(define-public (set-token-uri (value (string-utf8 256)))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_OWNER_ONLY)
        (var-set token-uri value)
        (ok (print {
              notification: "token-metadata-update",
              payload: {
                contract-id: current-contract,
                token-class: "ft"
              }
            })
        )
    )
)
;; ...

Hiro’s Token Metadata API watches for that specific print event (specifically the notification of "token-metadata-update") on the network and auto-updates the API’s database to reflect a change in the existing token’s metadata.

If your token contract did not implement this print event, you could use the helper contract below to invoke a function that'll emit the same print event notification. Just invoke the ft-metadata-update-notify function of this contract below:

SP1H6HY2ZPSFPZF6HBNADAYKQ2FJN75GHVV95YZQ.token-metadata-update-notify

Additional Resources

  • [dev.to] A Deep Dive into Token Standards: ERC-20 vs. SIP-10 vs. BRC20 vs. STX20

  • [StacksGov] SIP-010 Standard Trait Definition for Fungible Tokens

  • [StacksGov] SIP-016 Schema Definition for Metadata for Digital Assets

  • [StacksGov] SIP-019 Notifications for Token Metadata Updates

  • [contract] SP1H6HY2ZPSFPZF6HBNADAYKQ2FJN75GHVV95YZQ.token-metadata-update-notify

  • [StacksDevs YT] Fungible Token Standard (SIP-10) Tutorial For Bitcoin L2 Stacks

  • [LearnWeb3] SIP-010 Fungible Tokens & Traits

  • [Medium @n.campos.rojas] Learn how to create fungible tokens on Stacks (versus on Ethereum)

Last updated

Was this helpful?