diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md new file mode 100644 index 00000000..8c13ff4f --- /dev/null +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -0,0 +1,430 @@ +# Preamble + +SIP Number: 013 + +Title: Standard Trait Definition for Semi-Fungible Tokens + +Author: Marvin Janssen + +Consideration: Technical + +Type: Standard + +Status: Ratified + +Created: 12 September 2021 + +License: CC0-1.0 + +Sign-off: Jude Nelson , Brice Dobry + +Layer: Traits + +Discussions-To: https://github.com/stacksgov/sips + +# Abstract + +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. + +# License and Copyright + +This SIP is made available under the terms of the Creative Commons CC0 1.0 +Universal license, available at +https://creativecommons.org/publicdomain/zero/1.0/. This SIP's copyright is held +by the Stacks Open Internet Foundation. + +# 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: + +```JSON +{ + "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: + +```clarity +(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. + +```clarity +(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: + +1. The amount of semi-fungible tokens being transferred. +2. 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. + +```clarity +(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 + ;; + ;; + (try! (tag-nft-token-id {token-id: token-id, owner: sender})) + (try! (tag-nft-token-id {token-id: token-id, owner: recipient})) + ;; + (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](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. + +```clarity +(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](https://eips.ethereum.org/EIPS/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. + +# Backwards Compatibility + +Not applicable. + +# Trait deployments + +## Mainnet + +- Token trait: [SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-semi-fungible-token-trait](https://explorer.stacks.co/txid/0x7e9d8bac5157ab0366089d00a40a2a83926314ab08807ab3efa87ebc96d9e20a?chain=mainnet) +- Send-many trait: [SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-transfer-many-trait](https://explorer.stacks.co/txid/0x88457278a61b7e59c8a19704932eebb7b46817e0bbd3235436a1d72c956db19c?chain=mainnet) + +## Testnet + +- Token trait: [STDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTJTTH7YB.sip013-semi-fungible-token-trait](https://explorer.stacks.co/txid/0x37e846cce0d31f34be06d969efbb6ff413308066eefffa0bf1a8669bd4be0a05?chain=testnet) +- Send-many trait: [STDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTJTTH7YB.sip013-transfer-many-trait](https://explorer.stacks.co/txid/0x81ec048b187137ade2fb9519375d22ec96c271d114e79c2d44018434e9009911?chain=testnet) + +# 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 Implementations + +- https://github.com/MarvinJanssen/stx-semi-fungible-token