From 25077913e0cee9cd8510c555b9a2b9055d24e450 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Tue, 19 Oct 2021 14:37:37 -0700 Subject: [PATCH 01/10] feat: SIP-013 Semi-Fungible token standard --- .../sip-013-semi-fungible-token-standard.md | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 sips/sip-013/sip-013-semi-fungible-token-standard.md 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..1d175010 --- /dev/null +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -0,0 +1,232 @@ +# Preamble + +SIP Number: 013 + +Title: Standard Trait Definition for Semi-Fungible Tokens + +Author: Marvin Janssen + +Consideration: Technical + +Type: Standard + +Status: Draft + +Created: 12 September 2021 + +License: CC0-1.0 + +Sign-off: Jude Nelson , Technical Steering Committee Chair + +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 10 tokens of ID 1 and 20 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 10 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. 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-utf8 256)) uint))` + +Returns an optional UTF8 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 and may be defined in a future SIP. However, the metadata file should be in JSON format and should include a `version` property containing a string: + +```JSON +{ + "version: "v1" + // ... any other properties +} +``` + +Applications consuming these metadata files can base display capabilities on the version string. + +### 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 the `tx-sender`. | + +This 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 #vents section of this document below.) + +### 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 ((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. + +## Trait implementation + +An implementation of the proposed 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-utf8 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)) + + ;; 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)) + ) +) +``` +## 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_event` | `{type: "sft_transfer_event", token-id: uint, amount: uint, sender: principal, recipient: principal}` | Emitted when tokens are transferred. | +| `sft_mint_event` | `{type: "sft_mint_event", token-id: uint, amount: uint, recipient: principal}` | Emitted when new tokens are minted. | +| `sft_burn_event` | `{type: "sft_burn_event", token-id: uint, amount: uint, sender: principal, recipient: 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?` + +## 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. 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 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. + +However, since there are no Clarity primitive counterparts for semi-fungible tokens, post conditions can only safeguard users on a basic level. Semi-fungible token contracts that are implemented using the Clarity primitive `define-fungible-token` give users the ability to make assertions against the total number of tokens transferred in any single call. It does not, however, provide any securities as to the type of token transferred. + +Future research may be conducted to see if a custom non-fungible definition using `define-non-fungible-token` can help on this front, perhaps by minting and burning in one go, or by defining a more complex internal token ID. Another solution may be if a future Stacks version allows users to define post conditions for custom `print` events. + +# Related work + +## Ethereum ERC1155 + +- [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) + +# Backwards Compatibility + +Not applicable + +# Activation + +Trait deployments: + +- mainnet: [TODO](#TODO) +- Testnet: [TODO](#TODO) + +This trait will be considered activated when this trait is deployed to mainnet, and 3 different implementations of the trait have been deployed to mainnet, no later than Bitcoin block TODO. + +# Reference Implementations + +https://github.com/MarvinJanssen/stx-semi-fungible-token From 75c84900ae37a39e2d59486de77af461e45daea1 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Thu, 21 Oct 2021 11:00:59 -0700 Subject: [PATCH 02/10] fix: typos, naming --- .../sip-013-semi-fungible-token-standard.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index 1d175010..1590b8e4 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -127,13 +127,13 @@ Transfer many tokens in one contract call. Each transfer should follow the exact ### Bulk transfers with memos -`(transfer-many ((transfers (list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)}))) (response bool uint))` +`(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. -## Trait implementation +## Trait definition -An implementation of the proposed trait is provided below. +A definition of the proposed trait is provided below. ```clarity (define-trait sip013-semi-fungible-token-trait @@ -174,11 +174,11 @@ An implementation of the proposed trait is provided below. 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_event` | `{type: "sft_transfer_event", token-id: uint, amount: uint, sender: principal, recipient: principal}` | Emitted when tokens are transferred. | -| `sft_mint_event` | `{type: "sft_mint_event", token-id: uint, amount: uint, recipient: principal}` | Emitted when new tokens are minted. | -| `sft_burn_event` | `{type: "sft_burn_event", token-id: uint, amount: uint, sender: principal, recipient: principal}` | Emitted when tokens are burned. | +| 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 From e87a7e9e16eefaef9499b56e54fd18b450c00766 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Thu, 23 Dec 2021 13:19:26 +0100 Subject: [PATCH 03/10] fix: update SIP013 document based on feedback, reduce send-many max list size --- .../sip-013-semi-fungible-token-standard.md | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index 1590b8e4..478b5351 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -10,7 +10,7 @@ Consideration: Technical Type: Standard -Status: Draft +Status: Accepted Created: 12 September 2021 @@ -85,13 +85,13 @@ Returns the decimal places of a token type. This is purely for display reasons, ### Token URI -`(get-token-uri ((token-id uint)) (response (optional (string-utf8 256)) uint))` +`(get-token-uri ((token-id uint)) (response (optional (string-ascii 256)) uint))` -Returns an optional UTF8 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 and may be defined in a future SIP. However, the metadata file should be in JSON format and should include a `version` property containing a string: +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 `version` property containing a string: ```JSON { - "version: "v1" + "version": "1" // ... any other properties } ``` @@ -109,7 +109,9 @@ Transfer a token from the sender to the recipient. It is recommended to leverage | `u1` | The sender has insufficient balance. | | `u2` | The sender and recipient are the same principal. | | `u3` | Amount is `u0`. | -| `u4` | The sender is not the `tx-sender`. | +| `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`. A token contract without an allowance mechanism can return `(err u4)` simply when the `sender` is not equal to the `tx-sender`. This function should emit a special transfer event, as detailed in the Events section of this document. @@ -121,13 +123,13 @@ Transfer a token from the sender to the recipient and emit a memo. This function ### Bulk transfers -`(transfer-many ((transfers (list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal}))) (response bool uint))` +`(transfer-many ((transfers (list 100 {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-memo ((transfers (list 100 {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. @@ -154,7 +156,7 @@ A definition of the proposed trait is provided below. (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-utf8 256)) uint)) + (get-token-uri (uint) (response (optional (string-ascii 256)) uint)) ;; Transfer from one principal to another. (transfer (uint uint principal principal) (response bool uint)) @@ -163,10 +165,10 @@ A definition of the proposed trait is provided below. (transfer-memo (uint uint principal principal (buff 34)) (response bool uint)) ;; Transfer many tokens at once. - (transfer-many ((list 200 {token-id: uint, amount: uint, sender: principal, recipient: principal})) (response bool uint)) + (transfer-many ((list 100 {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)) + (transfer-many-memo ((list 100 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (buff 34)})) (response bool uint)) ) ) ``` @@ -206,7 +208,7 @@ The Stacks blockchain includes a feature known as Post Conditions. By defining p However, since there are no Clarity primitive counterparts for semi-fungible tokens, post conditions can only safeguard users on a basic level. Semi-fungible token contracts that are implemented using the Clarity primitive `define-fungible-token` give users the ability to make assertions against the total number of tokens transferred in any single call. It does not, however, provide any securities as to the type of token transferred. -Future research may be conducted to see if a custom non-fungible definition using `define-non-fungible-token` can help on this front, perhaps by minting and burning in one go, or by defining a more complex internal token ID. Another solution may be if a future Stacks version allows users to define post conditions for custom `print` events. +For strategies on how to best guard a semi-fungible token contract with post conditions, see the reference implementation at the end of this document. # Related work From 0662799cfb1d079217a5f702822091f617d74a8f Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Mon, 3 Jan 2022 17:46:07 +0100 Subject: [PATCH 04/10] fix: move to draft to actually follow protocol --- sips/sip-013/sip-013-semi-fungible-token-standard.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index 478b5351..83b3cd95 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -10,13 +10,13 @@ Consideration: Technical Type: Standard -Status: Accepted +Status: Draft Created: 12 September 2021 License: CC0-1.0 -Sign-off: Jude Nelson , Technical Steering Committee Chair +Sign-off: Layer: Traits From 9854c7c4e289ae661176ff79f175c057b3cd882d Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Fri, 4 Feb 2022 00:49:55 +0800 Subject: [PATCH 05/10] fix: update metadata, Clarity error codes, transfer function, post conditions --- .../sip-013-semi-fungible-token-standard.md | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index 83b3cd95..f63a3dbc 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -35,7 +35,7 @@ This SIP's copyright is held by the Stacks Open Internet Foundation. 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 10 tokens of ID 1 and 20 tokens of ID 2, for example. It effectively means that one contract can represent any combination of fungible and non-fungible tokens. +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. @@ -57,7 +57,7 @@ The Semi-Fungible Token trait, `sip013-semi-fungible-token-trait`, has 10 functi `(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. The function should never return an `err` response and is recommended to be defined as read-only. +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 @@ -87,16 +87,16 @@ Returns the decimal places of a token type. This is purely for display reasons, `(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 `version` property containing a string: +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 { - "version": "1" + "sip": 16 // ... any other properties } ``` -Applications consuming these metadata files can base display capabilities on the version string. +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 @@ -111,15 +111,21 @@ Transfer a token from the sender to the recipient. It is recommended to leverage | `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`. A token contract without an allowance mechanism can return `(err u4)` simply when the `sender` is not equal to the `tx-sender`. +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. -This function should emit a special transfer event, as detailed in the Events section of this document. +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 #vents section of this document below.) +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.) ### Bulk transfers @@ -195,18 +201,26 @@ The recommended native asset primitives to use: - `ft-get-supply` - `ft-mint?` - `ft-transfer?` +- And their NFT equivalents. ## 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. 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 faulty contract, and consumers of those contracts should proceed with caution. +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. -However, since there are no Clarity primitive counterparts for semi-fungible tokens, post conditions can only safeguard users on a basic level. Semi-fungible token contracts that are implemented using the Clarity primitive `define-fungible-token` give users the ability to make assertions against the total number of tokens transferred in any single call. It does not, however, provide any securities as to the type of token transferred. +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 factor 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`. For strategies on how to best guard a semi-fungible token contract with post conditions, see the reference implementation at the end of this document. From 02119bee9a6f7ed9cff1ac050bab3d2b946be43b Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Fri, 29 Apr 2022 12:33:41 +0800 Subject: [PATCH 06/10] feat: two traits to make send-many optional, update formatting --- .../sip-013-semi-fungible-token-standard.md | 250 +++++++++++++----- 1 file changed, 191 insertions(+), 59 deletions(-) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index f63a3dbc..0293adb9 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -24,32 +24,60 @@ 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. +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. +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. +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. +- 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 10 functions: +The Semi-Fungible Token trait, `sip013-semi-fungible-token-trait`, has 8 +functions: ## Trait functions @@ -57,37 +85,58 @@ The Semi-Fungible Token trait, `sip013-semi-fungible-token-trait`, has 10 functi `(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. +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. +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. +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. +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. +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: +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 { @@ -96,13 +145,19 @@ Returns an optional ASCII string that is a valid URI which resolves to this toke } ``` -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. +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?`. +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 | |------------|--------------------------------------------------| @@ -111,37 +166,37 @@ Transfer a token from the sender to the recipient. It is recommended to leverage | `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. +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: +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. +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.) - -### Bulk transfers - -`(transfer-many ((transfers (list 100 {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 100 {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. +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 proposed trait is provided below. +A definition of the trait is provided below. ```clarity (define-trait sip013-semi-fungible-token-trait @@ -169,18 +224,15 @@ A definition of the proposed trait is provided below. ;; Transfer from one principal to another with a memo. (transfer-memo (uint uint principal principal (buff 34)) (response bool uint)) - - ;; Transfer many tokens at once. - (transfer-many ((list 100 {token-id: uint, amount: uint, sender: principal, recipient: principal})) (response bool uint)) - - ;; Transfer many tokens at once with memos. - (transfer-many-memo ((list 100 {token-id: uint, amount: uint, sender: principal, recipient: principal, memo: (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`. +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 | |----------------------|-------------------------------------------------------------------------------------------------|--------------------------------------| @@ -188,10 +240,14 @@ Semi-fungible token contracts should emit custom events in certain situations vi | `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. +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: @@ -201,38 +257,112 @@ The recommended native asset primitives to use: - `ft-get-supply` - `ft-mint?` - `ft-transfer?` -- And their NFT equivalents. +- `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. +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. +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. +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 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 factor that should be checked by post conditions: +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`. +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`. + +## 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. -For strategies on how to best guard a semi-fungible token contract with post conditions, see the reference implementation at the end of this document. +## 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 -- [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) +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 +Not applicable. # Activation @@ -241,8 +371,10 @@ Trait deployments: - mainnet: [TODO](#TODO) - Testnet: [TODO](#TODO) -This trait will be considered activated when this trait is deployed to mainnet, and 3 different implementations of the trait have been deployed to mainnet, no later than Bitcoin block TODO. +This trait will be considered activated when this trait is deployed to mainnet, +and 3 different implementations of the trait have been deployed to mainnet, no +later than Bitcoin block TODO. # Reference Implementations -https://github.com/MarvinJanssen/stx-semi-fungible-token +- https://github.com/MarvinJanssen/stx-semi-fungible-token From 67a2a198f6b6a1184e5141423a41bfc5fd37cbe3 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Mon, 2 May 2022 11:52:46 +0800 Subject: [PATCH 07/10] feat: move to Accepted --- sips/sip-013/sip-013-semi-fungible-token-standard.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index 0293adb9..40c33d83 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -10,7 +10,7 @@ Consideration: Technical Type: Standard -Status: Draft +Status: Accepted Created: 12 September 2021 From 48a23ec42939d9734826629a11182c24356a3d96 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Tue, 5 Jul 2022 11:36:26 +0800 Subject: [PATCH 08/10] chore: update deployments and activation criteria --- .../sip-013-semi-fungible-token-standard.md | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index 40c33d83..ba14fd72 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -364,16 +364,24 @@ Ethereum. Not applicable. -# Activation +# 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) -Trait deployments: +## Testnet -- mainnet: [TODO](#TODO) -- Testnet: [TODO](#TODO) +- 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 -This trait will be considered activated when this trait is deployed to mainnet, -and 3 different implementations of the trait have been deployed to mainnet, no -later than Bitcoin block TODO. +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 From 605f1788388dae405f4c3df06ede11d27d33fee3 Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Wed, 5 Oct 2022 22:19:12 +0800 Subject: [PATCH 09/10] chore: add paragraph and example on burn-and-mint --- .../sip-013-semi-fungible-token-standard.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index ba14fd72..dda308ea 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -298,6 +298,48 @@ 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 From 3f18e17b348941b4f8724d3e0117ed26edc2f47e Mon Sep 17 00:00:00 2001 From: MarvinJanssen Date: Tue, 6 Dec 2022 12:05:20 -0800 Subject: [PATCH 10/10] chore: change to ratified and fill in sign-off section --- sips/sip-013/sip-013-semi-fungible-token-standard.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sips/sip-013/sip-013-semi-fungible-token-standard.md b/sips/sip-013/sip-013-semi-fungible-token-standard.md index dda308ea..8c13ff4f 100644 --- a/sips/sip-013/sip-013-semi-fungible-token-standard.md +++ b/sips/sip-013/sip-013-semi-fungible-token-standard.md @@ -10,13 +10,13 @@ Consideration: Technical Type: Standard -Status: Accepted +Status: Ratified Created: 12 September 2021 License: CC0-1.0 -Sign-off: +Sign-off: Jude Nelson , Brice Dobry Layer: Traits