Closed tash-2s closed 2 years ago
Thanks for reporting this issue, and I think it makes sense. I'll do some investigation further and implement it when ready.
Thank you for your response. The OpenSquare team is doing excellent work!
What can I do to move this forward?
We're a little busy with other products development. I'm wondering is there a standard for on-chain image? The format you mentioned is like following 2 json objects.
{"name":"🧬","image":"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' style='background:black'></svg>"}
{"image":"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 -7 9 9'><text font-size='7'>🆗</text></svg>"}
Is it a standard for image
field in a json object?
I believe putting a data URL into the image field, like the above examples, is a standard for on-chain images on Ethereum. Although there's no established specification for on-chain metadata, here is some reasoning.
EIP-721 defines metadata extension JSON structure: name
, description
, and image
. The image field is explained as:
A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents.
SVG data URLs satisfy this requirement.
Nextly, I break down the metadata of the example NFTs, which are supported on OpenSea. Uniswap and Loot use the same metadata format, so I only show Loot here for them. Many projects follow a similar structure.
On ERC721 Metadata, tokenURI()
returns metadata in the form of URI. (On pallet-uniques
metadata, it looks like directly set IPFS id or JSON dump are more used.)
tokenURI(1)
returns:
data:application/json;base64,eyJuYW1lIjogIkJhZyAjMSIsICJkZXNjcmlwdGlvbiI6ICJMb290IGlzIHJhbmRvbWl6ZWQgYWR2ZW50dXJlciBnZWFyIGdlbmVyYXRlZCBhbmQgc3RvcmVkIG9uIGNoYWluLiBTdGF0cywgaW1hZ2VzLCBhbmQgb3RoZXIgZnVuY3Rpb25hbGl0eSBhcmUgaW50ZW50aW9uYWxseSBvbWl0dGVkIGZvciBvdGhlcnMgdG8gaW50ZXJwcmV0LiBGZWVsIGZyZWUgdG8gdXNlIExvb3QgaW4gYW55IHdheSB5b3Ugd2FudC4iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhCeVpYTmxjblpsUVhOd1pXTjBVbUYwYVc4OUluaE5hVzVaVFdsdUlHMWxaWFFpSUhacFpYZENiM2c5SWpBZ01DQXpOVEFnTXpVd0lqNDhjM1I1YkdVK0xtSmhjMlVnZXlCbWFXeHNPaUIzYUdsMFpUc2dabTl1ZEMxbVlXMXBiSGs2SUhObGNtbG1PeUJtYjI1MExYTnBlbVU2SURFMGNIZzdJSDA4TDNOMGVXeGxQanh5WldOMElIZHBaSFJvUFNJeE1EQWxJaUJvWldsbmFIUTlJakV3TUNVaUlHWnBiR3c5SW1Kc1lXTnJJaUF2UGp4MFpYaDBJSGc5SWpFd0lpQjVQU0l5TUNJZ1kyeGhjM005SW1KaGMyVWlQaUpIY21sdElGTm9iM1YwSWlCSGNtRjJaU0JYWVc1a0lHOW1JRk5yYVd4c0lDc3hQQzkwWlhoMFBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJME1DSWdZMnhoYzNNOUltSmhjMlVpUGtoaGNtUWdUR1ZoZEdobGNpQkJjbTF2Y2p3dmRHVjRkRDQ4ZEdWNGRDQjRQU0l4TUNJZ2VUMGlOakFpSUdOc1lYTnpQU0ppWVhObElqNUVhWFpwYm1VZ1NHOXZaRHd2ZEdWNGRENDhkR1Y0ZENCNFBTSXhNQ0lnZVQwaU9EQWlJR05zWVhOelBTSmlZWE5sSWo1SVlYSmtJRXhsWVhSb1pYSWdRbVZzZER3dmRHVjRkRDQ4ZEdWNGRDQjRQU0l4TUNJZ2VUMGlNVEF3SWlCamJHRnpjejBpWW1GelpTSStJa1JsWVhSb0lGSnZiM1FpSUU5eWJtRjBaU0JIY21WaGRtVnpJRzltSUZOcmFXeHNQQzkwWlhoMFBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJeE1qQWlJR05zWVhOelBTSmlZWE5sSWo1VGRIVmtaR1ZrSUV4bFlYUm9aWElnUjJ4dmRtVnpQQzkwWlhoMFBqeDBaWGgwSUhnOUlqRXdJaUI1UFNJeE5EQWlJR05zWVhOelBTSmlZWE5sSWo1T1pXTnJiR0ZqWlNCdlppQkZibXhwWjJoMFpXNXRaVzUwUEM5MFpYaDBQangwWlhoMElIZzlJakV3SWlCNVBTSXhOakFpSUdOc1lYTnpQU0ppWVhObElqNUhiMnhrSUZKcGJtYzhMM1JsZUhRK1BDOXpkbWMrIn0=
The base64 part is decoded into:
{"name"=>"Bag #1", "description"=>"Loot is randomized adventurer gear generated and stored on chain. Stats, images, and other functionality are intentionally omitted for others to interpret. Feel free to use Loot in any way you want.", "image"=>"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCAzNTAgMzUwIj48c3R5bGU+LmJhc2UgeyBmaWxsOiB3aGl0ZTsgZm9udC1mYW1pbHk6IHNlcmlmOyBmb250LXNpemU6IDE0cHg7IH08L3N0eWxlPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9ImJsYWNrIiAvPjx0ZXh0IHg9IjEwIiB5PSIyMCIgY2xhc3M9ImJhc2UiPiJHcmltIFNob3V0IiBHcmF2ZSBXYW5kIG9mIFNraWxsICsxPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSI0MCIgY2xhc3M9ImJhc2UiPkhhcmQgTGVhdGhlciBBcm1vcjwvdGV4dD48dGV4dCB4PSIxMCIgeT0iNjAiIGNsYXNzPSJiYXNlIj5EaXZpbmUgSG9vZDwvdGV4dD48dGV4dCB4PSIxMCIgeT0iODAiIGNsYXNzPSJiYXNlIj5IYXJkIExlYXRoZXIgQmVsdDwvdGV4dD48dGV4dCB4PSIxMCIgeT0iMTAwIiBjbGFzcz0iYmFzZSI+IkRlYXRoIFJvb3QiIE9ybmF0ZSBHcmVhdmVzIG9mIFNraWxsPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxMjAiIGNsYXNzPSJiYXNlIj5TdHVkZGVkIExlYXRoZXIgR2xvdmVzPC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxNDAiIGNsYXNzPSJiYXNlIj5OZWNrbGFjZSBvZiBFbmxpZ2h0ZW5tZW50PC90ZXh0Pjx0ZXh0IHg9IjEwIiB5PSIxNjAiIGNsYXNzPSJiYXNlIj5Hb2xkIFJpbmc8L3RleHQ+PC9zdmc+"}
The image base64 part is decoded into:.
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">"Grim Shout" Grave Wand of Skill +1</text><text x="10" y="40" class="base">Hard Leather Armor</text><text x="10" y="60" class="base">Divine Hood</text><text x="10" y="80" class="base">Hard Leather Belt</text><text x="10" y="100" class="base">"Death Root" Ornate Greaves of Skill</text><text x="10" y="120" class="base">Studded Leather Gloves</text><text x="10" y="140" class="base">Necklace of Enlightenment</text><text x="10" y="160" class="base">Gold Ring</text></svg>
tokenURI(1208480988381788759709365916903450620926929939049)
returns:
data:text/plain,{"name":"Mandala 0xd3ae29654c1d0638d194bd9110422da551402a69","description":"A Unique Mandala","image":"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' shape-rendering='crispEdges' width='512' height='512'><g transform='scale(64)'><image width='8' height='8' style='image-rendering: pixelated;' href='data:image/gif;base64,R0lGODdhEwATAMQAAAAAAPb+Y/7EJfN3NNARQUUKLG0bMsR1SujKqW7wQwe/dQBcmQeEqjDR0UgXo4A0vrlq2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkKAAAALAAAAAATABMAAAdNgAAAAAAAAAAADQMNAAAAAAAAAAAAAAAKDgAAAAIJAgAAAA4KAAAAAAAABgUABAwBDQEMBAAFBgAAAAAKBhAGAwgNAQkBDQgDBhAGCgBNgAAOBQYECw0JAQEBCQ0LBAYFDgAAAAADCxAEAgINAgIEEAsDAAAAAAAECA0ECgUFAQUFCgQNCAQAAAAADA0JAgUEEAIQBAUCCQ0MAABNgA0CAQEBAgUQCgYKEAUCAQEBAg0DCQ0JAQ0BAgYJBgIBDQEJDQkDDQIBAQECBRAKBgoQBQIBAQECDQAADA0JAgUEEAIQBAUCCQ0MAABNgAAABAgNBAoFBQEFBQoEDQgEAAAAAAADCxAEAgINAgIEEAsDAAAAAA4FBgQLDQkBAQEJDQsEBgUOAAAKBhAGAwgNAQkBDQgDBhAGCgA6gAAAAAYFAAQMAQ0BDAQABQYAAAAAAAAKDgAAAAIJAgAAAA4KAAAAAAAAAAAAAAANAw0AAAAAAAAAAAGBADs='/></g></svg>"}
Their use of image fields matches ours.
OpenSea also accepts an image_data
field, allowing a pure SVG string, not a data URL string. I'm not sure how widely used/supported this field is.
Therefore, "image":"data:image/svg+xml,<svg...
is considered standard. "image_data":"<svg...
may be good also, as it uses fewer bytes.
Many thanks to the detailed explanation. I think they are enough for our implementation.
Thank you for working on this. https://github.com/opensquare-network/statescan/pull/1055 @hyifeng I'm wondering about the current state of the PR. What can I help you with?
@wliyongfeng This PR is waiting for my review. I will do it soon.
I'm sure you're very busy, and I don't want to bother you, but how is this going? https://github.com/opensquare-network/statescan/pull/1057 has been merged but not deployed yet?
@tash-2s Yeah, it has been merged and we will deploy it recent days.
Many thanks, that's perfect! I'm grateful for your support.
Could Statescan support NFT metadata stored directly on-chain?
Here is an example of an on-chain metadata asset created on Westmint.
Background
Storing IPFS id or external URL as metadata is common practice in the blockchain space. However, fully on-chain NFTs are becoming popular as well. They don't rely on other infrastructure, so it's useful in some areas.
Examples of on-chain NFTs on Ethereum:
Also, it looks like putting JSON directly into metadata is expected on
pallet-uniques
.So I want the support on Statescan.
A drawback for on-chain metadata for Statemint/e is a tight limit on metadata size. Up to 128 bytes is allowed now, so we cannot store general data. But this is enough for my use case.
@wliyongfeng