Metadata
Contract metadata offers context and descriptive information about a smart contract. While not affecting the contract's execution, they provide insights regarding its functionality, purpose, and other relevant details that may be used by wallets, explorers, decentralized applications, etc.
The primary Tezos standard for metadata is TZIP-016 (Tezos Metadata Standard).
Metadata dictionary
- sp.create_tzip16_metadata(name: str, description: str, version: str, license_name: str, license_details: str, interfaces: List[str], authors: List[str], homepage: str, source_uri: str,) → dict[str, any]
Return a TZIP-016 compliant metadata dictionary. All arguments are optional.
Args:
name
: The name of the contract. Should identify its purpose.description
: A proper natural language description of the contract.version
: The version of the contract.
The version together with the name should be unique in the chain.
For example:"1.0.0"
.license_name
: De facto license name when possible. See Debian guidelines.license_details
: Optional license details.interfaces
: A list of the TZIP interfaces supported by the contract."TZIP-016"
is automatically added to this list.
For example:["TZIP-012"]
.authors
: A list of the authors of the contract.
For example:["SmartPy <https://smartpy.io/>"]
.homepage
: The homepage is for human-consumption. It may be the location of the decentralized app website, a git repository, a SmartPy ide link, how to submit issue tickets, a more elaborate description, etc.source_uri
: The URI of a document giving all details to rebuild the contract from SmartPy source in the format'ipfs://<hash>'
.
For example,'ipfs://QmaV5gQ6p9ND9pjc1BPD3dc8oyi8CWEDdueSmkmasiaWGA'
.
See source.offchain_views
: The compiled offchain views of the contract.
See offchain_views.
For example:
pythonimport smartpy as sp @sp.module def main(): class MyContract(sp.Contract): pass @sp.add_test() def test sc = sp.test_scenario("MyTest", main) c1 = main.MyContract() metadata_dict = sp.create_tzip16_metadata( name="MyContract", version="1.0.0", license_name="CC0", description="This is a demo contract using SmartPy.", authors=["SmartPy <https://smartpy.io/>"], homepage="https://smartpy.io/ide?template=contract_metadata.py", # Uncomment if you want to upload the SmartPy source code of the # contract to IPFS and specify them in the metadata dictionary. # source_uri=sp.pin_on_ipfs(c1.get_source()), offchain_views=c1.get_offchain_views(), )
The TZIP-16 standard is flexible, you can add any non-standard field with information as needed. In the following example we associate a calendar url with the key "calendar": metadata_dict["calendar"] = "<url>.ics"
Upload to IPFS
The metadata dictionary must be converted to JSON and uploaded off-chain.
TIP
You can upload the file on your own, by adding import json
, encoded_metadata = json.dumps(metadata_dict)
and a way to push the JSON file to IPFS using your preferred tools.
SmartPy provides a utility to help you upload and pin the JSON data on IPFS through the Pinata service.
INFO
IPFS and "pinning" ensures that content remains accessible. As long as at least one node pins the content, it remains available to the network. Moreover, any party in possession of the data can re-upload it to IPFS. Since the data's hash remains consistent, the content becomes instantly accessible via the original URI.
To seamlessly upload your metadata to IPFS and ensure its continued availability, you can use sp.pin_on_ipfs
. This function interfaces with the Pinata service, a popular IPFS pinning provider, to store your data on the decentralized web.
Prior to this, you need to to create an account on Pinata and create an API key with the following right: in "API Endpoint Access" check "Pinning -> pinJSONToIPFS".
- sp.pin_on_ipfs(data: dict[str, any], api_key: str = None, secret_key: str = None,) → str
Pin a json file to IPFS through Pinata.
Args:
data
: The metadata to pin.
For example, the data returned bysp.create_tzip16_metadata(...)
or bycontract.get_source()
.api_key
: The API key of your Pinata api.
Optional, can be specified via the"PINATA_KEY"
environment variable.secret_key
: The secret key of your Pinata api.
Optional, can be specified via the"PINATA_SECRET"
environment variable.
For example:
python# Pins the source code on IPFS and retrieves the URI. # See "source" below. source_uri = sp.pin_on_ipfs(source_code) metadata_dict = sp.create_tzip16_metadata(..., source_uri=source_uri) metadata_uri = sp.pin_on_ipfs(metadata_dict)
TIP
Environment variables, like
"PINATA_KEY"
and"PINATA_SECRET"
, can be set in your operating system or within your development environment. They provide a secure means of storing sensitive information outside of your codebase.
Metadata bigmap
The metadata are held in a big map of type sp.big_map[sp.string, sp.bytes]
named metadata
in the storage of the contract. The bigmap always contain the empty key ""
and its value is an encoded URI which points to a JSON document (the one created with sp.create_tzip16_metadata
).
While it's possible to provide additional metadata directly in the contract's storage, the JSON document should be the main place to store your metadata.
- sp.scenario_utils.metadata_of_url(url: str) → sp.big_map[sp.string, sp.bytes]
Create a big map with an empty key with the encoded url for value.
For example:
pythonmetadata_big_map = sp.scenario_utils.metadata_of_url( "ipfs://QmaV5gQ6p9ND9pjc1BPD3dc8oyi8CWEDdueSmkmasiaWGA") # Equivalent to metadata_big_map = sp.big_map({ "": sp.bytes("0x" + s.encode("utf-8").hex()) })
Offchain views
Offchain views are public functions that can be called without interaction to the chain. They can be used by decentralized applications, wallets or explorers to show informations about the contract. Some are mandatory to comply certain contracts standards like FA2.
The code of the offchain views is held in the metadata JSON document under the "views"
field.
Source
If you want to expose the source code of your contract, you can retrieve it with c.get_source()
and push it onto IPFS (see Upload to IPFS). The resulting URI (starting with ipfs://
) can then be indicated in the metadata directory.
Here is an example of the source uploaded to IPFS.
It contains the following elements:
module_
: the module from which the contract was built.name
: the class name.param
: the parameters given to the constructor after evaluation.modules
: all the modules imported in the scenario.flags
: the flags given to the compilation.
Check source
Anyone can check that the source indicated in the metadata corresponds to the Michelson code uploaded on the Tezos blockchain.
- sp.get_metadata(address: str, network = "mainnet") → dict
Fetches and returns the metadata dictionary for a given contract address and network from an explorer API like TZKT.
- sp.get_metadata_code(metadata: dict) → dict
Fetches and returns the code metadata for a contract's metadata through an IPFS endpoint, for example ipfs.io.
- sp.get_code(address: str, network: "mainnet") → str
Fetches and returns the Michelson code of a contract from an explorer API like TZKT.
- sp.check_validity(metadata: dict, code_metadata: dict, onchain_michelson: str) →
Checks that the code given in the metadata generates the same onchain code. Raises
CodeMismatchException
if the codes do not match.INFO
The metadata dictionary is used to ensure that the version of SmartPy you are using is compatible with the version specified in the metadata. If you need to verify metadata for a contract created with a different version of SmartPy, you should run this function using that specific version of SmartPy.
For example:
pythonimport smartpy as sp address = "KT1EQLe6AbouoX9RhFYHKeYQUAGAdGyFJXoi" metadata = sp.get_metadata(address, network="ghostnet") code_metadata = sp.get_metadata_code(metadata) onchain_michelson = sp.get_michelson_code(address, network="ghostnet") try: sp.check_validity(metadata, code_metadata, onchain_michelson) print("Metadata code is valid") except sp.CodeMismatchException as e: print(e) print("Generated michelson:\n", e.generated_michelson) print("Details of the first difference:", e.diff_details)