-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SIP-013 Semi-Fungible Token standard #42
Conversation
Adding for visibility: @LNow suggested shorter keys for the
Re:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it.
|
||
| Event name | Tuple structure | Description | | ||
|----------------------|-------------------------------------------------------------------------------------------------|--------------------------------------| | ||
| `sft_transfer` | `{type: "sft_transfer", token-id: uint, amount: uint, sender: principal, recipient: principal}` | Emitted when tokens are transferred. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be nice if event types can be recognized easily from the hex representation (e.g. in a data base). It allows to filter by event type. As the keys of a tuple are ordered in alphabetic order it could make sense to use a
as the type key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about EVENT
or just E
? As long as there are no other keys written using capitals it should appear as first one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since one of the selling points Clarity is legibility, I would vote EVENT
over just E
. Then again, I am a stickler for code style and I do not use capitals anywhere. 😁 Event type recognition on a lower level would be pretty nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe action
|
||
### 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))` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why 200?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The send-many tool and some other bulk transfer functions have a 200 limit so I naturally adopted it. Do you have an alternative number in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but let's add the tx costs for a basic implementation (with Stacks 2.05+ cost functions)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a "worst case" scenario with a list of 200 items, all with a 34 byte buffer and calculated the costs with clarinet 0.20.0
. It looks like we exhaust read_count
:
+----------------------+-----------+------------+
| | Consumed | Limit |
+----------------------+-----------+------------+
| Runtime | 117399567 | 5000000000 |
+----------------------+-----------+------------+
| Read count | 12003 | 7750 |
+----------------------+-----------+------------+
| Read length (bytes) | 873618 | 100000000 |
+----------------------+-----------+------------+
| Write count | 8000 | 7750 |
+----------------------+-----------+------------+
| Write length (bytes) | 870000 | 15000000 |
+----------------------+-----------+------------+
So (7750-3)/60
puts us at a maximum list length of 129.1166666667
, let us say 128
to be safe.
I'll do the same for the send-many
function without memo and see how far I get.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A transaction should not take more than 80% of the block limit, otherwise it will be rejected, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source on this? If so, then ((7750*0.8)-3)/60 = 103.2833333333
or simply 100
to be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we could stick with a larger number to future-proof the SIP. I imagine costs will be lowered again at some point in the future. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at line 88 get-token-uri. as opposed to to string-ascii in 009? - seems a shame to diverge unless there a good reason? Urls dont support non ascii characters so specifying ascii-string is unambiguous as to whether you need to escape non ascii characters in the url.
That's right. Perhaps using UTF8 could save some space in comparison to punycode. What do you think? Here's SIP009: (get-token-uri (uint) (response (optional (string-ascii 256)) uint)) And here's SIP010: (get-token-uri () (response (optional (string-utf8 256)) uint)) |
I see and yes utf8 is neater than using punycode but would there need to be utf8 characters in the url or is this for future proofing? |
|
||
### 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))` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but let's add the tx costs for a basic implementation (with Stacks 2.05+ cost functions)
|
||
| Event name | Tuple structure | Description | | ||
|----------------------|-------------------------------------------------------------------------------------------------|--------------------------------------| | ||
| `sft_transfer` | `{type: "sft_transfer", token-id: uint, amount: uint, sender: principal, recipient: principal}` | Emitted when tokens are transferred. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe action
Here I created a game contract as a reference implementation, loosely based on Clash of Clans using this standard. I really like the idea of SFTs and the ease this provides in managing multi token apps |
Updated the SIP013 document based on feedback. @fiftyeightandeight would appreciate your feedback since your project is heavily leveraging SIP013. Did you run into any issues or limitations? Also note that the trait has changed a little; namely, |
We have a number of contracts implementing SIP013 (an example of which is here). |
Thanks for the quick response @fiftyeightandeight, immensely insightful. Would you mind giving a high level explanation of how ALEX utilises this SIP and what the upsides are in a few sentences? I think it is a nice implementation that warrants some background for other curious developers. |
Hi @MarvinJanssen , we at ALEX offer fixed-rate / fixed-term lending/borrowing. The rate of such lending/borrowing is determined collectively and dynamically by the ALEX community (by buying/selling the underlying contract). From the Clarity contract perspective, this means you have a number of substantially similar contracts (e.g. lending contract) with slightly different configuration (i.e. expiry). Before we adopted this SIP, we represented each of these contracts as a standalone contract, essentially copying and pasting an older contract, changing its name and updating it with a new expiry. You can see here what we had before we adopted this SIP. It is, put it mildly, not great. After we adopted this SIP, we can aggregate all similar contracts (for example, |
@fiftyeightandeight thanks for the explanation. Once (if) this SIP settles, I will update the reference repository with a list of interesting implementations like yours. |
This is super cool. I'm working on Fractal - an NFT fractionalisation protocol; blog here, testnet v1.5 here. Could we make some changes to this standard around memos? I don't think we need two functions for with/without memo. Wasn't aware of this proposal before working on this, so v2.0 of Fractal would support this standard. |
Just like you can transfer STX without providing memo, you should be able to do so with both FT and NFT, therefore I think separate functions should stay. On a side note - @0xAsteria you have a huge security hole in your contract (unguarded trait + as-contract). |
haha funny you pointed this out, I noticed it this morning when reworking a few things for 2.0. haven't worked out a fix though. any ideas there? |
As @LNow said (also, welcome back!), I also think it should stay as separate functions. Effectively, most token transfers do not include a memo and the naming also mirrors the upcoming Stacks 2.1 Fractional NFTs are basically a type of SFT. I created two (untested) example contracts on how it can be done with SIP013:
Was also going to point out the unguarded trait reference but @LNow beat me to it 😁. |
Whitelisting or sandboxing. |
I like this idea of Jude and Mike suggestion for N, minimum of 3. Little history of SIP010 and SIP009 Allowing 2-3 months for revisions, is that reasonable? In my view SIP010 (and SIP009) were special because they were made in a very hectic time when Stacks 2.0 was just launched and a lot was happening, this also lead to some senior developers noticing inconsistencies later in the process. Perhaps that is less likely to happen as we improve the SIP process. Block B, (six months) 25920 BTC blocks after activation, or should this be closer to C? generally before or after C? |
How are you all feeling about this SIP? Is it ready for the technical CAB to give it a formal review? This would mean that after sign-off, no substantial changes would be made without a withdraw/resubmit. |
I'm happy to move this forward. I think we can keep the post condition stuff as-is. However, we need to add activation criteria before we put it up for review. I'll sleep on it and submit the update tomorrow. |
@MarvinJanssen adding activation criteria is still outstanding. |
@radicleart updated Bitcoin block heights for activation and submitted. |
@MarvinJanssen is there a way for a caller to get the last token ID similar to SIP-009's This would be very useful for token metadata indexing services so they could iterate through all the available token classes for a SIP contract |
Thanks for adding the |
@rafaelcr having last token count in SIP 13 standard doesn't make sense in the way it did for the SIP 009 standard. Individual SIP 13s represent a mapping from {token-id, owner} --> amount as opposed to the SIP 009 {token-id} --> owner mapping. Instead of using the last token count concept you can try reading from the contracts mint events which are available via the stacks api |
Yes it is ready for review @jcnelson. Missed this one. |
@kantai Can you take a look at this SIP when you get a free moment? Thanks! |
Pinging @kantai, is it good to move forward? |
Hi @MarvinJanssen on the weekly SIP call #17 was said that Aaron is out of action until January afaiu. |
Looks good to me. It seems Aaron's concerns were addressed. |
Let's move to ratified! |
Yup! |
Please switch the status to |
@jcnelson done! |
This SIP proposes a standard for semi-fungible tokens. 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.
The standard is still in draft stage but a PR is the best way to request comments and feedback. Copying some text from my reference repo for brevity.
Uses
Semi-fungible tokens can be very useful in many different settings. Here are some examples:
Art
Art initiatives can use them to group an entire project into a single contract and mint multiple series or collections in a single go. A single artwork can have multiple editions that can all be expressed by the same identifier. Artists can also use them to easily create a track-record of their work over time. Curation requires tracking a single contract instead of a new one per project.
Games
Games that have on-chain economies can leverage their flexibility to express their full in-game inventory in a single contract. For example, they may express their in-game currency with one token ID and a commodity with another. In-game item supplies can be managed in a more straightforward way and the game developers can introduce new item classes in a transparent manner.
SFTs and post conditions
Post conditions are tricky because it is impossible to make assertions based on custom
print
events as of Stacks 2.0. Still, native events can be utilised to safeguard SFT actions in different ways. The reference SFT implementation defines a fungible token usingdefine-fungible-token
to allow for post conditions asserting the amount of tokens transferred. It enables the user to state "I will transfer exactly 50 semi-fungible tokens of contract A". I am still exploring options in which the user can also assert the type of token. There are definitely ways in which an NFT defined withdefine-non-fungible-token
can be used.Options I am considering:
The second option makes it so we can also possibly do away with custom
print
events. A downside is that it creates an ever-growing NFT collection on the contract itself.