Skip to content

Commit f2ef847

Browse files
huth-stacksclaude
andcommitted
Add Hiro developer tutorials in correct documentation locations
Faithfully migrates 4 Hiro guides to their proper locations: - NFT Marketplace, Decentralized Kickstarter, No-Loss Lottery → tutorials/ - Using Clarity Values → cookbook/stacks.js/ All content preserved verbatim from Hiro source including full introductions, explanatory prose, code examples, and testing sections. The Kickstarter guide (previously a placeholder stub) is now fully migrated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 386a20a commit f2ef847

6 files changed

Lines changed: 825 additions & 0 deletions

File tree

docs/cookbook/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* [Build an ft pc](stacks.js/cryptography-and-security/build-an-ft-pc.md)
2121
* [Build an nft pc](stacks.js/cryptography-and-security/build-an-nft-pc.md)
2222
* [Build a STX pc](stacks.js/cryptography-and-security/build-a-stx-pc.md)
23+
* [Using Clarity Values](stacks.js/using-clarity-values.md)
2324

2425
## Clarity
2526

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Using Clarity Values
2+
3+
{% hint style="info" %}
4+
This guide is migrated from the [Hiro documentation](https://docs.hiro.so/en/resources/guides/using-clarity-values).
5+
{% endhint %}
6+
7+
Some endpoints, like the [read-only function contract call](https://docs.hiro.so/api#operation/call_read_only_function), require input to a serialized [Clarity value](https://docs.stacks.co/docs/clarity/). Other endpoints return serialized values that need to be deserialized.
8+
9+
The example shown below illustrates Clarity value usage in combination with the API.
10+
11+
The `@stacks/transactions` library supports typed contract calls and makes [response value utilization much simpler](https://docs.stacks.co/docs/clarity/).
12+
13+
## Usage example
14+
15+
```ts
16+
import {
17+
Configuration,
18+
SmartContractsApiInterface,
19+
SmartContractsApi,
20+
ReadOnlyFunctionSuccessResponse,
21+
} from '@stacks/blockchain-api-client';
22+
import { uintCV, UIntCV, cvToHex, hexToCV, ClarityType } from '@stacks/transactions';
23+
24+
(async () => {
25+
const apiConfig: Configuration = new Configuration({
26+
fetchApi: fetch,
27+
// for mainnet, replace `testnet` with `mainnet`
28+
basePath: 'https://api.testnet.hiro.so', // defaults to http://localhost:3999
29+
});
30+
31+
const contractsApi: SmartContractsApiInterface = new SmartContractsApi(apiConfig);
32+
33+
const principal: string = 'ST000000000000000000002AMW42H';
34+
35+
// use most recent from: https://api.<mainnet/testnet>.hiro.so/v2/pox
36+
const rewardCycle: UIntCV = uintCV(22);
37+
38+
// call a read-only function
39+
const fnCall: ReadOnlyFunctionSuccessResponse = await contractsApi.callReadOnlyFunction({
40+
contractAddress: principal,
41+
contractName: 'pox',
42+
functionName: 'is-pox-active',
43+
readOnlyFunctionArgs: {
44+
sender: principal,
45+
arguments: [cvToHex(rewardCycle)],
46+
},
47+
});
48+
49+
console.log({
50+
status: fnCall.okay,
51+
result: fnCall.result,
52+
representation: hexToCV(fnCall.result).type === ClarityType.BoolTrue,
53+
});
54+
})().catch(console.error);
55+
```

docs/tutorials/SUMMARY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@
3333
* [Testing Clarity Contracts](bitcoin-primer/stacks-development-fundamentals/testing-clarity-contracts.md)
3434
* [Frontend with Stacks.js](bitcoin-primer/stacks-development-fundamentals/frontend-with-stacks.js.md)
3535
* [Deploying Stacks Apps](bitcoin-primer/stacks-development-fundamentals/deploying-stacks-apps.md)
36+
37+
## Hiro Guides
38+
39+
* [Build an NFT Marketplace](build-an-nft-marketplace.md)
40+
* [Build a Decentralized Kickstarter](build-a-decentralized-kickstarter.md)
41+
* [Build a No-Loss Lottery Pool](no-loss-lottery.md)
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# Build a Decentralized Kickstarter
2+
3+
{% hint style="info" %}
4+
This guide is migrated from the [Hiro documentation](https://docs.hiro.so/en/resources/guides/build-a-decentralized-kickstarter).
5+
{% endhint %}
6+
7+
{% hint style="warning" %}
8+
**Upstream content mismatch:** The Hiro source file for this guide (`build-a-decentralized-kickstarter.mdx`) has the title "Build a decentralized Kickstarter" and description "Learn how to create a crowdfunding app, enabling creators to fund their projects without a third party," but its body content is identical to the NFT marketplace guide. This appears to be an error in the upstream Hiro documentation. The content below is migrated faithfully as-is from the Hiro source.
9+
{% endhint %}
10+
11+
In this guide, you will learn how to create an NFT marketplace that allows users to list NFTs for sale. Users can specify the following details for their listings:
12+
- The NFT token to sell.
13+
- Listing expiry in block height.
14+
- The payment asset, either STX or a SIP010 fungible token.
15+
- The NFT price in the chosen payment asset.
16+
- An optional intended taker. If set, only that principal will be able to fulfil the listing.
17+
18+
This marketplace leverages the following Clarity traits:
19+
- `nft-trait` for handling NFTs.
20+
- `ft-trait` for handling fungible tokens.
21+
22+
Over the course of this guide, you will learn how to:
23+
1. Define and handle errors.
24+
2. Create and manage NFT listings.
25+
3. Whitelist asset contracts.
26+
4. Fulfil NFT purchases.
27+
28+
---
29+
30+
## Define and handle errors
31+
32+
First, define constants for various errors that may occur during listing, cancelling, or fulfilling NFT transactions. This helps in maintaining clean and readable code.
33+
34+
```clarity
35+
;; Define listing errors
36+
(define-constant ERR_EXPIRY_IN_PAST (err u1000))
37+
(define-constant ERR_PRICE_ZERO (err u1001))
38+
39+
;; Define cancelling and fulfilling errors
40+
(define-constant ERR_UNKNOWN_LISTING (err u2000))
41+
(define-constant ERR_UNAUTHORISED (err u2001))
42+
(define-constant ERR_LISTING_EXPIRED (err u2002))
43+
(define-constant ERR_NFT_ASSET_MISMATCH (err u2003))
44+
(define-constant ERR_PAYMENT_ASSET_MISMATCH (err u2004))
45+
(define-constant ERR_MAKER_TAKER_EQUAL (err u2005))
46+
(define-constant ERR_UNINTENDED_TAKER (err u2006))
47+
(define-constant ERR_ASSET_CONTRACT_NOT_WHITELISTED (err u2007))
48+
(define-constant ERR_PAYMENT_CONTRACT_NOT_WHITELISTED (err u2008))
49+
```
50+
51+
## Create and manage NFT listings
52+
53+
### Define data structures
54+
55+
Create a map data structure for the asset listings and a data variable for unique IDs.
56+
57+
```clarity
58+
;; Define a map data structure for the asset listings
59+
(define-map listings
60+
uint
61+
{
62+
maker: principal,
63+
taker: (optional principal),
64+
token-id: uint,
65+
nft-asset-contract: principal,
66+
expiry: uint,
67+
price: uint,
68+
payment-asset-contract: (optional principal)
69+
}
70+
)
71+
72+
;; Used for unique IDs for each listing
73+
(define-data-var listing-nonce uint u0)
74+
```
75+
76+
### List an asset
77+
78+
Create a public function to list an asset along with its contract. This function verifies the contract, checks expiry and price, and transfers the NFT ownership to the marketplace.
79+
80+
```clarity
81+
(define-public (list-asset
82+
(nft-asset-contract <nft-trait>)
83+
(nft-asset {
84+
taker: (optional principal),
85+
token-id: uint,
86+
expiry: uint,
87+
price: uint,
88+
payment-asset-contract: (optional principal)
89+
})
90+
)
91+
(let ((listing-id (var-get listing-nonce)))
92+
;; Verify that the contract of this asset is whitelisted
93+
(asserts! (is-whitelisted (contract-of nft-asset-contract)) ERR_ASSET_CONTRACT_NOT_WHITELISTED)
94+
;; Verify that the asset is not expired
95+
(asserts! (> (get expiry nft-asset) block-height) ERR_EXPIRY_IN_PAST)
96+
;; Verify that the asset price is greater than zero
97+
(asserts! (> (get price nft-asset) u0) ERR_PRICE_ZERO)
98+
;; Verify that the contract of the payment is whitelisted
99+
(asserts! (match (get payment-asset-contract nft-asset)
100+
payment-asset
101+
(is-whitelisted payment-asset)
102+
true
103+
) ERR_PAYMENT_CONTRACT_NOT_WHITELISTED)
104+
;; Transfer the NFT ownership to this contract's principal
105+
(try! (transfer-nft
106+
nft-asset-contract
107+
(get token-id nft-asset)
108+
tx-sender
109+
(as-contract tx-sender)
110+
))
111+
;; List the NFT in the listings map
112+
(map-set listings listing-id (merge
113+
{ maker: tx-sender, nft-asset-contract: (contract-of nft-asset-contract) }
114+
nft-asset
115+
))
116+
;; Increment the nonce to use for the next unique listing ID
117+
(var-set listing-nonce (+ listing-id u1))
118+
;; Return the created listing ID
119+
(ok listing-id)
120+
)
121+
)
122+
```
123+
124+
### Retrieve an asset
125+
126+
Create a read-only function to retrieve an asset, or listing, by its ID.
127+
128+
```clarity
129+
(define-read-only (get-listing (listing-id uint))
130+
(map-get? listings listing-id)
131+
)
132+
```
133+
134+
### Cancel a listing
135+
136+
Create a public function to cancel a listing. Only the NFT's creator can cancel the listing, and it must use the same asset contract that the NFT uses.
137+
138+
```clarity
139+
(define-public (cancel-listing (listing-id uint) (nft-asset-contract <nft-trait>))
140+
(let (
141+
(listing (unwrap! (map-get? listings listing-id) ERR_UNKNOWN_LISTING))
142+
(maker (get maker listing))
143+
)
144+
;; Verify that the caller of the function is the creator of the NFT to be cancelled
145+
(asserts! (is-eq maker tx-sender) ERR_UNAUTHORISED)
146+
;; Verify that the asset contract to use is the same one that the NFT uses
147+
(asserts! (is-eq
148+
(get nft-asset-contract listing)
149+
(contract-of nft-asset-contract)
150+
) ERR_NFT_ASSET_MISMATCH)
151+
;; Delete the listing
152+
(map-delete listings listing-id)
153+
;; Transfer the NFT from this contract's principal back to the creator's principal
154+
(as-contract (transfer-nft nft-asset-contract (get token-id listing) tx-sender maker))
155+
)
156+
)
157+
```
158+
159+
## Whitelist asset contracts
160+
161+
### Whitelist contracts
162+
163+
The marketplace requires any contracts used for assets or payments to be whitelisted by the contract owner. Create a map to store whitelisted asset contracts and a function to check if a contract is whitelisted.
164+
165+
```clarity
166+
(define-map whitelisted-asset-contracts principal bool)
167+
168+
(define-read-only (is-whitelisted (asset-contract principal))
169+
(default-to false (map-get? whitelisted-asset-contracts asset-contract))
170+
)
171+
```
172+
173+
### Set whitelisted contracts
174+
175+
Only the contract owner can whitelist an asset contract. Create a public function to set whitelisted asset contracts.
176+
177+
```clarity
178+
(define-public (set-whitelisted (asset-contract principal) (whitelisted bool))
179+
(begin
180+
(asserts! (is-eq contract-owner tx-sender) ERR_UNAUTHORISED)
181+
(ok (map-set whitelisted-asset-contracts asset-contract whitelisted))
182+
)
183+
)
184+
```
185+
186+
## Fulfill NFT purchases
187+
188+
### Fulfill listing with STX
189+
190+
Create a public function to purchase a listing using STX as payment.
191+
192+
```clarity
193+
(define-public (fulfil-listing-stx (listing-id uint) (nft-asset-contract <nft-trait>))
194+
(let (
195+
;; Verify the given listing ID exists
196+
(listing (unwrap! (map-get? listings listing-id) ERR_UNKNOWN_LISTING))
197+
;; Set the NFT's taker to the purchaser (caller of the function)
198+
(taker tx-sender)
199+
)
200+
;; Validate that the purchase can be fulfilled
201+
(try! (assert-can-fulfil (contract-of nft-asset-contract) none listing))
202+
;; Transfer the NFT to the purchaser (caller of the function)
203+
(try! (as-contract (transfer-nft nft-asset-contract (get token-id listing) tx-sender taker)))
204+
;; Transfer the STX payment from the purchaser to the creator of the NFT
205+
(try! (stx-transfer? (get price listing) taker (get maker listing)))
206+
;; Remove the NFT from the marketplace listings
207+
(map-delete listings listing-id)
208+
;; Return the listing ID that was just purchased
209+
(ok listing-id)
210+
)
211+
)
212+
```
213+
214+
### Fulfill listing with SIP-010
215+
216+
Create a public function to purchase a listing using another fungible token that follows the SIP-010 standard as payment.
217+
218+
```clarity
219+
(define-public (fulfil-listing-ft
220+
(listing-id uint)
221+
(nft-asset-contract <nft-trait>)
222+
(payment-asset-contract <ft-trait>)
223+
)
224+
(let (
225+
;; Verify the given listing ID exists
226+
(listing (unwrap! (map-get? listings listing-id) ERR_UNKNOWN_LISTING))
227+
;; Set the NFT's taker to the purchaser (caller of the function)
228+
(taker tx-sender)
229+
)
230+
;; Validate that the purchase can be fulfilled
231+
(try! (assert-can-fulfil
232+
(contract-of nft-asset-contract)
233+
(some (contract-of payment-asset-contract))
234+
listing
235+
))
236+
;; Transfer the NFT to the purchaser (caller of the function)
237+
(try! (as-contract (transfer-nft nft-asset-contract (get token-id listing) tx-sender taker)))
238+
;; Transfer the tokens as payment from the purchaser to the creator of the NFT
239+
(try! (transfer-ft payment-asset-contract (get price listing) taker (get maker listing)))
240+
;; Remove the NFT from the marketplace listings
241+
(map-delete listings listing-id)
242+
;; Return the listing ID that was just purchased
243+
(ok listing-id)
244+
)
245+
)
246+
```
247+
248+
### Validate purchase can be fulfilled
249+
250+
Create a private function to validate that a purchase can be fulfilled. This function checks the listing's expiry, the NFT's contract, and the payment's contract.
251+
252+
```clarity
253+
(define-private (assert-can-fulfil
254+
(nft-asset-contract principal)
255+
(payment-asset-contract (optional principal))
256+
(listing {
257+
maker: principal,
258+
taker: (optional principal),
259+
token-id: uint,
260+
nft-asset-contract: principal,
261+
expiry: uint,
262+
price: uint,
263+
payment-asset-contract: (optional principal)
264+
})
265+
)
266+
(begin
267+
;; Verify that the buyer is not the same as the NFT creator
268+
(asserts! (not (is-eq (get maker listing) tx-sender)) ERR_MAKER_TAKER_EQUAL)
269+
;; Verify the buyer has been set in the listing metadata as its `taker`
270+
(asserts!
271+
(match (get taker listing) intended-taker (is-eq intended-taker tx-sender) true)
272+
ERR_UNINTENDED_TAKER
273+
)
274+
;; Verify the listing for purchase is not expired
275+
(asserts! (< block-height (get expiry listing)) ERR_LISTING_EXPIRED)
276+
;; Verify the asset contract used to purchase the NFT is the same as the one set on the NFT
277+
(asserts! (is-eq (get nft-asset-contract listing) nft-asset-contract) ERR_NFT_ASSET_MISMATCH)
278+
;; Verify the payment contract used to purchase the NFT is the same as the one set on the NFT
279+
(asserts!
280+
(is-eq (get payment-asset-contract listing) payment-asset-contract)
281+
ERR_PAYMENT_ASSET_MISMATCH
282+
)
283+
(ok true)
284+
)
285+
)
286+
```

0 commit comments

Comments
 (0)