-
Notifications
You must be signed in to change notification settings - Fork 323
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
CIP-0054? | Cardano Smart NFTs #263
Changes from 7 commits
3d62b5b
1e9f568
580333d
78a95dc
40ec14f
dba888b
61a25f6
bafb040
c6d286a
a12e778
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
--- | ||
CIP: 54 | ||
Title: Cardano Smart NFTs | ||
Authors: Kieran Simkin | ||
Comments-URI: https://forum.cardano.org/t/cip-draft-cardano-smart-nfts/100470 | ||
Status: Draft | ||
Type: Standards | ||
Created: 2022-05-18 | ||
License: CC-BY-4.0 | ||
--- | ||
# Cardano “Smart NFTs” | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please no title in the markdown 🙏, the title already is part of the front-matter data. |
||
|
||
## Abstract | ||
|
||
This CIP specifies a standard for an API which should be provided to Javascript NFTs, it also defines some additions to the 721 metadata standard defined in [CIP-0025](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0025) which allow an NFT to specify it would like to receive certain current information from the blockchain. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anything below this paragraph seems to belong more the "Motivation" than the abstract. |
||
|
||
Currently if an NFT creator wishes to change or otherwise “evolve” their NFT after minting, they must burn the token and re-mint. It would be very nice if the user were able to modify their NFT simply by sending it to themselves with some extra data contained in the new transaction metadata. This would allow implementation of something like a ROM+RAM concept, where you have the original immutable part of the NFT (in Cardano’s case represented by the original 721 key from the mint transaction), and you also have a mutable part – represented by any subsequent transaction metadata. | ||
|
||
It would also be nice to be able to retrieve data that has been previously committed to the blockchain, separately to the NFT which wishes to access it. This would be useful for retrieving oracle data such as current Ada price quotes – as well as for allowing an NFT to import another NFT’s data. | ||
|
||
Further to this - for on-chain programatically generated NFTs, it makes sense to mint the code to render the NFT as one token, and then have the individual NFTs contain only the input variables for that code. This CIP specifies an additional metadata option which specifies that an NFT should be rendered by another token - this will massively reduce code duplication in on-chain NFTs. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. side-note: I'd love to see some example of existing JavaScript NFT in introduction to build a mental picture of those :) |
||
|
||
This combination of functionality enables many exciting new possibilities with on-chain NFTs. | ||
|
||
## Description: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please rename as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i.e. no "colon" ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops, not sure where my mind was. |
||
|
||
Currently the NFT sites which support on-chain Javascript NFTs do so by creating a sandboxed `<iframe>` into which they inject the HTML from the NFT’s metadata. From within this sandbox it is not possible to bring-in arbitrary data from external sources – everything must be contained within the NFT, or explicitly bought into the sandbox via an API. | ||
|
||
This proposal suggests an addition to the 721 metadata key from [CIP-0025](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0025), to enable an NFT to specify that it would like to receive a particular transaction history accessible to it from within the sandbox – thus defining it as a “Smart NFT”. | ||
|
||
In tandem with the additional metadata, we also define a standard for the Javascript API which is provided to the NFT within the sandbox. | ||
|
||
## The Metadata | ||
|
||
Minting metadata for Smart NFTs – based on the existing [CIP-0025](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0025) standard: | ||
|
||
``` | ||
{ | ||
"721": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This proposal should use new key or, a new |
||
"<policy_id>": { | ||
"<asset_name>": { | ||
"name": <string>, | ||
|
||
"image": <uri | array>, | ||
"mediaType": "image/<mime_sub_type>", | ||
|
||
"description": <string | array>, | ||
|
||
"files": [{ | ||
"id": <string> | ||
"name": <string>, | ||
"mediaType": <mime_type>, | ||
"src": <uri | array>, | ||
<other_properties> | ||
}], | ||
|
||
"uses": { | ||
"transactions": <string | array>, | ||
"tokens": <string | array>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest to avoid double types when designing API as much as possible. If there's only one item, then an array of one item is just fine and makes it much easier for code dealing with that structure later on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, consider providing a CDDL specification for that on-chain format, rather than JSON. JSON is only how on-chain metadata are typically rendered, but they are actual stored as binary objects following in CBOR. |
||
"renderer": <string> | ||
} | ||
} | ||
}, | ||
"version": "1.0" | ||
} | ||
} | ||
``` | ||
|
||
Here we have added the “uses” key – any future additions to the Smart NFT API can be implemented by adding additional keys here. | ||
We've also added an additional field to the files array - this is to specify a unique identifier to enable the files to be referenced by the Javascript API below. | ||
|
||
### The transactions key | ||
|
||
To enable evolving NFTs where the NFT monitors a transaction history and changes in response to new transaction metadata, we define a sub-key called “transactions”, which can contain either a string or an array, specifying the tokens or addresses the NFT wishes to receive the transaction history for. These transaction histories will be provided to the NFT via the Javascript API detailed below. | ||
|
||
We also define a special keyword “own” which can be used to monitor the NFT’s own transaction history. So if we wish to create an “evolvable” NFT that can respond to additions to its own transaction history, the “uses” key within the metadata would look like: | ||
|
||
``` | ||
"uses": { | ||
"transactions": "own" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the semantic of "own"? How would a transaction identify as "own"? Would that refer to any transaction that has moved this NFT around (even if it didn't change wallets but was just carried as change)? Would that include the minting transaction? etc.. |
||
} | ||
``` | ||
|
||
If you wanted to create an evolving NFT which monitors its own transaction history, as well as that of an external smart contract address, the metadata would look like this: | ||
|
||
``` | ||
"uses": { | ||
"transactions": [ | ||
"own", | ||
"addr1wywukn5q6lxsa5uymffh2esuk8s8fel7a0tna63rdntgrysv0f3ms" | ||
] | ||
} | ||
``` | ||
Finally, we also provide the option to receive the transaction history for a specific token other than the NFT itself (generally this is intended to enable import of Oracle data or control data from an external source – although monitoring an address transaction history could also be used for that). | ||
|
||
When specifying an external token to monitor, you should do so via the token’s fingerprint as in this example: | ||
|
||
``` | ||
"uses": { | ||
"transactions": [ | ||
"own", | ||
"asset1frc5wn889lcmj5y943mcn7tl8psk98mc480v3j" | ||
] | ||
} | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both examples with the asset id and the address are heavily user-facing / JSON-specific. In principle, this metadata would be stored on-chain and I would advise against storing these things are text strings. Both addresses and asset ids have well-defined on-chain binary representation, use them! Also, regarding asset fingerprints, these are not meant to be global unique identifiers. They are fine in the context of a single wallet but they should not be used as id in a global setup. Rather, use the asset id (i.e. policy id + asset name). Incidentally, it would be okay to however display the asset fingerprint to the user, so long as it is stored as an asset id. |
||
|
||
### The tokens key | ||
|
||
To enable modifier tokens - (that is, a token which you can hold alongside a Smart NFT which changes the Smart NFT's appearance or behaviour in some way); we provide a way for the Smart NFT to monitor the tokens held by a specific address. Similarly to the “transactions” key, the “tokens” key will also accept either a string or array. | ||
|
||
In this case we define the “own” keyword to mean the tokens held by the same stake key that currently holds the Smart NFT itself. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about stake part not directly associated to a single stake key? For instance, a stake part captured by a native monetary script as a combination of three keys? Or simply, an address without stake part? Would those be simply excluded from the 'own' keyword? |
||
|
||
For example, to create an evolvable NFT which also supports modifier tokens, the “uses” block would look like this: | ||
|
||
``` | ||
"uses": { | ||
"transactions": "own", | ||
"tokens": "own" | ||
} | ||
``` | ||
|
||
We could also monitor a particular smart contract address, for example if we wanted to see how many tokens were listed for sale on a marketplace. The following example creates an NFT that supports modifier tokens and also monitors the tokens held by a script address: | ||
|
||
``` | ||
"uses": { | ||
"tokens": [ | ||
"own", | ||
"addr1wywukn5q6lxsa5uymffh2esuk8s8fel7a0tna63rdntgrysv0f3ms" | ||
] | ||
} | ||
``` | ||
|
||
### The renderer key | ||
|
||
The idea behind the renderer key is to reduce code duplication in on-chain Javascript by moving the generative code part of the project into a single asset which is minted once in the policy. Each individual NFT within a project is then just a set of input parameters to the generative script - this totally removes the need to fill the metadata of every mint transaction with encoded HTML and Javascript, as is the case with many on-chain Javascript NFTs now. | ||
|
||
When a Smart NFT is encountered which specifies another asset as the renderer, the site rendering the NFT should look-up the referenced asset and render that - the rendering token will then be responsible for reading the appropriate information via the Javascript API below and changing its appearance and/or behaviour based on that. In its simplest form, the rendering token could simply read the current token fingerprint and use that to seed a random number generator - this would enable a generative project to mint NFTs without even changing anything in the metadata and still have the renderer change its appearance for each one. In practice though, it's probably cooler to put actual traits like "colour scheme" or "movement speed" into the metadata and then have the renderer change its behaviour based on that. | ||
|
||
Via the Javascript API, the rendering token will always receive the properties of the child token which specified it as its renderer. This means if you wish to use a renderer with a token which also evolves based on its own transaction history, you will need to specify both "renderer" and "transactions" keys within the child token, and within the renderer token you do not need to specify these keys. | ||
|
||
For example, to create a Smart NFT which is rendered by another token, and is also evolvable based on its own transaction history, the "uses" key would look like this: | ||
|
||
``` | ||
"uses": { | ||
"transactions": "own", | ||
"renderer": "asset1frc5wn889lcmj5y943mcn7tl8psk98mc480v3j" | ||
} | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure to understand the rationale for storing the rendering code on-chain 🤔 ? It seems to me like a misuse of a blockchain as a database (which it is not!). If the goal is to enforce that only a certain code is executed for rendering an certain NFT, I'd suggest to instead store a hash of that code and have client applications select the right rendering code based on that. If the issue is to keep the client as dumb as possible then, I'd suggest to store the code on a system meant to store files such as IPFS, and only record a link / hash to that file on the Cardano blockchain. |
||
|
||
## The Javascript API | ||
|
||
When an on-chain Javascript NFT is rendered which specifies any of the metadata options above, the website / dApp / wallet which creates the `<iframe>` sandbox, should inject the API defined here into that `<iframe>` sandbox. It is worth saying that the wallet dApp integration API from [CIP-0030](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0030) should probably not be exposed inside the sandbox, to prevent cross-site-scripting attacks. | ||
|
||
The Paginate data type along with APIError and PaginateError are copied directly from [CIP-0030](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0030) and these functions should operate in a similar manner to that API. | ||
|
||
It is recommended that the Smart NFT API not be injected for every NFT – only the ones which specify the relevant metadata - this is an important step so that it’s clear which NFTs require this additional API, and also to enable pre-loading and caching of the required data. We are aiming to expose only the specific data requested by the NFT in its metadata – in this CIP we are not providing a more general API for querying arbitrary data from the blockchain. | ||
|
||
*There is potentially a desire to provide a more open-ended interface to query arbitrary data from the blockchain – perhaps in the form of direct access to GraphQL – but that may follow in a later CIP – additional fields which could be added to the `uses: {}` metadata to enable the NFT to perform more complex queries on the blockchain.* | ||
|
||
Although an asynchronous API is specified – so the data could be retrieved at the time when the NFT actually requests it – it is expected that in most instances the site which renders the NFT would gather the relevant transaction logs in advance, and inject them into the `<iframe>` sandbox at the point where the sandbox is created, so that the data is immediately available to the NFT without having to | ||
perform an HTTP request. | ||
|
||
### cardano.nft.fingerprint: String | ||
|
||
The fingerprint of the current token - in the case where we're rendering a child token, this will be the fingerprint of the child token. | ||
|
||
### cardano.nft.metadata : Array | ||
|
||
The content of the 721 key from the metadata json from the mint transaction of the current NFT - if we are rendering on behalf of a child NFT, this will be the metadata from the child NFT. | ||
|
||
### cardano.nft.getTransactions( string which, paginate: Paginate = undefined ) : Promise\<Object> | ||
|
||
Errors: `APIError`, `PaginateError` | ||
|
||
The argument to this function should be either an address, token fingerprint or the keyword “own”. It must match one of the ones specified via the `transactions` key in the new metadata mechanism detailed above. | ||
|
||
This function will return a list of transaction hashes and metadata relating to the specified address or token. The list will be ordered by date with the newest transaction first, and will match the following format: | ||
|
||
``` | ||
{ | ||
"transactions": [ | ||
{ | ||
"txHash": "1507d1b15e5bd3c7827f1f0575eb0fdc3b26d69af0296a260c12d5c0c78239e0", | ||
"metadata": <raw metadata from blockchain> | ||
}, | ||
<more transactions here> | ||
], | ||
"fetched": "2022-05-01T22:39:03.369Z" | ||
} | ||
``` | ||
For simplicity, we do not include anything other than the txHash and the metadata – since any other relevant details about the transaction can always be encoded into the metadata, there is no need to over-complicate by including other transaction data like inputs, outputs or the date of the transaction etc. That is left for a potential future extension of the API to include more full GraphQL support. | ||
|
||
### cardano.nft.getTokens( string address, paginate: Paginate = undefined ) : Promise\<Object> | ||
|
||
Errors: `APIError`, `PaginateError` | ||
|
||
This function accepts either an address or the keyword “own” as its argument - it must match one of the ones specified via the the `tokens` key in the new metadata mechanism detailed above. | ||
|
||
This function will return a list of the tokens held by the address specified in the argument, or held by the same stake key as the current token in the case of the “own” keyword. | ||
|
||
``` | ||
{ | ||
"tokens": [ | ||
{ | ||
"policyID": "781ab7667c5a53956faa09ca2614c4220350f361841a0424448f2f30", | ||
"assetName": "Life150", | ||
"fingerprint": "asset1frc5wn889lcmj5y943mcn7tl8psk98mc480v3j", | ||
"quantity": 1 | ||
}, | ||
<more tokens here> | ||
], | ||
"fetched": "2022-05-01T22:39:03.369Z" | ||
} | ||
``` | ||
|
||
### cardano.nft.getFileURL( string id = null, string fingerprint = null ) : Promise\<String> | ||
|
||
Errors: `APIError` | ||
|
||
This function provides access to the contents of files listed in the `files[]` array for this NFT - if the NFT is rendering on behalf of another NFT, the files arrays from both should be merged, with the child NFT items overwriting the rendering NFT, in the case of ID conflicts. | ||
|
||
The first argument specifies which entry from the files array should be retreived - if this argument is null, then the NFT's default image should be returned, which will typically come from the NFT's `image` metadata field rather than the files array. | ||
The second argument allows you to specify which token's files to search - it should either be the token itself (either the child token or the rendering token, in the case of tokens with a separate renderer). In the case where an NFT also uses the `tokens` part of this API, then the getFileURL() function will also allow you to specify any one of the fingerprints returned by the getTokens() query. | ||
|
||
The URL returned by this function should be in a format that is accessible from within the `<iframe>` sandbox - perhaps using `window.URL.createObjectURL()` to generate a static URL from raw data if necessary. |
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.
This one would be a "Process" track as it does not entail any changes in the core Cardano components (no ledger or consensus changes; it is purely built upon the current system capabilities).
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.
Support of the mutable NFT part is necessary for recording system eternal changes and will allow to chase use cases like gaming (build your character, add items to stash, work your stats) as well as multi-verse cases (transfer your character and play in another game; interactions with multi-verse market places, etc.). You can also chase cases as Cardano Weather Station, Cardano Seismic Activity, Cardano Financial Markets, New Google Maps layers from Cardano NFTs (Helium internet coverage, Favourite fruit prices in your surroundings,...). And many many many more!