{"id":13731576,"url":"https://github.com/jcnelson/nftree","last_synced_at":"2026-01-27T02:35:09.611Z","repository":{"id":76940264,"uuid":"439772471","full_name":"jcnelson/nftree","owner":"jcnelson","description":"A scalable way to mint NFTs that earn their owners a yield in STX","archived":false,"fork":false,"pushed_at":"2022-03-10T15:46:21.000Z","size":95,"stargazers_count":46,"open_issues_count":2,"forks_count":10,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-05-29T16:39:36.272Z","etag":null,"topics":["clarity-smart-contracts","nft","stacks","stx"],"latest_commit_sha":null,"homepage":"","language":"Clarity","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jcnelson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-12-19T04:12:06.000Z","updated_at":"2025-03-29T13:42:13.000Z","dependencies_parsed_at":"2024-01-07T06:00:16.949Z","dependency_job_id":"9ed84330-9c50-454b-b5b2-6835716ad465","html_url":"https://github.com/jcnelson/nftree","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jcnelson/nftree","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcnelson%2Fnftree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcnelson%2Fnftree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcnelson%2Fnftree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcnelson%2Fnftree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jcnelson","download_url":"https://codeload.github.com/jcnelson/nftree/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcnelson%2Fnftree/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28796977,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T01:07:07.743Z","status":"online","status_checked_at":"2026-01-27T02:00:07.755Z","response_time":168,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clarity-smart-contracts","nft","stacks","stx"],"created_at":"2024-08-03T02:01:33.325Z","updated_at":"2026-01-27T02:35:09.595Z","avatar_url":"https://github.com/jcnelson.png","language":"Clarity","funding_links":[],"categories":["Clarity"],"sub_categories":[],"readme":"# NFTrees\n\nA scalable way to mint arbitrarily large collections of NFTs that\nearn their owners a yield in STX.\n\n## TL;DR: Features\n\n* Mint an *arbitrarily large* collection of NFTs with a *single* transaction.\n* The buyer, not the creator, pays the transaction fee for minting the NFT.\n* NFTs generate *passive income* for their owner in STX if the owner\n  locks them up to prevent them from being sold or accessed on-chain.\n* Fire-and-forget minting.  NFTs are instantiated on-chain via the smart\n  contract on buyer demand.  The NFT creator does not need to mint and sell\nNFTs once the collection is published as an NFTree.\n* The NFT creator gets a commission fee each time an NFT is instantiated.\n* Minting process rewards early NFT community members of successful NFT projects\n  built as NFTrees.\n\n## Background\n\nAn NFT is a representation of a binding between a principal and data on a blockchain.  The principal\nis identified by a set of one or more public keys or smart contracts, and the\ndata is a public arbitrary string of bytes (such as, but not limited to, an\nimage).  The binding is maintained by a blockchain, which provides a reasonable\nand programmatically-enforced assurance that only the bound principal can\n_transfer_ the data to another principal.\n\nNFTs are a fundamental building block for a user-owned Internet.  Providing a\nway to prove that a particular piece of data is bound to a particular\nprincipal enables users to treat their data as _captial assets_ within the\ncontext of NFT-aware applications.  For example, an NFT can act as an API key\nto an application's features: only people who own a particular kind of NFT may\naccess them.  If the features are valuable -- as in, someone might be willing to\npay for them -- then the NFT itself is valuable.  As another example, an NFT can\nact as a row in an equity capitalization table of a company: the owner of the\nNFT is entitled to revenue from the company proportional to the amount of equity\nthe NFT represents.  As a third example, an NFT can represent user-generated\ndata, such as artworks, songs, movies, novels, and so on, whereby the act of\nproducing and selling NFTs that represent this data is the act of funding the\nuser's creative process.\n\nThe act of storing the NFT's binding on a blockchain is the act of empowering\nindividuals across the world to seamlessly treat their data as capital assets.\nThis is what differentiates NFTs from similar monetizable principal/data binding\nsystems like domain names and online game items.  The open-access nature of NFTs not only enables any\nuser anywhere to acquire, use, and transfer NFTs, but also produce whole new\nsets of NFTs with as-of-yet-unknown use-cases.\n\n### NFT Production and Valuation\n\nRegardless of how NFTs are used, they are usually produced in the same way:\n\n* They are produced as a collection.  The creator instantiates NFTs in batches,\n  such as to raise funds for a creative endeavor or promote the applications that use them.\n\n* Collections are produced in a series.  An NFT creator may produce multiple\n  batches over time, such as when they produce more data to sell or add more\nfeatures to applications that use them.\n\nThis also applies to NFTs that are programmatically generated by a smart\ncontract: depending on the mechanism, the NFTs minted this way can be treated as\na single batch of NFTs minted over a long time period, or a series of 1-item\nNFTs minted over the same time period.\n\nOnce an NFT is produced, it can be traded via the blockchain to other principals\nfor other crypto assets.  This enables a market for NFTs from a particular\ncollection or series to form, which in turn determines the spot price for an NFT\nin a particular collection in a particular series over time.\n\n### Problems with the State-of-the-Art\n\nThe state-of-the-art way to sell a collection of NFTs is for the NFT creator -- be it a\nperson or smart contract -- to instantiate the bindings for each NFT in one of\ntwo ways: all up-front, or upon request from a buyer.\nOnce instantiated, they can be transferred to other users at the creator's whim,\nsuch as by selling them.  While straightforward, this introduces several problems:\n\n* It can clog the blockchain.  If the NFT creator instantiates an NFT before\n  there is a buyer, it needlessly consumes blockchain compute capacity.\n\n* It costs the creator tokens.  In both approaches, the NFT\ncreator ends up paying for the transaction fee in the underlying blockchain's\ntokens.  While the creator can price the transaction fee into the sale cost, the\ncurrent sale mechanism creates a recurring token cost that the seller must be\nwilling to pay.\n\n* It creates a moral hazard for the NFT creator.  The collection creator initially\n  owns all NFTs, and effectively controls the market for them.  The NFT creator\ncould devalue their collection by dumping their holdings.  Also, because the NFT\ncreator receives payment for their sold NFTs up-front, they are in a position to\nexit-scam their users:  they could promise to use the raised funds to build out an\napplication for the NFTs, and then just take the money and leave.\n\n* It underutilizes and under-rewards the user community.  Selling NFTs\nto would-be users this way only encourages them flip NFTs, instead of trying\nto make the project itself better.  This is unsustainable, because\nnothing appreciates forever.  If the NFT is sold as a way to raise funds to\nbuild out some good or service that the NFT will eventually be used to access,\nthen this introduces a window of time where users would rather buy and flip NFTs\ninstead of commit to the project long-term (distorting the market and creating a\nmoral hazard for the NFT creators).\n\nThese problems are addressed by NFTrees.\n\n## What is an NFTree?\n\nAn NFTree is an NFT that represents an arbitrarily-large collection of other NFTs and\nNFTrees, while behaving as a single NFT on the blockchain.  A user instantiates an NFT\noff of an NFTree by submitting a proof that the NFT is represented in the NFTree,\nwhich then instantiates that NFT as a separate binding owned by that user.  Once\n\"plucked\" from the NFTree, the NFT cannot be instantiated again -- it is\nexclusively owned by that user.\n\nAn NFT creator would mint a single NFTree for each collection they make.  An NFT\nbuyer would pay to instantiate an NFT off of the NFTree.  This way, NFTs are\nonly instantiated once there is demand for them, which prevents the blockchain\nfrom getting clogged.\n\nTwo of the problems with the state-of-the-art way of minting NFTs have to\ndo with creating the right incentives for a successful NFT project.  NFTrees\naddress these two problems by introducing a _mining_ protocol, modeled after how\nan arcade makes money.\n\n### The NFT Arcade\n\nThe astute reader will have deduced that an NFTree is probably a Merkle tree of\nNFTs (and they would be right), and might be wondering how the NFT pricing\nmechanism works.  The answer to this is that the NFTree commits to not only the\nNFTs, but also a number of _tickets_ that they are worth.\n\nAn NFTree smart contract contains an internal \"tickets\" fungible token that\nusers must first acquire and then burn in order to instantiate the NFT.  This is\nanalogous to how an arcade works: in order to win prizes (the NFTs) whose value\nis denominated in tickets, the user must pay to play arcade games that produce\ntickets.  When they have enough tickets, they bring them to the prize counter\nand exchange them for the prize.\n\nWith NFTrees, users earn tickets by participating in a proof-of-transfer-lite\n([PoX-lite](https://github.com/jcnelson/poxl)) protocol to mine tickets, much like how\nthe [CityCoins](https://github.com/citycoins) project works.  They commit STX to\nthe NFTree contract, and in each Stacks block, they win a fixed amount of\ntickets with probability proportional to how much STX they committed relative to\neveryone else.  Once the user has earned enough tickets this way (or bought some\nfrom someone else), they can mint an NFT off of the NFTree.\n\nThe reason for introducing tickets for buying NFTs is that it gives the NFT\ncreator a way to specify the _relative_ worth of NFTs without specifying an\nabsolute price.  The ticket mining protocol ensures that the tickets/STX ratio\nis known at all Stacks blocks; from there, the price of each NFT in STX can be\ncalculated.  This partially removes the NFT creator's moral hazard: the NFT\ncreator does not control the initial valuation of the NFTs; the *users* do.\nMoreover, that valuation changes over time based on *user* mining activity.\nAdditionally, if the NFT creator wants to own any of the NFTs they produce via\nthe NFTree, they must participate in ticket-mining as well.\n\nTo be clear, not all users need to participate in mining tickets.  They can just\nbuy them from someone who does.  But even then, as will be explained below, the\nNFTree smart contract includes a set of marketplace functions that let users\nsubmit buy-offers for NFTs in STX (including ones that have not yet been plucked off of\nthe NFTree), which can be fulfilled by other users who do have the requisite\ntickets.\n\n### Stacking NFTs\n\nThe other reason for pricing NFTs in tickets is that it introduces a way for\nNFTs to generate _passive income_ for the users that hold them, but do not sell\nthem.  Like the STX token, an NFT in an NFTree can be stacked -- it can be\nlocked up so that it does not resolve on-chain and cannot be transferred for a\ntime, but during this time, the user receives a fraction of the STX spent by\nticket miners proportional to the ticket-denominated value of the NFT.  For\nexample, if there are 90 tickets-worth of NFTs stacked, and Alice stacks an NFT\nworth 10 tickets, then she would receive 10% of the STX paid into the smart\ncontract for mining tickets over the duration of her NFT lock-up period.\n\nThe amount of STX users would receive depends on not only the ticket-denominated\nvaluation of their NFTs, but also how much STX other users spend mining tickets.\nSince producing tickets is the act of mining them, the amount of STX flowing\ninto the NFTree contract would equal the value of the tickets produced.  Since\nthe worth of the tickets is underpinned by the worth of the NFTs, the amount of\nSTX a stacking user can expect to receive is determined by the current\nSTX-denominated valuation of the NFTs yet to be claimed.  In other words, users\nwho stack NFTs are incentivized to expand the market for NFTs in the NFTree --\nboth by growing the userbase and making the NFTs more valuable -- since this is\nwhat earns them the most passive income.\n\nMaking it possible to stack NFTs addresses the third problem in the\nstate-of-the-art: by giving the user a choice between using the NFT, selling it,\nor _stacking_ it and earing passive income, the user has more of a reason to\nhold onto an NFT even if they can't use it for its intended purpose yet.  If they \nstack it, they don't need to engage in a speculative market for buying/selling\nNFTs; they just need to get more people involved in the NFT project.  This is\nultimately achieved by making the NFT useful, which aligns the user's behavior\nwith the long-term success of the NFT project.\n\nThis is not to say that speculation will not happen (it will); this is to say that\nNFTrees give users an alternative that offers positive cash-flow over time while\nthe NFT project is being built out.  This invests users in the long-term success\nof the NFT project.\n\nAs a nice side-effect of stacking, the market price of an NFT would include its\ndiscounted cash-flow on top of whatever fundamental value the NFT project\noffers.  NFTs created from NFTrees would only become worthless if no one mines\ntickets for them.\n\n## User Roles\n\nThere are five user rules in an NFTree project:\n\n* Creators, who mint whole collections of NFTs as NFTrees.  They receive a\n  commission each time an NFT is minted off of an NFTree that they created.\n\n* Ticket miners, who send STX to the NFTree contract to mine NFTree tickets\n  which will be burnt to instantiate NFTs.  Ticket miners can claim the NFTs for\nthemselves, they can sell their tickets to other users, and they can atomically\ninstantiate and then sell an NFT for STX.\n\n* NFT buyers, who spend STX to acquire NFTs by submitting buy orders to the\n  contract.  The contract holds their STX in escrow for the duration of the buy\norder's lifetime, which in turn provides a global insight into how much demand\n(in STX) there is for NFTs in the contract's NFTrees.\n\n* NFT sellers, who fulfill buy orders by either selling NFTs they own, or\n  burning tickets to mint uninstantiated NFTs for buyers.  In the latter case, a\ncommission fee will be claimed by the NFTree creator for the sold NFT.\n\n* NFT stackers, who lock up their NFTs to accumulate a STX yield from ticket\n  mining.  The act of locking the NFT renders it unsellable, and unresolvable\nvia other smart contracts.\n\nEach type of user has a different set of APIs to use to carry out their roles.\n\n# How it Works\n\nThere are two parts to this project: the NFTree smart contract, and the\n`nftree.js` command-line tool for making NFTrees.  The smart contract is a\nproof-of-concept, and should be tailored to your project needs.\n\n## NFT Descriptors\n\nBecause NFTrees commit to both the NFT data and tickets, they are represented in\nthe contract as a `(buff 64)`, which encodes three pieces of data:\n\n* The NFT SHA512/256 hash\n* The NFT's number of tickets\n* The NFT data's size\n\nThe last field is included as an anti-DDoS measure for Gaia hubs and other data\nhosts that hold onto the NFT data, so they'll know how big each NFT is before\ntrying to store it.\n\nThe encoding is as follows:\n\n```\n|--hash (32 bytes)--|--size (16 bytes)--|--tickets (16 bytes)--|\n```\n\nThe `size` and `tickets` fields are big-endian.\n\n## NFTree Construction\n\nBuilding an NFTree is the act of building an NFT descriptor that commits to\nall of the NFTs in the collection.  This is achieved by building a Merkle tree\nout of the NFTs, and making the `hash` field of the NFTree's NFT descriptor the\nMerkle root.\n\nBuilding the Merkle tree, the Merkle proofs, and NFT descriptors from a set of\nNFT data is handled by the `nftree.js` program.\n\n```\n$ cd ./src\n$ ./nftree.js build /path/to/your/NFTs /path/to/NFTree/output\n```\n\n* The `/path/to/your/NFTs` argument is a directory with all of your NFTs as\n  files.  In addition, there must be a `tickets.csv` file that has two columns:\n`name` and `tickets`.  The `name` column is the file name, and `tickets` is the\nnumber of tickets the NFT is worth.\n\n* The `/path/to/NFTree/output` argument is a directory into which the NFTs will\n  be copied, and into which NFT descriptors and NFT Merkle proofs will be\nwritten.  Each NFT will be named after its SHA512/256 hash; each NFT descriptor\nwill be named after its hash and a `.desc` suffix; each Merkle proof will be\nnamed after its hash and a `.proof` suffix.\n\nThe `/path/to/your/NFTs` argument can contain nested subdirectories.  Each\nsubdirectory represents NFTrees within the NFTree, and must have its own\n`tickets.csv` file for its files.  An NFT descriptor will be created for each\nsubdirectory NFTree, such that once the NFTree is minted, the inner NFTs can\nthen be minted.\n\nThis program prints out the hex-encoded NFT descriptor for the NFTree as a JSON\nstring.\n\nFor example:\n\n```\n$ ./nftree.js build /tmp/nftree-input/ /tmp/nftree-test\n\"8a480f3f87b03dc1d6b8270cd65fc0e77f7640d49f97d4cd3b789c9de2d280080000000000000000000000000000001f00000000000000000000000000000684\"\n```\n\nIn more detail:\n\n```\n$ find /tmp/nftree-input/\n/tmp/nftree-input/\n/tmp/nftree-input/foo\n/tmp/nftree-input/foo/foo\n/tmp/nftree-input/foo/tickets.csv\n/tmp/nftree-input/foo/bar\n/tmp/nftree-input/tickets.csv\n/tmp/nftree-input/hello\n/tmp/nftree-input/snarf\n/tmp/nftree-input/boop\n$\n$ cat /tmp/nftree-input/tickets.csv\nname,tickets\nhello,123\nboop,456\nsnarf,789\n$\n$ cat /tmp/nftree-input/foo/tickets.csv\nname,tickets\nfoo,100\nbar,200\n$\n$ ./nftree.js build /tmp/nftree-input/ /tmp/nftree-test\n\"8a480f3f87b03dc1d6b8270cd65fc0e77f7640d49f97d4cd3b789c9de2d280080000000000000000000000000000001f00000000000000000000000000000684\"\n$\n$ find /tmp/nftree-test\n/tmp/nftree-test/\n/tmp/nftree-test/6ff3e7040fc45301764d3b5be9a01814a64f756545d869f4a44d9689e854bf1f.proof\n/tmp/nftree-test/23000de7d22826a683fac628c926427ddd986913dba5332d6227085e746b577f.proof\n/tmp/nftree-test/04c2f43e067d637611958ae92a54e90848dedeb5908bb3d3363cc57c573332b4\n/tmp/nftree-test/23000de7d22826a683fac628c926427ddd986913dba5332d6227085e746b577f.desc\n/tmp/nftree-test/d3dbb6686010b4b74742c52dfa8564f2e1501219568452a01b5a69d295d7d8c2.proof\n/tmp/nftree-test/8a480f3f87b03dc1d6b8270cd65fc0e77f7640d49f97d4cd3b789c9de2d28008.desc\n/tmp/nftree-test/332bc3d727592cdc62f40526cc2476d2627441656352b33da1eb53556c69cd17\n/tmp/nftree-test/d3dbb6686010b4b74742c52dfa8564f2e1501219568452a01b5a69d295d7d8c2\n/tmp/nftree-test/d3dbb6686010b4b74742c52dfa8564f2e1501219568452a01b5a69d295d7d8c2.desc\n/tmp/nftree-test/6ff3e7040fc45301764d3b5be9a01814a64f756545d869f4a44d9689e854bf1f\n/tmp/nftree-test/root\n/tmp/nftree-test/bf73ee1fb7e8bf8fcdd5da06dd547052cd0f929a88f4aead68b218d3bde91134.proof\n/tmp/nftree-test/6ff3e7040fc45301764d3b5be9a01814a64f756545d869f4a44d9689e854bf1f.desc\n/tmp/nftree-test/332bc3d727592cdc62f40526cc2476d2627441656352b33da1eb53556c69cd17.proof\n/tmp/nftree-test/bf73ee1fb7e8bf8fcdd5da06dd547052cd0f929a88f4aead68b218d3bde91134.desc\n/tmp/nftree-test/04c2f43e067d637611958ae92a54e90848dedeb5908bb3d3363cc57c573332b4.proof\n/tmp/nftree-test/bf73ee1fb7e8bf8fcdd5da06dd547052cd0f929a88f4aead68b218d3bde91134\n/tmp/nftree-test/04c2f43e067d637611958ae92a54e90848dedeb5908bb3d3363cc57c573332b4.desc\n/tmp/nftree-test/332bc3d727592cdc62f40526cc2476d2627441656352b33da1eb53556c69cd17.desc\n```\n\nThe NFTree creator needs to upload the contents of `/path/to/NFTree/output` to a\nWeb server, so they'll be available for viewing.  The URL to each NFT will be\nconstructed by the NFTree smart contract by appending the SHA512/256 hash of the\nNFT to a knwon URL prefix.  So for example, if the NFTs are going to be\navailable at `https://nftree-example.com/nft-data`, the NFT creator would put\nthe contents of this directory onto the Web server such that\n`https://nftree-example.com/nft-data/6ff3e7040fc45301764d3b5be9a01814a64f756545d869f4a44d9689e854bf1f`\nresolved to the file `6ff3e7040fc45301764d3b5be9a01814a64f756545d869f4a44d9689e854bf1f`.\nThe NFTree project creator can change the URL prefix by setting the\n`NFT_URL_PREFIX` constant in the `nftree.clar` smart contract.\n\nOnce the NFT creator has the outputted NFT descriptor, then can call one of the\ntop-level functions to instantiate it on-chain -- `(register-nftree)` and\n`(register-contract-nftree)`:\n\n```\n(define-public (register-nftree (nft-rec { tickets: uint, data-hash: (buff 32), size: uint }))\n(define-public (register-contract-nftree (nft-rec { tickets: uint, data-hash: (buff 32), size: uint }))\n```\n\nThe only difference between them is that the contract will own the root NFTree\nin the latter case, so no commission will be paid out for the root.\n\nBoth of these functions are used to mint the whole collection in `/path/to/your/NFTs`.  The NFT project leader(s)\nwould call this to instantiate new collections are they are made.  All NFTs\nrepresented in `/path/to/your/NFTs` can then be instantiated by this smart\ncontract by interested users; they'd use the corresponding `.proof` and `.desc`\nfiles to construct the relevant contract-calls.\n\nThese functions return an integer NFT ID, which must be passed into functions\nrelated to instantiating NFTs off of the NFTree (this argument is called\n`parent-nft-id`).\n\n**WARNING**: If you make an NFT project, you will want to update these functions\nto limit who can produce NFTrees in your project this way.  Usually, this will\nonly be a designated admin, but any authentication rules you can write in\nClarity are permitted.  For example, you could have a DAO contract decide who\ncan call this function.\n\n## Ticket Mining\n\nTo mine NFTree tickets, a miner can either commit STX in a single block, or over a\nrange of blocks.  The functions to do so are:\n\n```\n(define-public (mine-tickets (amount-ustx uint))\n```\n\nand \n\n```\n(define-public (mine-tickets-multi (amount-ustx-per-block uint) (num-blocks uint))\n```\n\nThere will be one ticket winner per Stacks block, and the number of tickets\ngranted to the winner is fixed regardless of how many or few STX are committed.\n\nThe winning ticket miner will not be known for 100 blocks, to ensure that the\ncommitments are sufficiently confirmed.  At or after the 101st subsequent block,\nthe winner can claim their tickets by calling `(claim-tickets)`:\n\n```\n(define-public (claim-tickets (miner principal) (blk uint))\n```\n\nHere, `blk` is the Stacks block height at which the winner mined.\nMiners can check if they were the winner in a block with `(check-winner)`:\n\n```\n(define-read-only (check-winner (miner principal) (blk uint))\n```\n\n## Buying an NFT\n\nUsers are not required to acquire and burn tickets in order to buy NFTs; it's\nonly necessary that _someone_ does this.  Instead, users can simply offer STX\nfor an NFT in the NFTree.  Importantly, the user can offer STX for an NFT that\nis not yet instantiated, but is represented by the NFTree.\n\nTo submit a buy offer for a particular NFT, the user calls the `(submit-buy-offer)` function:\n\n```\n(define-public (submit-buy-offer (nft-desc (buff 64)) (amount-ustx uint) (expires uint))\n```\n\n* The `nft-desc` is the NFT descriptor for the NFT to buy\n\n* The `amount-ustx` argument is the number of uSTX this buyer is willing to\n  spend to get it.\n\n* The `expires` argument is a future Stacks block height at which the buy offer\n  expires.\n\nWhen the user submits the buy offer, the contract escrows their STX so that the\nNFT owner can later sell them the NFT.  If the buy offer expires, then the buyer\ncan call `(reclaim-buy-offer)` to get their STX back:\n\n```\n(define-public (reclaim-buy-offer (nft-desc (buff 64)))\n```\n\nThey would pass the same `nft-desc` as they did when the called\n`(submit-buy-offer)`.\n\nA user can out-bid an existing buy offer by calling `(submit-buy-offer)` with a\nhigher `amount-ustx` offer for the same `nft-desc`.  If so, then the previous\nbuyer's STX are returned to them and the new buyer's buy offer replaces it.\n\n### Fulfilling a Buy Offer\n\nThe NFT in a buy offer does not need to exist in order to be fulfilled.  If the\nNFT already exists, then the NFT owner can sell the NFT by calling\n`(fulfill-buy-offer)`:\n\n```\n(define-public (fulfill-buy-offer (nft-desc (buff 64)))\n```\n\nThey would pass the NFT descriptor for their NFT as the sole argument.  If the\ncall succeeds, then ownership of the NFT transfers to the buyer in the buy offer\nfor this NFT, and the seller gets the STX escrowed by the smart contract.\n\nIf the NFT does not exist, then someone with tickets can fulfill the buy offer\nby instantiating the NFT and then selling it to the buyer for the STX in one go via\nthe `(fulfill-mine-order)` function:\n\n```\n(define-public (fulfill-mine-order\n                    (nft-desc (buff 64))\n                    (parent-nft-id uint)\n                    (proof { hashes: (list 32 (buff 32)), index: uint })\n               )\n```\n\n* The `nft-desc` argument is the NFT descriptor for the NFT to be instantiated\n  and sold.  It is stored in a `.desc` file created by `./nftree.js build`.\n\n* The `parent-nft-id` argument is the NFT identifier of the NFTree that contains\n  this NFT.\n\n* The `proof` argument is a Merkle proof that links the `nft-desc` to the NFTree\n  identified by `parent-nft-id`.  Its JSON representation is created via a\n`./nftree.js build` invocation, and is stored in a `.proof` file.\n\nIf this function succeeds, the caller's tickets are burnt, the NFT is\ninstantiated and then transferred to the buyer, and the caller gets the offered STX.\nThe function returns the NFT ID for the instantiated NFT.\n\n## Stacking an NFT\n\nOnce the user owns an NFT, they can stack it via the `(stack-nft)` function:\n\n```\n(define-public (stack-nft (nft-id uint) (num-cycs uint))\n```\n\n* The `nft-id` argument is the numeric NFT identifier, which is returned once the NFT\n  is instantiated.\n\n* The `num-cycs` argument is the number of reward cycles for which the NFT is\n  stacked.\n\nNFTs are stacked in fixed-length reward cycles, much like how PoX works in the\nStacks blockchain.  While the NFT is stacked, it cannot be transferred and it\nwill not resolve via the SIP 009 interface.  However, the owner of the NFT will\nreceive a portion of the STX spent on mining tickets while it is locked up,\nproportional to the number of tickets the NFT is worth.\n\nIf an NFT is stacked, and the NFT is really an NFTree, then buy orders that\ninstantiate NFTs off of it will fail.\n\nTo get the stacking rewards after the NFT unlocks, the user would call\n`(claim-stacking-rewards)`:\n\n```\n(define-public (claim-stacking-rewards (start-cyc uint) (num-cycs uint))\n```\n\n* `start-cyc` is the first reward cycle in which the NFT was stacked\n\n* `num-cycs` is the number of cycles in which the NFT was stacked\n\nTo determine what the `start-cyc` is, the user can call the function\n`(blk-to-cyc)` to convert the Stacks block height at which they stacked their\nNFT into the reward cycle in which it resides.  The `start-cyc` value is the\n_next_ reward cycle -- as with STX in PoX, the start reward cycle is the next\nwhole reward cycle at the point in time when the NFT gets stacked.\n\n## Nested NFTrees\n\nEach NFTree can represent up to about 4.1 billion (2^32 - 1) NFTs.  If for some\nreason this isn't enough, an NFT in an NFTree can be another NFTree.  In this\ncase, the NFT hash field of its NFT descriptor is the Merkle root of the NFTs it\nrepresents.\n\nIf someone instantiates an NFT that happens to be an NFTree, anyone can then\nproceed to instantiate the NFTs it represents by passing the NFTree's\n`parent-nft-id` value to whatever function is doing the instantiation.\n\nNested NFTrees are an advanced feature.  They are meant to enable use-cases such\nas, but not limited to, the following:\n\n* Selling a collection of NFTs to a single buyer in one go.\n\n* Blocking the release of subsequent NFTs until enough tickets have been mined.\n\n* Implementing a semi-fungible token.\n\nIn all cases, the _owner_ of the NFTree receives the commission fees for minting\nNFTs off of it.  What this means is that if Alice registers an NFTree that\ncontains another NFTree, and Bob buys the inner NFTree, then the commission \nfees instantiated off of Bob's NFTree will go to Bob, not Alice.\n\n## Standards Conformance\n\nThe proof-of-concept `nftree.clar` contract implements SIP 009 faithfully, and\nimplements all but the `(transfer)` function of SIP 010 for tickets.  The reason for this\nomission is because both SIPs have a `(transfer)` function.  In place of a\n`(transfer)` function for tickets, this contract offers `(transfer-tickets)`\nwith the same semantics.\n\nThe URL of an NFT is calculated by appending the hash of the NFT from its NFT\ndescriptor to a URL prefix.\n\n# Development\n## Running tests\n\nYou will need `clarity-cli` in your `$PATH`.  You can get it from\nhttps://github.com/blockstack/stacks-blockchain.\n\n```bash\n$ cd ./contracts/tests \u0026\u0026 ./run-tests.sh\n```\n\n## Contributing\n\nThis repository is meant to be a proof-of-concept.  In keeping with the Stacks\nFoundation ethos, this project is meant to set up other Stacks ecosystem\nparticipants to succeed.  I will not be monetizing or productizing it for as\nlong as I work for the Stacks Foundation.\n\nAs such, I encourage you to fork this repo and make your own changes instead of\ntrying to send PRs.\n\n# Business Models\n\nHere are a few ways you can use this contract.  A few modifications may be\nnecessary, depending on the application, but they are straight-forward.\n\n## Content Mining\n\nImagine an application that shows high-quality content on some topic.  NFTree\ncreators act as journalists -- they gather and produce a firehose of content, and every so\noften, they register an NFTree with their new content.\n\n   * **Small tweak**:  The contract will need to charge NFTree creators a\n     registration fee, in NFTree tickets, for registering a new NFTree.  The fee\nshould be dynamic -- it should increase if there are more NFTrees being created,\nand decrease if there are less.\n\nNFTree creators are compensated via commission fees whenever someone buys an\nNFT off of their NFTrees.  Not all content they produce will be purchased, so\nthey are incentivized to only produce content that will likely be bought (i.e.\ncontent that is on-topic).\n\nA piece of content only shows up in the app if its NFT is actually purchased.\nThe NFT buyers act as content moderators.  The set of NFTs is visible to everyone,\nbut it's a firehose of data -- buyers act as curators for the data, and in \ndoing so provide a good experience for the app's users.  Buyers are\ncompensated by stacking their NFTs.\n\n   * **Small tweak**:  The contract will need to be altered so that an NFT can\n     only be stacked once, and within a fixed window of time after purchase.  This is to ensure that there won't be free-riders\nwho stack or accumulate lots of NFTs but don't provide any useful service in exchange for the\nincome they'd receive.\n\nMiners make money by mining tickets which are then sold to NFTree creators to\npay their registration fees.  In addition, they make money when they fulfill a\nmine order -- they receive the non-commission portion of the buyer's STX.\n\n### Why It Works\n\nEveryone makes more money if more users are drawn into using the app.  Users are\ndrawn to the app by more and better content, which creates demand for both content\ncreators and content curators (buyers), which in turn creates demand for miners.\n\nA subset of users are incentivized to curate high-quality content, since if they draw more \nusers in, they can recoup their purchase price by stacking the NFT later.  Because they\ncan only stack it once, and because the NFT can expire,\nthey are incentivized to increase the amount of STX flowing into the contract\nbefore they stack and do it quickly.  This incentivizes curators to grow\ndemand for miner tickets, which is driven both by curation and by content\ncreation.\n\nSome users are also incentivized to become content creators, because high-quality\ncontent earns them a commission fee.  The registration fee they pay in NFTree\ntickets drives demand for tickets, and thus income to NFT stackers.  As long as\nthe registration fee is lower than the expected NFT purchase price, creating\nhigh-quality content will be profitable.  By making it so that the registration\nfee grows or shrinks with NFTree creation rates, there will always be a time\nwhen the registration fee will be low enough for a user to participate as long\nas the expected NFT purchase exceeds the blockchain transaction fee.\n\nThe system reaches a steady state when the profit margin for selling tickets\nequals the total cost for mining them (transaction fee plus STX committed), when\nthe profit margin for stacking an NFT equals the cost of buying it (including\ntransaction fees), and when the profit margin for creating and selling NFTs via\nan NFTree equals the ticket fee and transaction fee for registering them.\n\n* If creating NFTrees and selling NFTs becomes unprofitable, the registration fee will decrease\n  until it becomes break-even or profitable again.\n\n* If buying and stacking NFTs becomes unprofitable, fewer buyers will stack\n  them, causing stacking yields to increase, and thereby encouraging more\nstacking.\n\n* If mining tickets becomes unprofitable, fewer miners will participate, meaning\n  that it will take fewer STX to win the same amount of tickets.  As long as the\nexpected ticket reward meets or exceeds the underlying blockchain transaction\nfee, there will always be at least one miner producing tickets for NFTree\ncreators and buyers to purchase. \n\n## Paid Subscriptions That Become Free once Popular\n\nImagine an application that acts like Medium or Substack, in which only free\narticles are available to the public, but subscribers can see additional content\nfor a fee.  NFTrees can implement a variant of this whereby a piece of content\nbecomes public if enough people sponsor it.\n\nTo do so, an author breaks their document into a set of N [Shamir\nSecrets](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing), each of which\nis represented as an NFT.  They publish an NFTree out of these shares, but they\ndo not make the shares public.  Once someone buys an NFT from the NFTree, the\nauthor discloses the share to the buyer.  The buyer can then disclose the share\nif they wish.  Once M \u003c= N shares have been bought and dislosed, anyone can\nrecombine the M shares and recover the original content.\n   \n   * **Small tweak**:  The contract will need to be altered so that an NFT's\n     stacking power begins to diminish with age (but never goes to zero).  This\ncauses older NFTs to become less valuable, but it also means that early\nsubscribers still stand to gain more STX than late subscribers.  By causing\nolder NFTs to depreciate, it \"makes room\" for new subscribers to join in the\nsystem (and it also encourages OG subscribers to keep subscribing to remain\ncompetative).\n\n## Why It Works\n\nBuyers are like subscribers here, but they also share in the upside of the\nauthor if the author becomes popular enough to have many subscribers.\nSpecifically, _early_ subscribers gain _more_ upside than late subscribers,\nsince early subscribers can stack their NFTs and earn STX yield from them.  This\naligns subscribers' interests with the author -- subscribers are incentivized to\n_help_ the author find more subscribers.\n\nMiners gain from this because they must mine and sell the tickets to the buyers\nin order to mint the NFTs.\n\nThe author of course gains from having more buyers because the earn a commission\non each Shamir Secret NFT they can sell.\n\n## Anyone-Can-Pay Web Hosting\n\nImagine a Web host that would keep hosting a website's content as long as\n_somebody_ paid the bills.  It doesn't have to be the website creator; in fact,\nthe website creator can just walk away from the website and let its users take\nit over this way.\n\nThis can be enabled with NFTrees as follows.  Every billing cycle, the Web host\ncreates an NFTree out of the hosted content and sets a price for hosting it for\nthe next billing cycle.  All NFTs that are bought off of the NFTree remain\nonline for the next cycle; all that are not bought are removed from storage.\nThe `tickets` field would reflect a meaningful measurement of the relative cost\nto keep the data online.\n\nThe website itself would encourage visitors to buy NFTs periodically, such as by\ngiving flares to sponsoring users or giving them access to extra features or\ncontent.  In the limit, the website could structure itself like shareware -- \nonly people who help pay to keep it going get access to the full features;\neveryone else gets only limited features.\n\nBy making it so that frequent sponsors can stack their NFTs, early\nsponsors can profit by helping make the website more and more popular as there\nis increased demand for NFTs (and tickets).  This in turn crowdsources the task\nof helping increase the website's popularity -- as the website gets more\npopular, there would likely become more content to host, which in turn drives\nmore demand for mining tickets, which in turn increases the yield on stacked\nNFTs.\n\nMiners continue to mine and burn tickets to sell NFTs as before, but with the\nfollowing tweak:\n\n   * **Small tweak**: To ensure that the hosting bill is paid in full, the\n     contract must treat the `tickets` field as the number of STX to pay, not\ntickets.  To ensure this, ticket mining must be tweaked so that the number of\ntickets minted is _equal to_ the number of STX put into the contract, instead of\nfixed.\n\nThis tweak gives users the ability to leverage the appreciation of STX to lower\ntheir hosting bill.  If they commit STX at a lower price than it is when the\nhosting bill is paid, then they will have saved money, since the appreciation of\nSTX in the mean time pays for part of the hosting cost.\n\n### Why It Works\n\nThe system works much like how crowdsourcing anything else works -- if enough\npeople pay for a good or service, the service continues.  The key difference is\nthat there does not need to be a point person for gathering the money to pay the\noperating bills.\n\nOnce the size of the userbase reaches its peak will the act of buying and stacking NFTs become\nbreak-even -- the stacking revenue will match the cost of hosting the data, plus any\nmarkup the host applies, plus transaction fees on the underlying blockchain.\nBut as the userbase is growing, stacking is profitable, since there is\nincreasing demand for NFTs.\n\nIf the userbase shrinks, then the web host would delete unpaid-for site data.\nThis in turn prunes the website of content that users no longer want, until\nmaintaining the website becomes break-even with the new userbase size.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcnelson%2Fnftree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjcnelson%2Fnftree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcnelson%2Fnftree/lists"}