Browse Source

Merge branch 'feat/nft-onboarding'

friedger-patch-7
Patrick Gray 3 years ago
parent
commit
585b9e8bc0
  1. BIN
      public/images/pages/nft/btc-stx.png
  2. BIN
      public/images/pages/nft/contract-interaction.png
  3. BIN
      public/images/pages/nft/contract-name.png
  4. BIN
      public/images/pages/nft/faucet.png
  5. BIN
      public/images/pages/nft/nft-claimed.png
  6. BIN
      public/images/pages/nft/nft-preview.png
  7. BIN
      public/images/pages/nft/nft.png
  8. BIN
      public/images/pages/nft/open-tab.png
  9. BIN
      public/images/pages/nft/token.png
  10. BIN
      public/images/pages/nft/wallet.png
  11. 2
      src/common/navigation.yaml
  12. 2
      src/pages/index.md
  13. 291
      src/pages/write-smart-contracts/nft.md
  14. 111
      src/pages/write-smart-contracts/tokens.md

BIN
public/images/pages/nft/btc-stx.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/images/pages/nft/contract-interaction.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

BIN
public/images/pages/nft/contract-name.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
public/images/pages/nft/faucet.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
public/images/pages/nft/nft-claimed.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
public/images/pages/nft/nft-preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

BIN
public/images/pages/nft/nft.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
public/images/pages/nft/open-tab.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

BIN
public/images/pages/nft/token.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/images/pages/nft/wallet.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

2
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

2
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

291
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<string, Account>) {
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.

111
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
Loading…
Cancel
Save