diff --git a/public/images/pages/nft/btc-stx.png b/public/images/pages/nft/btc-stx.png new file mode 100644 index 00000000..3a38788d Binary files /dev/null and b/public/images/pages/nft/btc-stx.png differ diff --git a/public/images/pages/nft/contract-interaction.png b/public/images/pages/nft/contract-interaction.png new file mode 100644 index 00000000..f86ea326 Binary files /dev/null and b/public/images/pages/nft/contract-interaction.png differ diff --git a/public/images/pages/nft/contract-name.png b/public/images/pages/nft/contract-name.png new file mode 100644 index 00000000..9f72e9b6 Binary files /dev/null and b/public/images/pages/nft/contract-name.png differ diff --git a/public/images/pages/nft/faucet.png b/public/images/pages/nft/faucet.png new file mode 100644 index 00000000..c7e3d2f9 Binary files /dev/null and b/public/images/pages/nft/faucet.png differ diff --git a/public/images/pages/nft/nft-claimed.png b/public/images/pages/nft/nft-claimed.png new file mode 100644 index 00000000..10a9741c Binary files /dev/null and b/public/images/pages/nft/nft-claimed.png differ diff --git a/public/images/pages/nft/nft-preview.png b/public/images/pages/nft/nft-preview.png new file mode 100644 index 00000000..310a7d65 Binary files /dev/null and b/public/images/pages/nft/nft-preview.png differ diff --git a/public/images/pages/nft/nft.png b/public/images/pages/nft/nft.png new file mode 100644 index 00000000..0116fc1d Binary files /dev/null and b/public/images/pages/nft/nft.png differ diff --git a/public/images/pages/nft/open-tab.png b/public/images/pages/nft/open-tab.png new file mode 100644 index 00000000..82fcd750 Binary files /dev/null and b/public/images/pages/nft/open-tab.png differ diff --git a/public/images/pages/nft/token.png b/public/images/pages/nft/token.png new file mode 100644 index 00000000..4e1c305d Binary files /dev/null and b/public/images/pages/nft/token.png differ diff --git a/public/images/pages/nft/wallet.png b/public/images/pages/nft/wallet.png new file mode 100644 index 00000000..7a8d0812 Binary files /dev/null and b/public/images/pages/nft/wallet.png differ diff --git a/src/common/navigation.yaml b/src/common/navigation.yaml index bedd75a1..f3f4105f 100644 --- a/src/common/navigation.yaml +++ b/src/common/navigation.yaml @@ -43,11 +43,13 @@ sections: - path: /principals - path: /values - path: /clarinet + - path: /tokens sections: - title: Tutorials pages: - path: /hello-world-tutorial - path: /counter-tutorial + - path: /nft - path: /billboard-tutorial - path: /testing-contracts - path: /build-apps diff --git a/src/pages/index.md b/src/pages/index.md index 44aaf8b7..395ed345 100644 --- a/src/pages/index.md +++ b/src/pages/index.md @@ -11,7 +11,7 @@ description: Write Clarity smart contracts, build apps, and starting mining with ## Write smart contracts [@page-reference | grid] -| /write-smart-contracts/overview, /write-smart-contracts/hello-world-tutorial +| /write-smart-contracts/overview, /write-smart-contracts/hello-world-tutorial, /write-smart-contracts/tokens, /write-smart-contracts/nft ## Build apps diff --git a/src/pages/write-smart-contracts/nft.md b/src/pages/write-smart-contracts/nft.md new file mode 100644 index 00000000..3c91b9a1 --- /dev/null +++ b/src/pages/write-smart-contracts/nft.md @@ -0,0 +1,291 @@ +--- +title: NFT tutorial +description: Build your own NFT on Bitcoin +duration: 15 minutes +experience: intermediate +tags: + - tutorial +icon: TestnetIcon +images: + large: /images/pages/nft/nft.png + sm: /images/pages/nft/nft.png +--- + +![What you'll build in this tutorial](/images/pages/nft/nft-preview.png) + +## Introduction + +Non-fungible tokens, or NFTs, are a type of [token](/write-smart-contracts/tokens#non-fungible-tokens-nfts) that can +represent unique data. NFTs are an emerging technology in blockchain, and there are many different potential uses for +them. NFTs have desirable [characteristics](/write-smart-contracts/tokens) like uniqueness, programmability, and +verifiable ownership. Simply put, an NFT is a piece of information that's unique. A common example of an NFT might be a +piece of digital art. + +Clarity offers native support for token creation and management. On top of that, the Stacks ecosystem has adopted a +[standard for NFTs](https://github.com/stacksgov/sips/blob/main/sips/sip-009/sip-009-nft-standard.md). With these two +resources, creating your own NFT on Stacks is easy. + +In this tutorial you will: + +- Create a new Clarinet project +- Add contracts to the project, and set dependencies for those contracts +- Define an NFT contract based on the [SIP-009](https://github.com/stacksgov/sips/blob/main/sips/sip-009/sip-009-nft-standard.md) standard +- Verify the contract using Clarinet +- Optionally, deploy and test the contract on the testnet blockchain + +## Prerequisites + +For this tutorial, you should have a local installation of Clarinet. Refer to [Installing Clarinet](/write-smart-contracts/clarinet#installing-clarinet) +for instructions on how to set up your local environment. You should also have a text editor or IDE to edit the Clarity +smart contracts. + +If you are using Visual Studio Code, you may want to install the [Clarity Visual Studio Code plugin](https://marketplace.visualstudio.com/items?itemName=HiroSystems.clarity-lsp). + +### Optional prerequisites + +While this tutorial primarily focuses on local smart contract development, you may wish to deploy your contract to a +live blockchain. For simplicity, contract deployment is performed using the [testnet sandbox](https://explorer.stacks.co/sandbox/deploy?chain=testnet). +If you wish to complete the optional deployment step, you should have the [Stacks Web Wallet](https://www.hiro.so/wallet/install-web) +installed, and you should request testnet STX tokens from the [testnet faucet](https://explorer.stacks.co/sandbox/faucet?chain=testnet) +on the testnet explorer. Note that requesting testnet STX from the faucet can take up to 15 minuets, so you may wish to +request the tokens before beginning the tutorial. + +![faucet](/images/pages/nft/faucet.png) + +## Step 1: Create a new project + +With [Clarinet installed locally](/write-smart-contracts/clarinet#installing-clarinet), open a terminal window and +create a new Clarinet project with the command: + +```sh +clarinet new clarity-nft && cd clarity-nft +``` + +This command creates a new directory for your smart contract project, populated with boilerplate configuration and +testing files. Creating a new project only creates the Clarinet configuration, in the next stel you can add contracts +to the project. + +## Step 2: Add contracts to the project + +Because NFTs rely on the traits defined in [SIP-009](https://github.com/stacksgov/sips/blob/main/sips/sip-009/sip-009-nft-standard.md), +the project should have two contracts: one that defines the traits, and the other to define your specific NFT. The NFT +contract is dependent on the contract that defines the traits. + +From the `clarity-nft` directory, create two new Clarity contracts with the commands: + +```sh +clarinet contract new nft-trait; clarinet contract new my-nft +``` + +These commands add four new files: a `nft-trait.clar` and `my-nft.clar` file in the `contracts` director, and +corresponding test files in the `tests` directory. + +Remove the `nft-trait_test.ts` file from the `tests` directory, as it's not necessary. + +```sh +rm tests/nft-trait_test.ts +``` + +-> Remember that at any point in this tutorial, you can run `clarinet check` to check the validity of your contract. + +## Step 3: Configure dependencies and define traits + +The NFT standard, [SIP-009](https://github.com/stacksgov/sips/blob/main/sips/sip-009/sip-009-nft-standard.md), defines +a set of standard traits that a compliant contract must implement. This is useful to ensure that different tokens are +able to be supported by Stacks wallets without additional development on the wallet. On the live blockchain, a contract +can declare that it conforms to a specific set of traits with the [`impl-trait`](/references/language-functions#impl-trait) +Clarity function. When a contract uses `impl-trait` to assert compliance with a set of standard traits, the contract can +fail deployment to the blockchain if it violates the trait specification. + +In the local Clarinet REPL, you must specify the contract dependency in the configuration files. Open `Clarinet.toml` +and edit the `contracts.my-nft` heading to declare the dependency on the `nft-trait` contract. + +```toml +[contracts.my-own-nft] +path = "contracts/my-own-nft.clar" +depends_on = ["nft-trait"] +``` + +Update the `nft-trait.clar` contract to define the required traits for [SIP-009](https://github.com/stacksgov/sips/blob/main/sips/sip-009/sip-009-nft-standard.md#trait). +You can paste the contract from this page, or from [Friedger's repository](https://github.com/friedger/clarity-smart-contracts/blob/master/contracts/sips/nft-trait.clar). + +```clarity +(define-trait nft-trait + ( + ;; Last token ID, limited to uint range + (get-last-token-id () (response uint uint)) + + ;; URI for metadata associated with the token + (get-token-uri (uint) (response (optional (string-ascii 256)) uint)) + + ;; Owner of a given token identifier + (get-owner (uint) (response (optional principal) uint)) + + ;; Transfer from the sender to a new principal + (transfer (uint principal principal) (response bool uint)) + ) +) +``` + +## Step 4: Define your personal NFT + +For this tutorial, you'll define an NFT contract for the Stacks testnet. Open the `my-nft.clar` file and copy the +following code into the file. + +```clarity +;; use the SIP090 interface (testnet) +(impl-trait 'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.nft-trait.nft-trait) + +;; define a new NFT. Make sure to replace MY-OWN-NFT +(define-non-fungible-token MY-OWN-NFT uint) + +;; Store the last issues token ID +(define-data-var last-id uint u0) + +;; Claim a new NFT +(define-public (claim) + (mint tx-sender)) + +;; SIP009: Transfer token to a specified principal +(define-public (transfer (token-id uint) (sender principal) (recipient principal)) + (if (and + (is-eq tx-sender sender)) + ;; Make sure to replace MY-OWN-NFT + (match (nft-transfer? MY-OWN-NFT token-id sender recipient) + success (ok success) + error (err error)) + (err u500))) + +;; SIP009: Get the owner of the specified token ID +(define-read-only (get-owner (token-id uint)) + ;; Make sure to replace MY-OWN-NFT + (ok (nft-get-owner? MY-OWN-NFT token-id))) + +;; SIP009: Get the last token ID +(define-read-only (get-last-token-id) + (ok (var-get last-id))) + +;; SIP009: Get the token URI. You can set it to any other URI +(define-read-only (get-token-uri (token-id uint)) + (ok (some "https://docs.stacks.co"))) + +;; Internal - Mint new NFT +(define-private (mint (new-owner principal)) + (let ((next-id (+ u1 (var-get last-id)))) + ;; Make sure to replace MY-OWN-NFT + (match (nft-mint? MY-OWN-NFT next-id new-owner) + success + (begin + (var-set last-id next-id) + (ok true)) + error (err error)))) +``` + +Continue editing the file, making sure that you replace the `MY-OWN-NFT` string in the contract with your own string. + +When you have finished editing the file, run `clarinet check` in the terminal to check that your Clarity code is valid. + +## Step 5: Review contracts and methods in the console + +If the Clarity code is valid, you can run `clarinet console` in the terminal to interact with the contract. + +``` +Contracts ++-----------------------------------------------------+---------------------------------+ +| Contract identifier | Public functions | ++-----------------------------------------------------+---------------------------------+ +| ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.my-nft | (claim) | +| | (get-last-token-id) | +| | (get-owner (token-id uint)) | +| | (get-token-uri (token-id uint)) | +| | (transfer | +| | (token-id uint) | +| | (sender principal) | +| | (recipient principal)) | ++-----------------------------------------------------+---------------------------------+ +| ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.nft-trait | | ++-----------------------------------------------------+---------------------------------+ +``` + +Try claiming the NFT by running the command `(contract-call? .my-nft claim)`. You should receive console output similar +to the following: + +``` +>> (contract-call? .my-nft claim) +Events emitted +{"type":"nft_mint_event","nft_mint_event":{"asset_identifier":"ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.my-nft::MY-OWN-NFT","recipient":"ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE","value":"u1"}} +(ok true) +``` + +## Step 6: Add tests + +At this point, the contract functions as intended, and can be deployed to the blockchain. However, it is good practice +to write automated testing to ensure that the contract functions always perform in the expected way. When adding +complexity or changing the contract, having pre-written, working tests can help you verify that changes you make don't +alter the way that contract functions behave. + +Open the `tests/my-nft_test.ts` file in your IDE. In this step, you will add a single automated test to verify the +`get-last-token-id` and `get-token-uri` functions of the contract. + +The test uses the `chain.mineBlock()` function to simulate the mining of a block. Within that simulated block, the test +makes 2 contract calls (`Tx.contractCall()`), one each to each of the contract functions under test. + +Once the simulated block is mined, the test can make assertions about the return values of the functions under test. The +test checks that 2 contract calls were made in the block, and that exactly one block was mined. The test then asserts +that the return values of each contract call were `ok`, and that the value wrapped in the `ok` is the expected value. + +Replace the contents of the `tests/my-nft_test.ts` file with the following code: + +```ts +import { Clarinet, Tx, Chain, Account, types } from 'https://deno.land/x/clarinet@v0.12.0/index.ts'; +import { assertEquals } from 'https://deno.land/std@0.90.0/testing/asserts.ts'; + +Clarinet.test({ + name: 'Ensure that NFT token URL and ID is as expected', + async fn(chain: Chain, accounts: Map) { + let wallet_1 = accounts.get('wallet_1')!; + let block = chain.mineBlock([ + Tx.contractCall('my-nft', 'get-last-token-id', [], wallet_1.address), + Tx.contractCall('my-nft', 'get-token-uri', [types.uint(1)], wallet_1.address), + ]); + assertEquals(block.receipts.length, 2); + assertEquals(block.height, 2); + block.receipts[0].result.expectOk().expectUint(0); + block.receipts[1].result.expectOk().expectSome().expectAscii('https://docs.stacks.co'); + }, +}); +``` + +Run `clarinet test` in the terminal to review the output of the test. + +=> You have now learned how to work with contract traits, and how to unit test a contract with Clarinet. If you would +like to try deploying your contract to the testnet, proceed with the following optional step. + +## Optional: Deploy the NFT to the testnet + +For this tutorial, you'll use the [testnet sandbox](https://explorer.stacks.co/sandbox/deploy?chain=testnet) to deploy +your smart contract. Make sure you have connected your [Stacks web wallet](https://www.hiro.so/wallet/install-web) to +the sandbox using the **Connect wallet** button, then copy and paste the `my-nft.clar` smart contract into the Clarity +code editor on the [Write & Deploy](https://explorer.stacks.co/sandbox/deploy?chain=testnet) page. Edit the contract name or use the randomly generated name provided to you. + +Click **Deploy** to deploy the contract to the blockchain. This will display the Stacks web wallet window with +information about the transaction. Verify that the transaction looks correct, and the network is set to `Testnet`, and +click **Confirm**. + +The deployment process can take up to 15 minutes to complete. You can review it on the +[transactions](https://explorer.stacks.co/transactions?chain=testnet) page of the explorer, or in the activity field of +your web wallet. + +When your contract is confirmed, navigate to the +[Call a contract](https://explorer.stacks.co/sandbox/contract-call?chain=testnet) page of the sandbox, and search for +your contract. Enter your wallet address in the top field, can you copy this address by clicking the Stacks web wallet +icon and clicking the **Copy address** button. Enter the contract name in the bottom field, in this case `my-nft`. Click +**Get contract** to view the contract. + +Click the `claim` function in the function summary, then click **Call function** to perform the function call in the +sandbox. This will display the Stacks web wallet with information about the transaction. Verify the information, then +click **Confirm** to execute the function call. The function call can take up to 15 minutes to complete. + +When the transaction is complete, you can access the transaction summary page from the activity panel of your web +wallet. The transaction summary page displays the output of the function. You should also see your personal NFT in your +web wallet. diff --git a/src/pages/write-smart-contracts/tokens.md b/src/pages/write-smart-contracts/tokens.md new file mode 100644 index 00000000..9d2172a0 --- /dev/null +++ b/src/pages/write-smart-contracts/tokens.md @@ -0,0 +1,111 @@ +--- +title: Tokens +description: Learn about token support within Clarity +icon: TestnetIcon +images: + large: /images/pages/nft/token.png + sm: /images/pages/nft/token.png +--- + +## Introduction + +A fundamental use of blockchain technology is the representation, store, and transfer of value between users of a +blockchain. Cryptocurrency is a very common use of blockchain technology, and remains one of the primary drivers +of adoption of blockchain technology. Cryptocurrencies are represented by blockchain tokens: individual units of +value within a given blockchain ecosystem. Blockchain tokens can extend beyond just digital currency, however, and +recent developments throughout the cryptocurrency community have demonstrated potential for the use of blockchain to +tokenize and represent not just money but other tangible assets. + +A blockchain token is a digital asset that can be verifiably owned by a user of a blockchain. Blockchain tokens are +governed by a set of rules that are defined by either the blockchain itself (in the case of native tokens) or by a +smart contract on a blockchain. Rules can vary depending on the nature and the use of the token. + +Tokens on a blockchain fall into two general categories, depending on their properties: [fungible][] or +[non-fungible][]. The following sections discuss the properties of both types of tokens, and provide information about +implementation of the two types of tokens on Stacks. + +## Fungible tokens + +A core property of any token on a blockchain is fungibility. A fungible token is a token that's mutually interchangable +or capable of mutual substitution. In other words, one quantity or part of a fungible token can be replaced by an +equal quantity or part of the same fungible token. Fungible tokens are often used to represent real-world fungible +assets like currency. The STX token is an example of a fungible token. Other examples include stablecoins, tokens that +represent voting rights in a DAO, or tokens that algorithmically track the price of stocks. + +Fungible tokens form one of the most important value propositions for blockchain technology, the ability to store value +and exchange that value through both internal and external transactions. Because fungible tokens can be divided into +smaller parts and recombined into the same value representation, they serve a great utility for transferring value +between blockchain users. + +The primary fungible token on the Stacks blockchain is the native token, STX. Because the Stacks blockchain allows for +the creation of [smart contracts][], other fungible tokens can be created on the Stacks blockchain as well. [SIP-010][] +specifies the standard for fungible tokens on the Stacks blockchain. This specification defines the functions and traits +that a fungible token on Stacks _must_ have. By complying with this standard, fungible tokens on Stacks can be easily +represented by wallets that support Stacks. + +### Understanding the fungible token standard + +The [SIP-010][] standard is an interface definition that allows Stacks applications and wallets to interact with +fungible tokens in a standard way. Supporting the standard reduces complexity for token creators to get their tokens +into the ecosystem. Under the [SIP-010][] standard, fungible tokens must have the following characteristics: + +- Ability to transfer a specified amount of the token to a recipient (`transfer`). The recipient is required to be a + Stacks principal. +- Ability to obtain the human-readable name of the token (`get-name`). +- Ability to obtain a short name (ticker symbol) for the token (`get-symbol`). +- Ability to get the number of decimals in the token representation (`get-decimals`). This is used to construct a + representation of the token that humans would be familiar dealing with. For example, the US dollar has 2 decimals, if + the base unit is cents. +- Ability to get the balance of the token for a particular Stacks principal (`get-balance-of`). +- Ability to get the total supply of the token (`get-total-supply`). +- A URI to metadata associated with the token (`get-token-uri`). This can resolve to off-chain metadata about the + token or contract, such as an image icon for the token or a description. + +### Examples of fungible tokens on Stacks + +- [Nothing](https://nothingtoken.com/) ([contract](https://explorer.stacks.co/txid/0x022bed728d648ff1a68036c40f3aff8136ee22fee18380731df0ab9d76d3c4a9?chain=mainnet)) + +## Non-fungible tokens (NFTs) + +Non-fungible tokens (NFTs) are a type of token that are not interchangeable. NFTs have unique traits (usually in the +form of attached metadata) that restrict the abillity to replace them with identical tokens. An NFT is a token that is +unique, such as a piece of art, or ownership rights to a real-world asset such as a house. + +NFTs alone don't have an inherent value, like a currency. The value of an NFT is derived from the assets that the NFT +represents. The use of NFTs are myriad, including digital art, collectibles, domain names, and representation of +ownership of content rights. NFTs can be used as digital certificates that track the authenticty of real world items, or +digitize the ownership rights to property. + +As with fungible tokens, NFTs on the Stacks blockchain are created with [smart contracts][]. [SIP-009][] specifies the +standard for NFTs on the Stacks blockchain. This specification defines the functions and traits that an NFT _must_ have, +but most NFTs have more functions or traits attached than those solely described by the specification. By complying with +this standard, non-fungible tokens on Stacks can be easily represented by wallets that support Stacks. + +### Understanding the non-fungible token standard + +The [SIP-009][] standard is an interface definition that the Stacks ecosystem +aligned on. With support for this standard across wallets and tools, it becomes easy to interact with NFTs. Under the +[SIP-009][] standard, NFT contract must have the following characteristics: + +- Ability to obtain the last token identifier (`get-last-token-id`). This id represents the upper limit of NFTs issued + by the contract. +- A URI to metadata associated with a specific token identifier. (`get-token-uri`). This URI could resolve to a JSON + file with information about the creator, associated media files, descriptions, signatures, and more. +- Ability to verify the owner for a given token identifier (`get-owner`). The owner resolves to a + [Stacks principal](/write-smart-contracts/principals). +- Ability to transfer an NFT to a recipient (`transfer`). The recipient is required to be a Stacks principal. + +### Examples of NFTs on Stacks + +- [This is #1](https://thisisnumberone.com) ([contract](https://explorer.stacks.co/txid/SP3QSAJQ4EA8WXEDSRRKMZZ29NH91VZ6C5X88FGZQ.thisisnumberone-v2?chain=mainnet)) + +## Further reading + +- [The Difference Between Fungible and Non-Fungible Tokens](https://101blockchains.com/fungible-vs-non-fungible-tokens/) +- [Explain It Like I Am 5: NFTs](https://messari.io/article/explain-it-like-i-am-5-nfts) + +[fungible]: #fungible-tokens +[non-fungible]: #non-fungible-tokens-nfts +[smart contracts]: /write-smart-contracts/overview +[sip-010]: https://github.com/hstove/sips/blob/feat/sip-10-ft/sips/sip-010/sip-010-fungible-token-standard.md +[sip-009]: https://github.com/friedger/sips/blob/main/sips/sips/sip-009-nft-standard.md