From 92e46355b3422bc2b6a857c8bd01d273e9421049 Mon Sep 17 00:00:00 2001 From: Patrick Gray Date: Mon, 7 Jun 2021 08:38:05 -0400 Subject: [PATCH] feat: added Heystack example app --- next.config.js | 15 + public/images/pages/heystack-app.svg | 1 + src/common/navigation.yaml | 12 +- .../{tutorials => examples}/angular.md | 0 src/pages/build-apps/examples/heystack.md | 619 ++++++++++++++++++ .../{tutorials => examples}/indexing.md | 0 .../public-registry.md | 0 .../{tutorials => examples}/todos.md | 0 src/pages/build-apps/overview.md | 4 +- 9 files changed, 645 insertions(+), 6 deletions(-) create mode 100644 public/images/pages/heystack-app.svg rename src/pages/build-apps/{tutorials => examples}/angular.md (100%) create mode 100644 src/pages/build-apps/examples/heystack.md rename src/pages/build-apps/{tutorials => examples}/indexing.md (100%) rename src/pages/build-apps/{tutorials => examples}/public-registry.md (100%) rename src/pages/build-apps/{tutorials => examples}/todos.md (100%) diff --git a/next.config.js b/next.config.js index 134a0785..1c11c102 100755 --- a/next.config.js +++ b/next.config.js @@ -8,6 +8,21 @@ const withFonts = require('next-fonts'); async function redirects() { return [ + { + source: '/build-apps/tutorials/todos', + destination: '/build-apps/examples/todos', + permanent: true, + }, + { + source: '/build-apps/tutorials/public-registry', + destination: '/build-apps/examples/public-registry', + permanent: true, + }, + { + source: '/build-apps/tutorials/angular', + destination: '/build-apps/examples/angular', + permanent: true, + }, { source: '/browser/todo-list.html', destination: '/build-apps/tutorials/todos', diff --git a/public/images/pages/heystack-app.svg b/public/images/pages/heystack-app.svg new file mode 100644 index 00000000..6b114f4a --- /dev/null +++ b/public/images/pages/heystack-app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/common/navigation.yaml b/src/common/navigation.yaml index b0be8e79..8ccd162a 100644 --- a/src/common/navigation.yaml +++ b/src/common/navigation.yaml @@ -55,12 +55,13 @@ sections: - path: /guides/transaction-signing - path: /guides/data-storage - - title: Tutorials + - title: Example Apps usePageTitles: true pages: - - path: /tutorials/todos - - path: /tutorials/public-registry - - path: /tutorials/angular + - path: /examples/todos + - path: /examples/heystack + - path: /examples/public-registry + - path: /examples/angular - title: Stacks.js References usePageTitles: true @@ -77,6 +78,9 @@ sections: - external: href: 'https://github.com/blockstack/stacks.js/tree/master/packages/transactions' title: transactions + - external: + href: 'https://github.com/blockstack/stacks-blockchain-api/tree/master/client' + title: blockchain-api-client - external: href: 'https://github.com/blockstack/stacks.js/tree/master/packages/stacking' title: 'stacking' diff --git a/src/pages/build-apps/tutorials/angular.md b/src/pages/build-apps/examples/angular.md similarity index 100% rename from src/pages/build-apps/tutorials/angular.md rename to src/pages/build-apps/examples/angular.md diff --git a/src/pages/build-apps/examples/heystack.md b/src/pages/build-apps/examples/heystack.md new file mode 100644 index 00000000..9cb7d11b --- /dev/null +++ b/src/pages/build-apps/examples/heystack.md @@ -0,0 +1,619 @@ +--- +title: Heystack app +description: Interacting with the wallet and smart contracts from a React application +tags: + - example-app +images: + large: /images/pages/heystack-app.svg +--- + +## Introduction + +This example application demonstrates important features of the Stacks blockchain, and is a case study for how a frontend +web application can interact with a Clarity smart contract. The full source of the application is provided, this page +highlights important code snippets and design patterns to help you learn how to develop your own Stacks application. + +This app highlights the following platform features: + +- Authenticating users with the web wallet +- Using a smart contract to store data on the blockchain +- Minting new fungible tokens with a [SIP-010][] compliant smart contract +- Creating and monitoring transactions on the Stacks blockchain using [Stacks.js][] + +You can access the [online version][heystack] of the Heystack app to interact with it. The source for Heystack is also +available on [Github][heystack_gh]. This page assumes some familiarity with [React][]. + +## Heystack overview + +Heystack is a web application for chatting with other Stacks users. The application uses the [Stacks web wallet][] to +authenticate users in the frontend. When a user logs in to Heystack, they're given a genesis amount of $HEY fungible +tokens, which allows them to send and like messages on the platform. + +Heystack is powered by Clarity smart contracts so each message is a transaction on the Stacks blockchain. Each time a +user sends a message on the platform, they must sign the message with the [Stacks web wallet][] (or another compatible +wallet) and pay a small gas fee in STX. A user spends a $HEY token to send every message, and recieves a $HEY token for +every like that their messages receive. + +## Review smart contracts + +Heystack depends on two smart contracts to execute the backend functions of the app on the Stacks blockchain: a contract +for handling the messaging content, and a contract for minting and distributing the $HEY token. + +### Content contract + +The `hey.clar` contract provides two primary functions for the application, one to publish content to +the blockchain and another to like a piece of content based on its ID. This section reviews the implementation of +these primary functions, but is not a comprehensive discussion of the contract. + +In order to accomplish the two primary functions, the contract relies on a data variable `content-index` and two +[data maps][], `like-state` and `publisher-state` which contain the number of likes a piece of content has received, and +the principal address of the account that published the content. + +Note that all variables are defined at the top of the contract, which is a requirement of the Clarity language. These +include constants such as the `contract-creator`, error codes, and a treasury address. + +```clarity +;; +;; Data maps and vars +(define-data-var content-index uint u0) + +(define-read-only (get-content-index) + (ok (var-get content-index)) +) + +(define-map like-state + { content-index: uint } + { likes: uint } +) + +(define-map publisher-state + { content-index: uint } + { publisher: principal } +) +``` + +Read-only functions provide a method for getting the like count of a piece of content, and getting the principal address +of the message publisher. + +```clarity +(define-read-only (get-like-count (id uint)) + ;; Checks map for like count of given id + ;; defaults to 0 likes if no entry found + (ok (default-to { likes: u0 } (map-get? like-state { content-index: id }))) +) + +(define-read-only (get-message-publisher (id uint)) + ;; Checks map for like count of given id + ;; defaults to 0 likes if no entry found + (ok (unwrap-panic (get publisher (map-get? publisher-state { content-index: id })))) +``` + +The `get-like-count` method accepts a content ID and returns the number of likes associated with that content. The +method uses the [`default-to`][] function to return `0` if the content ID isn't found in the map of likes. + +The `get-message-publisher` method accepts a content ID and returns the principal address of the content publisher. The +method uses the [`unwrap-panic`][] function to halt execution of the method if the principal address isn't found in +the map of publishers. + +The two primary public methods are the `send-message` and `like-message` functions. These methods allow the contract +caller to store a message on the blockchain (creating entries in the data maps for the message sender and the number +of likes). Note that the message itself isn't stored in a contract variable, the frontend application reads the content +of the message directly from the transaction on the blockchain. + +```clarity +;; +;; Public functions +(define-public (send-message (content (string-utf8 140))) + (let ((id (unwrap! (increment-content-index) (err u0)))) + (print { content: content, publisher: tx-sender, index: id }) + (map-set like-state + { content-index: id } + { likes: u0 } + ) + (map-set publisher-state + { content-index: id } + { publisher: tx-sender } + ) + (transfer-hey u1 HEY_TREASURY) + ) +) +``` + +The `send-message` method accepts a utf-8 string with a maximum length of 140 characters. The method defines an internal +variable `id` using the `let` function and assigns the next content ID to that variable by calling the +`increment-contract-index` method of the contract. The value assignment of this variable is bound by the [`unwrap!`][] +function, which returns an error and exits the control-flow if the `increment-contract-index` function isn't +successfully called. + +The method then assigns `u0` likes to the content in the `like-state` data map, and adds the principal address to the +`publisher-state` data map using the [`map-set`][] function. Finally, the private method `transfer-hey` is called to +transfer 1 $HEY token from the message sender to the $HEY treasury address stored in the `HEY_TREASURY` constant. + +```clarity +(define-public (like-message (id uint)) + (begin + ;; cannot like content that doesn't exist + (asserts! (>= (var-get content-index) id) (err ERR_CANNOT_LIKE_NON_EXISTENT_CONTENT)) + ;; transfer 1 HEY to the principal that created the content + (map-set like-state + { content-index: id } + { likes: (+ u1 (get likes (unwrap! (get-like-count id) (err u0)))) } + ) + (transfer-hey u1 (unwrap-panic (get-message-publisher id))) + ) +) +``` + +The `like-message` method accepts a content ID. The method checks that the ID is lower than the current content ID using +the [`asserts!`][] function, to verify that the provided ID is a valid ID. If the [`asserts!`][] assessment is `false`, +the method returns an error code. If the ID is valid, the method performs a [`map-set`][] to look up the content in the +`like-state` data map and add a like to the value stored in the map. Once again, the [`unwrap!`][] function is used to +ensure that an invalid value isn't stored in the map. + +The `hey.clar` contract provides some additional functions for working with the $HEY token contract, discussed in the +next section. + +### Token contract + +Heystack creates a native fungible token for use in the application. When a user authenticates with Heystack, they're +automatically eligible to claim 100 $HEY tokens to allow them to start messaging. + +[SIP-010][] defines the fungible token standard on Stacks, which allows Stacks compatible wallets to handle fungible +tokens through a set of standardized methods. SIP-010 defines 7 traits that a fungible token contract must have in order +to be compliant: + +- `transfer`: method for transferring the token from one principal to another +- `get-name`: returns the human-readable name of the token +- `get-symbol`: returns the ticker symbol of the token +- `get-decimals`: returns number of decimal places in the token +- `get-balance`: return the balance of a given principal +- `get-total-supply`: returns the total supply of the token +- `get-token-uri`: returns an optional string that resolves to a valid URI for the token's metadata. + +In Clarity, a contract can declare that it intends to implement a set of standard traits. + +```clarity + +;; Implement the `ft-trait` trait defined in the `ft-trait` contract +;; https://github.com/hstove/stacks-fungible-token +(impl-trait 'ST3J2GVMMM2R07ZFBJDWTYEYAR8FZH5WKDTFJ9AHA.ft-trait.sip-010-trait) +``` + +The [`impl-trait`][] function asserts that the smart contract is fully implementing a given set of traits defined by the +argument. Like variable definitions, `impl-trait` must be declared at the top of a smart contract definition. + +-> The contract address for SIP-010 trait definition is different depending on which network (mainnet, testnet, etc.) +your contract is deployed on. See the standard for the current addresses of the standard traits. + +The `hey-token.clar` contract implements the required 7 traits of [SIP-010][], and one additional method, the +`gift-tokens` method, that allows a principal to request tokens from the contract. + +```clarity +(define-public (gift-tokens (recipient principal)) + (begin + (asserts! (is-eq tx-sender recipient) (err u0)) + (ft-mint? hey-token u1 recipient) + ) +) +``` + +## Authentication + +Authentication is handled through the [`@stacks/connect-react`][] and [`@stacks/auth`][] packages, which interact with +compatible Stacks wallet extensions and provide methods for interacting with a user session respectively. [Jotai][] +provides application state management. + +The [connect wallet button component][] implements the interface with the Stacks web wallet through the +[`@stacks/connect-react`][] package. + +```tsx +import { Button } from '@components/button'; +import React from 'react'; +import { useConnect } from '@stacks/connect-react'; +import { ButtonProps } from '@stacks/ui'; +import { useLoading } from '@hooks/use-loading'; +import { LOADING_KEYS } from '@store/ui'; + +export const ConnectWalletButton: React.FC = props => { + const { doOpenAuth } = useConnect(); + const { isLoading, setIsLoading } = useLoading(LOADING_KEYS.AUTH); + return ( + + ); +}; +``` + +Once connected, [`/src/store/auth.ts`][] populates the user session data into the Jotai store, allowing the application +to access the user information. + +You can see in the [welcome panel component][] how the presence or absence of stored user data is used to display the +wallet connect button or the signed in view. + +```tsx +... +const UserSection = memo((props: StackProps) => { + const { user } = useUser(); + + return ( + + {!user ? : console.log('click')} />} + + ); +}); +... +``` + +### Token faucet + +The `use-claim-hey.ts` file provides a React hook for interacting with the token faucet of the Clarity smart contract. + +```ts +import { useLoading } from '@hooks/use-loading'; +import { LOADING_KEYS } from '@store/ui'; +import { useConnect } from '@stacks/connect-react'; +import { useNetwork } from '@hooks/use-network'; +import { useCallback } from 'react'; +import { useHeyContract } from '@hooks/use-hey-contract'; +import { REQUEST_FUNCTION } from '@common/constants'; +import { principalCV } from '@stacks/transactions/dist/clarity/types/principalCV'; +import { useCurrentAddress } from '@hooks/use-current-address'; + +export function useHandleClaimHey() { + const address = useCurrentAddress(); + const { setIsLoading } = useLoading(LOADING_KEYS.CLAIM_HEY); + const { doContractCall } = useConnect(); + const [contractAddress, contractName] = useHeyContract(); + const network = useNetwork(); + + const onFinish = useCallback(() => { + void setIsLoading(false); + }, [setIsLoading]); + + const onCancel = useCallback(() => { + void setIsLoading(false); + }, [setIsLoading]); + + return useCallback(() => { + void setIsLoading(true); + void doContractCall({ + contractAddress, + contractName, + functionName: REQUEST_FUNCTION, + functionArgs: [principalCV(address)], + onFinish, + onCancel, + network, + stxAddress: address, + }); + }, [setIsLoading, onFinish, network, onCancel, address, doContractCall]); +} +``` + +The [`@stacks/connect-react`][] package exports the `doContractCall` method, which interacts with the smart contract on +the blockchain. There are more examples of transaction calls in the next section. It's important to note that it's +necessary to convert Javascript types to Clarity types using the types exported by the [`@stacks/transactions`][] package. +Further discussion of this conversion is in the [Clarity types in Javascript][] section. + +## Transactions + +Since messages in Heystack are transactions against a Clarity smart contract, the application must be able to create +transactions and read their content from the blockchain. The following sections highlight code snippets that perform +Clarity transactions and read both completed and pending transactions from the Stacks blockchain. + +### Issuing transactions + +The two primary functions of the `hey.clar` smart contract are publishing a message and accepting a like on an already +published message. The [`src/hooks/use-publish-hey.ts`][] file implements the frontend method for calling the smart +contract on the blockchain with the appropriate values. + +```ts +... + return useCallback( + (content: string, _onFinish: () => void) => { + void setShowPendingOverlay(true); + void setIsLoading(true); + + void doContractCall({ + contractAddress, + contractName, + functionName: MESSAGE_FUNCTION, + functionArgs: [ + stringUtf8CV(content), + attachmentUri !== '' ? someCV(stringUtf8CV(attachmentUri)) : noneCV(), + ], + onFinish: () => { + _onFinish(); + onFinish(); + }, + postConditions: [ + createFungiblePostCondition( + address, + FungibleConditionCode.Equal, + new BN(1), + createAssetInfo(contractAddress, 'hey-token', 'hey-token') + ), + ], + onCancel, + network, + stxAddress: address, + }); + }, + [setIsLoading, onFinish, network, onCancel, address, doContractCall] + ); +... +``` + +The frontend uses the `doContractCall` function from the [`@stacks/connect-react`][] package to perform the call to the +Clarity smart contract. In order to support the mapping of [Javascript types to Clarity types][], helpers exported from +the [`@stacks/transactions`][] package are used as arguments to the contract call. + +Note that the contract call also create post conditions to verify that a single $HEY token is transferred by the +execution of the contract call. Post conditions are a powerful feature of Clarity that can be used to prevent +rug-pulling and other detrimental behavior by smart contracts. + +### Reading transactions + +Heystack achieves pseudo-real-time messaging by reading both confirmed and pending transactions from the blockchain. +Pending transactions are read from the mempool, whereas confirmed transactions are read directly from the chain. The +[`src/store/hey.ts`][] file contains the implementation of both. + +```ts +... +export const heyTransactionsAtom = atomWithQuery(get => ({ + queryKey: ['hey-txs'], + ...(defaultOptions as any), + refetchInterval: 500, + queryFn: async (): Promise => { + const client = get(accountsClientAtom); + const txClient = get(transactionsClientAtom); + + const txs = await client.getAccountTransactions({ + limit: 50, + principal: HEY_CONTRACT, + }); + const txids = (txs as TransactionResults).results + .filter( + tx => + tx.tx_type === 'contract_call' && + tx.contract_call.function_name === MESSAGE_FUNCTION && + tx.tx_status === 'success' + ) + .map(tx => tx.tx_id); + + const final = await Promise.all(txids.map(async txId => txClient.getTransactionById({ txId }))); + return final as ContractCallTransaction[]; + }, +})); +... +``` + +The `getAccountTransactions` from the `AccountsApi` object exported by [`@stacks/blockchain-api-client`][] is used to +read confirmed blockchain transactions against the `hey.clar` contract from the Stacks API. The list of transactions +returned by the API is filtered to only transactions representing a call to the message function that was successful, +and then mapped to an array of transaction IDs. + +Finally, the array of IDs is used to read each full transaction from the blockchain using the `getTransactionsById` +method from the `TransactionsApi` object exported by the [`@stacks/blockchain-api-client`][] package. + +Pending transactions are read from the mempool in a similar implementation. + +```ts +export const pendingTxsAtom = atomWithQuery(get => ({ + queryKey: ['hey-pending-txs'], + refetchInterval: 1000, + ...(defaultOptions as any), + queryFn: async (): Promise => { + const client = get(transactionsClientAtom); + + const txs = await client.getMempoolTransactionList({ limit: 96 }); + const heyTxs = (txs as MempoolTransactionListResponse).results + .filter( + tx => + tx.tx_type === 'contract_call' && + tx.contract_call.contract_id === HEY_CONTRACT && + tx.contract_call.function_name === MESSAGE_FUNCTION && + tx.tx_status === 'pending' + ) + .map(tx => tx.tx_id); + + const final = await Promise.all(heyTxs.map(async txId => client.getTransactionById({ txId }))); + + return ( + (final as ContractCallTransaction[]).map(tx => { + const attachment = tx.contract_call.function_args?.[1].repr + .replace(`(some u"`, '') + .slice(0, -1); + + return { + sender: tx.sender_address, + content: tx.contract_call.function_args?.[0].repr + .replace(`u"`, '') + .slice(0, -1) as string, + id: tx.tx_id, + attachment: attachment === 'non' ? undefined : attachment, + timestamp: (tx as any).receipt_time, + isPending: true, + }; + }) || [] + ); + }, +})); +``` + +Pending transactions are read from the mempool using the `getMempoolTransactionList` method from the `TransactionsApi` +exported by [`@stacks/blockchain-api-client`][]. Similar to confirmed transactions, the returned array is filtered to +a list of IDs, and then used to generate an array of full transactions. + +Because of differences in the data structure of the pending transactions vs. confirmed transactions, the pending +transaction list must be standardized before being returned. + +Note that for the low stakes of a messaging app, pending transactions can be treated as likely permanent state +transitions. For applications implementing higher stakes business logic (such as the transfer of representations +of value) it would be more appropriate to wait to display confirmed transactions. + +### Clarity types in Javascript + +In order to create transactions to call functions in Clarity contracts, the [`@stacks/transactions`][] package exports +classes that make it easy to construct well-typed Clarity values in Javascript. According to the Clarity language +specification, Clarity has the following types: + +- `(tuple (key-name-0 key-type 0) (key-name-1 key-type-1) ...)` - a typed tuple with named fields +- `(list max-len entry-type)` - a list of maximum length `max-len`, with entries of type `entry-type` +- `(response ok-type err-type)` - object used by public functions to commit their state changes or abort +- `(optional some-type)` - an option type for objects that can be either `(some-value)` or `none` +- `(buff max-len)` - byte buffer of maximum length +- `principal` - object representing a principal address (contract or standard) +- `bool` - boolean value (`true` or `false`) +- `int` - signed 128-bit integer +- `uint` - unsigned 128-bit integer + +To support these types in Javascript, [`@stacks/transactions`][] exports the following helpers: + +```ts +// construct boolean clarity values +const t = trueCV(); +const f = falseCV(); + +// construct optional clarity values +const nothing = noneCV(); +const something = someCV(t); + +// construct a buffer clarity value from an existing Buffer +const buffer = Buffer.from('foo'); +const bufCV = bufferCV(buffer); + +// construct signed and unsigned integer clarity values +const i = intCV(-10); +const u = uintCV(10); + +// construct principal clarity values +const address = 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B'; +const contractName = 'contract-name'; +const spCV = standardPrincipalCV(address); +const cpCV = contractPrincipalCV(address, contractName); + +// construct response clarity values +const errCV = responseErrorCV(trueCV()); +const okCV = responseOkCV(falseCV()); + +// construct tuple clarity values +const tupCV = tupleCV({ + a: intCV(1), + b: trueCV(), + c: falseCV(), +}); + +// construct list clarity values +const l = listCV([trueCV(), falseCV()]); +``` + +You should use these helpers when calling Clarity contracts with Javascript to avoid failed contract calls due to bad +typing. + +## Reading BNS names + +An important feature of Stacks is the [Blockchain Naming System][] (BNS). BNS allows users to register a human-readable +identity to their account, that can act as both a username and a web address. + +Names registered to a user can be read from a Stacks API endpoint, as demonstrated in [`src/store/names.ts`][]. + +-> Due to ecosystem limitations, it's currently uncommon for BNS names to be registered on any testnet. For the purpose +of demonstration, Heystack looks for BNS names against the user's mainnet wallet address. + +```ts +export const namesAtom = atomFamily((address: string) => + atom(async get => { + if (!address || address === '') return; + const network = get(mainnetNetworkAtom); + if (!network) return null; + + const local = getLocalNames(network.coreApiUrl, address); + + if (local) { + const [names, timestamp] = local; + const now = Date.now(); + const isStale = now - timestamp > STALE_TIME; + if (!isStale) return names; + } + + try { + const names = await fetchNamesByAddress({ + networkUrl: network.coreApiUrl, + address, + }); + if (names?.length) { + setLocalNames(network.coreApiUrl, address, [names, Date.now()]); + } + return names || []; + } catch (e) { + console.error(e); + return []; + } + }) +); +``` + +In order to reduce network traffic, Heystack also caches names in the browser's local storage. + +A common design pattern in Stacks 2.0 apps is to check if a user has a registered BNS name (only 1 name can be tied to +an account) and display that name in the app where appropriate. If the user doesn't own a BNS name, the wallet address +is used as a stand in. Often, the wallet address is truncated to avoid displaying an overly long string. + +The account name component in [`src/components/user-area.tsx`][] demonstrates this design pattern: + +```tsx +... +const AccountNameComponent = memo(() => { + const { user } = useUser(); + const address = useCurrentMainnetAddress(); + const names = useAccountNames(address); + const name = names?.[0]; + return {name || user?.username || truncateMiddle(address)}; +}); +... +``` + +[heystack]: https://heystack.xyz +[stacks.js]: https://github.com/blockstack/stacks.js +[stacks web wallet]: https://www.hiro.so/wallet/install-web +[react]: https://reactjs.org/ +[heystack_gh]: https://github.com/blockstack/heystack +[data maps]: /references/language-functions#define-map +[`default-to`]: /references/language-functions#default-to +[`asserts!`]: /references/language-functions#asserts +[`unwrap-panic`]: /references/language-functions#unwrap-panic +[`unwrap!`]: /references/language-functions#unwrap +[`map-set`]: /references/language-functions#map-set +[sip-010]: https://github.com/hstove/sips/blob/feat/sip-10-ft/sips/sip-010/sip-010-fungible-token-standard.md +[`impl-trait`]: /references/language-functions#impl-trait +[`@stacks/connect-react`]: https://github.com/blockstack/connect#readme +[`@stacks/auth`]: https://github.com/blockstack/stacks.js/tree/master/packages/auth +[jotai]: https://github.com/pmndrs/jotai +[connect wallet button component]: https://github.com/blockstack/heystack/blob/main/src/components/connect-wallet-button.tsx +[welcome panel component]: https://github.com/blockstack/heystack/blob/63ce30f4f6de7a9c846fcdba3acbb6c7b82b83e3/src/components/welcome-panel.tsx#L102 +[`/src/store/auth.ts`]: https://github.com/blockstack/heystack/blob/main/src/store/auth.ts +[clarity types in javascript]: /build-apps/examples/heystack#clarity-types-in-javascript +[`@stacks/transactions`]: https://github.com/blockstack/stacks.js/tree/master/packages/transactions#constructing-clarity-values +[blockchain naming system]: /build-apps/references/bns +[`src/store/names.ts`]: https://github.com/blockstack/heystack/blob/main/src/store/names.ts +[javascript types to clarity types]: /build-apps/examples/heystack#clarity-types-in-javascript +[`@stacks/blockchain-api-client`]: https://github.com/blockstack/stacks-blockchain-api/tree/master/client +[`src/common/hooks/use-publish-hey.ts`]: https://github.com/blockstack/heystack/blob/main/src/common/hooks/use-publish-hey.ts +[`src/store/hey.ts`]: https://github.com/blockstack/heystack/blob/main/src/store/hey.ts +[`src/components/user-area.tsx`]: https://github.com/blockstack/heystack/blob/22e4e9020f8bbb404e8c1e36f32f000050f90818/src/components/user-area.tsx#L62 diff --git a/src/pages/build-apps/tutorials/indexing.md b/src/pages/build-apps/examples/indexing.md similarity index 100% rename from src/pages/build-apps/tutorials/indexing.md rename to src/pages/build-apps/examples/indexing.md diff --git a/src/pages/build-apps/tutorials/public-registry.md b/src/pages/build-apps/examples/public-registry.md similarity index 100% rename from src/pages/build-apps/tutorials/public-registry.md rename to src/pages/build-apps/examples/public-registry.md diff --git a/src/pages/build-apps/tutorials/todos.md b/src/pages/build-apps/examples/todos.md similarity index 100% rename from src/pages/build-apps/tutorials/todos.md rename to src/pages/build-apps/examples/todos.md diff --git a/src/pages/build-apps/overview.md b/src/pages/build-apps/overview.md index c96222b6..d99e0dff 100644 --- a/src/pages/build-apps/overview.md +++ b/src/pages/build-apps/overview.md @@ -29,7 +29,7 @@ While integration is possible for any type of app, most of the resources availab [@page-reference | grid] | /build-apps/guides/authentication, /build-apps/guides/transaction-signing, /build-apps/guides/data-storage -## Tutorials +## Example apps [@page-reference | grid] -| /build-apps/tutorials/todos, /build-apps/tutorials/public-registry, /build-apps/tutorials/angular, /build-apps/tutorials/radiks +| /build-apps/examples/todos, /build-apps/examples/heystack, /build-apps/examples/public-registry, /build-apps/examples/angular