Stefan Adolf
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
      • No invitee
    • Publish Note

      Publish Note

      Everyone on the web can find and read all notes of this public team.
      Once published, notes can be searched and viewed by anyone online.
      See published notes
      Please check the box to agree to the Community Guidelines.
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
No invitee
Publish Note

Publish Note

Everyone on the web can find and read all notes of this public team.
Once published, notes can be searched and viewed by anyone online.
See published notes
Please check the box to agree to the Community Guidelines.
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# IP-NFTs: Technical Background for Developers IP-NFTs are non fungible tokens that represent legally binding intellectual property (IP) currently being used within decentralized science ([DeSci](https://ethereum.org/en/desci/)). Here, we outline their inner workings and how you could utilize their metadata schema, data layer and smart contract methods to create your own intellectual property representation on EVM based blockchains. To get started with the general idea and scope of IP-NFTs, we recommend to first read [our vision of the future of research](https://www.molecule.to/blog/the-scientific-singularity-our-vision-for-the-future-of-research-with-ip-nfts) and [our non technical intro into IP-NFT mints for users](https://www.molecule.to/blog/introducing-ip-nft-v2). ## Tokens in a Nutshell Non fungible tokens ([NFTs](https://ethereum.org/en/nft/)) are smart contract based assets that associate a unique token identifier with the blockchain address of its respective owner. Their underlying smart contracts define rules on how they are minted (brought into existence), transferred or burned (destroyed). It also can restrict their ability to be transferred or offer features that are unlocked for individual token holders. Two standard NFT implementations have emerged in the Ethereum ecosystem so far: the classic [ERC-721](https://eips.ethereum.org/EIPS/eip-721) defines a unique ownership relationship between asset ids and their owners. It adds a rudimentary metadata standard by exposing a `tokenURI(uint)` method that yields an individual (or common) link to the metadata of each asset id. The slightly newer multi token standard [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155) is not fully compatible to ERC-721 but also allows users to mint and distribute several tokens of the same "kind", not unlike several instances of a rare - but not unique - gem. An ERC-1155 token kind with only one instance effectively represents assets in the same way as ERC-721 does. When it comes to web3 based ownership of assets, those two ERCs are the gold standard, they're supported by all major marketplaces and countless projects have built an ecosystem around them for trading, locking, voting and fractionalizing. NFTs can represent real world assets and brand values, too. We're using them to represent an ownership aspect for the most abstract asset class of them all: intellectual property. ## Tokenizing Intellectual Property with NFTs While there's no final answer on how real world IP can be represented by a blockchain based asset, IP-NFTs refer to legally binding paper agreements that are attached to their metadata as digital copies (PDFs). Those agreements denominate the owning entity of an intellectual property as the holder of an NFT on a certain collection. The current NFT owner can legally prove that they're holding the IP rights by downloading and verifying the attached legal documents on their own machine. Not all agreements are meant to be visible to the general public though, so mostly these documents shall be encrypted in a way that only the current NFT holder or users they granted read access can decrypt them - an aspect that's also known as "token gated" content. Once represented as an NFT, all known token mechanics become operational on the IP: it can be traded on open marketplaces, being bid and asked upon, it can be fractionalized to represent distributed ownership rights or even locked into token synthesizers to farm tokens that represent voting rights on other protocols. It's not the IP-NFT protocol itself that unleashes these possibilities, it's the NFT ecosystem that's unlocked once they have been minted by their initial holders. ## Technical Foundations IP-NFTs are ERC-1155 assets on Ethereum blockchains. Their relevant collection contract [is deployed on mainnet](https://etherscan.io/address/0x0dccd55fc2f116d0f0b82942cd39f4f6a5d88f65#code) and [on Görli testnet](https://goerli.etherscan.io/address/0x36444254795ce6E748cf0317EEE4c4271325D92A#code). Each token's metadata is stored as a file descriptor URI (e.g. `ar://HxXKCIE0skR4siRNYeLKI61Vwg_TJ5PJTbxQmtO0EPo`) that must be resolved client side, e.g. by using decentralized storage network gateways ([Arweave](https://arweave.news/introduction-to-the-most-important-infrastructure-in-the-ar-ecosystem-ar-gateway/) or [IPFS](https://ipfs.github.io/public-gateway-checker/)). The contract is non enumerable, i.e. users can't simply query their owned assets on chain but rather must rely on reading the respective event logs to build their own off chain state. We've deployed subgraphs [on mainnet](https://api.thegraph.com/subgraphs/name/moleculeprotocol/ip-nft-mainnet) and [on Görli](https://thegraph.com/explorer/subgraph/dorianwilhelm/ip-nft-subgraph-goerli) that can be queried for asset ownership and other IP-NFT related information. Here's a GraphQL example on how to do that: ```graphql query IPNFTsOf($owner: ID!) { ipnfts(where: {owner: $owner}) { id owner createdAt tokenURI } } ``` Variables: ```json { "owner": "0xd1f5B9Dc9F5d55523aB25839f8785aaC74EDE98F" } ``` Result: ```json { "data": { "ipnfts": [ ... { "id": "21", "owner": "0xd1f5b9dc9f5d55523ab25839f8785aac74ede98f", "createdAt": "1671818892", "tokenURI": "ar://HxXKCIE0skR4siRNYeLKI61Vwg_TJ5PJTbxQmtO0EPo" } ] } } ``` ### Interacting with Smart Contracts A detailed instruction on how accounts can interact with blockchain based smart contracts is far out of scope of this article. If you're new to that space, it's highly adviseable to familiarize yourself with [the fundamentals of Ethereum development](https://ethereum.org/en/developers/) and understand how clients interact with deployed contracts using [providers](https://docs.ethers.org/v5/getting-started/#getting-started--glossary), [signers](https://docs.ethers.org/v5/getting-started/#getting-started--signing), [transactions](https://docs.ethers.org/v5/api/contract/contract/#Contract--metaclass) and contract [ABIs](https://docs.soliditylang.org/en/v0.8.17/abi-spec.html?highlight=abi). We've deployed the contracts as [UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) that are owned and upgradeable by the Molecule developer team, thus the contract you're interacting with and the contract that contains the current logic are different. Make sure to always invoke functions on the UUPS proxy - its official addresses can be found on the public repo's [README file](https://github.com/moleculeprotocol/IPNFT/blob/main/README.md). The implementation contracts have been verified on Etherscan, so you can [easily retrieve their ABIs](https://etherscan.io/address/0xa4ed1346ba6a4231e35677e73eb7866c05d61725#code). ## Reserving Token IDs IP-NFTs can generally be minted by arbitrary accounts. However, to keep the collection's items as clean and valid as possible, we added a guard in front of the `mintReservation` method that requires a minter to hold a valid mintpass. To retrieve a mintpass for mainnet usage, users can submit [this self service form](https://airtable.com/shr9QN0tPPeK4GGjA) that notifies the Molecule team to drop a new mintpass for the requestor. On testnets users can simply issue mintpasses for themselves, either by following the interactive flow [on the official IP-NFT minting UI](https://ip-nft.molecule.to) or by calling one of our [dedicated mintpass dispenser contract](https://goerli.etherscan.io/address/0x0F1Bd197c5dCC6bC7E8025037a7780010E2Cd22A#writeContract)'s `dispense` methods on chain. Once holding a mintpass, the next step on the minting journey is to reserve an IP-NFT token id by calling the IP-NFT contract's `reserve()` method. This capturing step is necessary because the legal documents attached to the final IP-NFT are referring to the NFT's token id that only becomes available after the mint has occurred. Minters will use the token id to craft the legal documents that outline the rights and obligations of owning that NFT in the real world. A selection of premade contract templates for IP-NFTs can be found [on Github](https://github.com/moleculeprotocol/Legal-Contracts). ## Assemble and Upload Metadata The JSON metadata documents behind IP-NFTs are required to strictly validate against a [well defined JSON schema](https://bafybeiho346bhsmtvt3opy3fxznraiueeagmco2rvzy5pfa74ru5vovk6m.ipfs.w3s.link/ipnft.schema.json) that's flexible enough to cover many relevant use cases. [Here's a visual tool](https://jsonschema.dev/s/EmB1F) to investigate a valid IP-NFT's metadata interactively. Note that the generic fields `name`, `image` and `description` are located on the document's root level [as required by ERC-1155](https://eips.ethereum.org/EIPS/eip-1155#metadata), whereas the `agreements` and `project_details` structures are modeled as rich property definitions. ```json { "schema_version": "0.0.1", "name": "Our awesome test IP-NFT", "description": "Lorem ipsum dolor sit amet, ...", "image": "ar://7De6dRLDaMhMeC6Utm9bB9PRbcvKdi-rw_sDM8pJSMU", "external_url": "https://ip-nft.com/1", "properties": { "type": "IP-NFT", "agreements": [ { "type": "License Agreement", "url": "ar://4FG3GR8qCdLo923tVBC85NTYHaaAc3TvCsF3aZwum_o", "mime_type": "application/pdf", "content_hash": "bagaaiera7ftqs3jmnoph3zgq67bgjrszlqtxkk5ygadgjvnihukrqioipndq", } ], "project_details": { "industry": "Pharmaceutical R&D", "organization": "Newcastle University, UK", "topic": "Aging", "research_lead": { "name": "Chuck Norris", "email": "chuck@norris.com" } } } } ``` ### Validate Metadata Correctness To validate IP-NFT metatadata documents against that schema, you can use arbitrary JSON schema tools. [Ajv](https://ajv.js.org/guide/getting-started.html) is one of the most powerful ones in Javascript. We're omitting the code to retrieve schema or documents for brevity's sake but in a nutshell validation boils down to: ```typescript import Ajv from "ajv"; import addFormats from "ajv-formats"; (async () => { const ipnftSchema = JSON.parse(ipnftSchemaJson); const document = JSON.parse(ipnftMetadataJson); const ajv = new Ajv(); addFormats(ajv); const validateIpnft = ajv.compile(ipnftSchema); const validationResult = validateIpnft(document); })(); ``` ### Link to External Resources IP-NFT metadata documents require you to refer to external resources, e.g. the `image` or `agreements[].url` fields. While you can choose to go with the well known `https://` protocol, it's advisable to use web3-native decentralized storage pointers like `ar://` or `ipfs://` instead. Clients are supposed to resolve them to their respective http gateway counterparts and most NFT related services and frontends can handle them. You don't need to run an Arweave or IPFS node yourself - [Ardrive](https://app.ardrive.io) or [web3.storage](web3.storage/) are excellent helper services that get the job done and our official IP-NFT minting UI uses [Bundlr](https://bundlr.network/) to efficiently upload files to the Arweave network with [near instant finalization](https://docs.bundlr.network/docs/FAQs/Dev-FAQ#what-is-optimistic-finalization). Here's a nodejs example on how to upload a JSON document using web3.storage (it's simpler to do this [within a browser context](https://web3.storage/docs/how-tos/store/#preparing-files-for-upload), though): ```typescript import dotenv from "dotenv"; import { Blob } from "node:buffer"; import { Web3Storage } from "web3.storage"; dotenv.config(); const w3sClient = new Web3Storage({ token: process.env.W3S_TOKEN }); (async () => { const content = { uploaded_at: new Date().toISOString(), }; const binaryContent = new Blob([JSON.stringify(content)], { type: "application/json", }); const file = { name: "filename.json", stream: () => binaryContent.stream(), }; //@ts-ignore const cid = await w3sClient.put([file], { name: "some.json", wrapWithDirectory: false, }); console.log(cid); })(); ``` results in an IPFS CIDv1: ``` bafkreicrhuxfzrydht6tmd4kyy6pkbhspqthswg6xbiqlaztmf774ojxhq ``` To resolve the published content, you can request it from any public IPFS http gateway. You'll experience the lowest latency when querying a gateway close to the node that you used for uploading; web3.storage offers `https://w3s.link` for that purpose. Requesting [https://w3s.link/ipfs/bafkreicrhuxfzrydht6tmd4kyy6pkbhspqthswg6xbiqlaztmf774ojxhq](https://w3s.link/ipfs/bafkreicrhuxfzrydht6tmd4kyy6pkbhspqthswg6xbiqlaztmf774ojxhq) yields ``` {"uploaded_at":"2023-01-02T22:45:17.788Z"} ``` ## Decentralized Encryption and IP-NFT Token Gating An IP-NFT's most important metadata property are its `agreements`, another term for legal documents attached to it. These documents refer to the IP-NFT smart contract's ("collection") address and the IP-NFT's token id. The agreements' content might contain confidential information about the involved parties and hence should be encrypted before being uploaded. Decentralized and permissionless encryption is a non trivial requirement that we solve by relying on [Lit Protocol](https://developer.litprotocol.com/SDK/Explanation/encryption). Lit runs a network of nodes that derive signing and encryption keys by multiparty computation / threshold cryptography on trusted computing enclaves. The nodes themselves only know parts of the private key that effectively is fully assembled on the client side after all conditions for key retrieval have been met. Lit protocol allows gating any content behind access control conditions that are backed by blockchain state, therefore it's disclosing decryption keys only to holders of an NFT or users that meet a certain condition on chain. [Lit's documentation](https://developer.litprotocol.com/SDK/Explanation/encryption#encrypting) lays out the encryption process in detail. To instantiate a Lit SDK instance that's capable of encrypting or decrypting content, [it needs](https://developer.litprotocol.com/sdk/explanation/walletsigs/authsig/#obtaining-the-authsig) an [EIP-4361](https://login.xyz) compatible signature that proves control over the current user's account. Once authenticated we can request a new symmetric key to encrypt our content and ultimately ask the Lit network nodes to store its key shares along with an access control condition. That request yields an encrypted decryption key (😵‍💫) that has been created by the network nodes. ```typescript import LitJsSdk from '@lit-protocol/sdk-browser' const litClient = new LitJsSdk.LitNodeClient() await client.connect() //connects to Lit network nodes const file: Blob | File = //some file const { encryptedFile, symmetricKey } = await LitJsSdk.encryptFile({ file }) //you can reuse a Siwe / EIP-4361 compatible signed message here, see https://login.xyz/ const authSig = await LitJsSdk.checkAndSignAuthMessage({ chain: "ethereum" }); accessControlConditions = { conditionType: 'evmBasic', contractAddress: tokenContractAddress, standardContractType: 'ERC1155', chain: "ethereum", method: 'balanceOf', parameters: [':userAddress', tokenId], returnValueTest: { comparator: '>', value: '0' // user owns more than 0 of this token id } } const u8encryptedSymmetricKey = await litClient.saveEncryptionKey({ unifiedAccessControlConditions: accessControlConditions, symmetricKey, authSig, chain, permanent: false }) ``` A user who wants to decrypt the content must again initialize the SDK using a signed message that proves control over their address. Next, they ask several network nodes to present their key shares of the encrypted key by presenting the access control conditions and the encrypted symmetric key to the network. If the network nodes find that the account matches the given conditions, each one yields its key share for the encrypted decryption key. With that, the SDK decrypts the key the content has initially been encrypted with. ```typescript const authSig = await LitJsSdk.checkAndSignAuthMessage({ chain: "ethereum" }); const symmetricKey = await litClient.getEncryptionKey({ unifiedAccessControlConditions: accessControlConditions, toDecrypt: encryptedSymmetricKey, chain: "ethereum", authSig }) ``` An IP-NFT metadata's `agreements` item can store the encrypted symmetric key and its access control conditions inside its `encryption` field. Note that the IP-NFT JSON schema of `access_control_conditions` is externally [defined by Lit protocol](https://github.com/LIT-Protocol/lit-accs-validator-sdk/tree/main/src/schemas). ```json "agreements": [ { "type": "License Agreement", "url": "ar://4FG3GR8qCdLo923tVBC85NTYHaaAc3TvCsF3aZwum_o", "mime_type": "application/pdf", "content_hash": "bagaaiera7ftqs3jmnoph3zgq67bgjrszlqtxkk5ygadgjvnihukrqioipndq", "encryption": { "protocol": "lit", "encrypted_sym_key": "fcbeaf3af31c7104d1d1f7099a6c6f6fda5803a4f7a0bef93256f3377450291872ad07bed3e9402cb47cc932c8f48219e56b84c06becd5ec0ee83ef2c0c93b932fb675c7932fa8df0ad164f17642b32415e382081577a403c19da2eff22c9083fa134ad1f370c2bec449adcdea790498637c7238b7d67cf2d69507a962656d3200000000000000205e4ad4e6323e06862babc934f740bc2e566d175a5da23bb4f1b35635e5cc3768cd040e8776307ea038484ff42033c18f", "access_control_conditions": [ { "conditionType": "evmBasic", "contractAddress": "0xa1c301d77f701037f491c074e1bd9d4ac24cf5e5", "standardContractType": "ERC1155", "chain": "goerli", "method": "balanceOf", "parameters": [ ":userAddress", "6" ], "returnValueTest": { "comparator": ">", "value": "0" } } ] } } ] ``` ### Using Multisig Wallet Signers Due to the high value nature of IP-NFTs you might feel tempted to use a multisig wallet for the minting process, maybe because you'd like to prove that the IP-NFT has been created by a dedicated group of individuals. This works fine for all contract function invocations but is not supported by Lit protocol. Multisig wallets (or contract wallets to be precise) cannot sign messages in the way it's required to authenticate against Lit nodes because they're not based on a private key. This might once be possible by utilizing [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) compatible wallet signatures but [is not supported](https://discord.com/channels/896185694857343026/916383445784096839/1058018912010248232) at the time of writing. The recommended workaround is to denote a dedicated trusted member of the multisig that's supposed to initially own the minted IP-NFT. This could be the researcher, a core contributor or maintainer of the project. The IP-NFT contract's `mintReservation` function takes a recipient parameter (`to`) that defines the NFT's initial owner. Note, that the account that *invokes* the mint function is required to hold a mint pass, not the receiver. ### Granting Read Access to Third Parties Another shortcoming related to Lit's requirement of private key based authentication signatures is that multisig token holders cannot prove their address to the protocol. To allow multisig members to decrypt the accompanying agreement documents, the IP-NFT contract contains a [`grantReadAccess`](https://goerli.etherscan.io/address/0x36444254795ce6E748cf0317EEE4c4271325D92A#writeProxyContract#F3). It can be invoked by the current token holder (e.g. a multisig) to grant certain accounts (e.g. some of their members or potential buyers) read access to the underlying content for a limited amount of time. Its counterpart [`canRead`](https://goerli.etherscan.io/address/0x36444254795ce6E748cf0317EEE4c4271325D92A#readProxyContract#F3) yields a boolean whether the `reader` is currently allowed access. For the current owner of an IP-NFT this method always returns `true`. To make read grants work inside Lit protocol, one can craft a [custom contract access control condition](https://developer.litprotocol.com/coreConcepts/accessControl/EVM/customContractCalls) that not only takes the current IP-NFT ownership into account but also lets users pass that currently are granted read access: ```json "encryption": { "protocol": "lit", "encrypted_sym_key": "...", "access_control_conditions": [ { "conditionType": "evmContract", //the IP-NFT UUPS proxy contract address "contractAddress": "0x36444254795ce6E748cf0317EEE4c4271325D92A", "chain": "goerli", "functionName": "canRead", "functionParams": [ ":userAddress", "10" ], "functionAbi": { "name": "canRead", "inputs": [ { "internalType": "address", "name": "reader", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" } ], "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, "returnValueTest": { "key": "", "comparator": "=", "value": "true" } } ] } ``` ## Proving Content Integrity Since agreement documents are encrypted before being stored, each agreement item may contain a `content_hash` that downloaders can use to prove the legal documents' content integrity after they've decrypted it. When using IPFS as storage layer this hash is not adding much value since the network's content ids already provide an untamperable way of guaranteeing content integrity, however [they're not derived from the original content](https://docs.ipfs.tech/concepts/hashing/#content-identifiers-are-not-file-hashes) and hard to prove without an IPFS node at hand. The `content_hash` field shall contain the sha-256 digest of the attachment's binary content, encoded as a multihash compatible to CIDv1. This allows clients to decode the content hash and verify a document's content without being aware of the hashing algorithm used. This is how it looks like in Typescript using the [multiformats NPM package](https://www.npmjs.com/package/multiformats): ```typescript import { CID } from "multiformats/cid"; import * as json from "multiformats/codecs/json"; import { sha256 } from "multiformats/hashes/sha2"; const checksum = async (u8: Uint8Array) => { //https://multiformats.io/multihash/ const digest = await sha256.digest(u8); return CID.create(1, json.code, digest); }; const verifyChecksum = async ( u8: Uint8Array, _cid: string ): Promise<boolean> => { const cid = CID.parse(_cid); //https://github.com/multiformats/multicodec/blob/master/table.csv#L9 console.log("hash algo used: 0x%s", cid.multihash.code.toString(16)); const digest = await sha256.digest(u8); return cid.multihash.bytes.every((el, i) => el === digest.bytes[i]); }; (async () => { const binaryContent = new TextEncoder().encode("This is the content"); const cid = await checksum(binaryContent); const valid = await verifyChecksum(binaryContent, cid.toString()); console.log(cid, valid); })(); ``` ## Putting it all together: The IP-NFT Minting Flow for Developers To sum up, minting an IP-NFT technically requires the following steps: - Get a Mintpass for the account that's supposed to finally invoke the `mintReservation` function. - invoke the IPNFT contract's `reserve()` function to reserve a token id - it will revert if the caller is not holding a valid Mintpass - get the reserved token id by parsing the method's event log - upload an image to a (de)centralised network of your choice - use the token id and the IPNFT contract's address to create legal documents - compute a checksum over the original documents - optionally encrypt the documents with a Lit access control condition - assemble a metadata structure containing the file pointers, access control conditions, encrypted symmetric key and checksum - verify that this metadata structure is valid - upload the metadata to a (de)centralised network of your choice - invoke `mintReservation` on the IPNFT contract with - `address to`: the recipient of the minted NFT (e.g. a Multisig wallet) - `uint256 reservationId`: the token id you've reserved - `uint256 mintPassId`: the Mintpass id that's going to be redeemed during the mint - `string memory tokenURI`: the URI that resolves to the metadata ## Conclusion IP-NFTs are DeSci primitives that allow researchers, institutions and laboratories to prematurely mint the legal implications of their work as a tradable and verifiable asset. They deal as fundamental ownership anchors that can be used to refer and gate access to other kinds of protectable content, e.g. machine learning models, data streams or non public documents. Being built on well known standard interfaces, they can be traded or fractionalized using a vast range of protocols that already exist today. As you have learnt here, the minting process of IP-NFTs is not a trivial task, particularly if they're containing references to encrypted content. However, their metadata schema is designed to be flexible enough to cover many IP related use cases and the IP-NFT's contract interface should be simple to interact with. The IP-NFT contract is deployed as an upgradeable contract that's maintained by the Molecule core team and we're planning to add more exciting features to it in the near future. You can find all relevant sources on our official [Github repo](https://github.com/moleculeprotocol/IPNFT) and runnable versions of the code samples depicted in this article in the accompanying [samples repo](https://github.com/moleculeprotocol/ipnft-samples).

Import from clipboard

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template is not available.
Upgrade
All
  • All
  • Team
No template found.

Create custom template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

How to use Slide mode

API Docs

Edit in VSCode

Install browser extension

Get in Touch

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Upgrade to Prime Plan

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

No updates to save
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Upgrade

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Upgrade

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully