diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_abi.png b/chapters/book/modules/chapter_2/images/basic_dapp_abi.png new file mode 100644 index 000000000..17019c7fe Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_abi.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_abi_new.png b/chapters/book/modules/chapter_2/images/basic_dapp_abi_new.png new file mode 100644 index 000000000..eb83e6c78 Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_abi_new.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_erc20.png b/chapters/book/modules/chapter_2/images/basic_dapp_erc20.png new file mode 100644 index 000000000..af0d055b2 Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_erc20.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_localhost.png b/chapters/book/modules/chapter_2/images/basic_dapp_localhost.png new file mode 100644 index 000000000..98eadc5ac Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_localhost.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_pub1.png b/chapters/book/modules/chapter_2/images/basic_dapp_pub1.png new file mode 100644 index 000000000..5ff28be69 Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_pub1.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_pub2.png b/chapters/book/modules/chapter_2/images/basic_dapp_pub2.png new file mode 100644 index 000000000..1f9d45fd2 Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_pub2.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_pub3.png b/chapters/book/modules/chapter_2/images/basic_dapp_pub3.png new file mode 100644 index 000000000..bcf22e957 Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_pub3.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_pub4.png b/chapters/book/modules/chapter_2/images/basic_dapp_pub4.png new file mode 100644 index 000000000..a69c76dd5 Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_pub4.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_react_files.png b/chapters/book/modules/chapter_2/images/basic_dapp_react_files.png new file mode 100644 index 000000000..ada8a8beb Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_react_files.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_screenshot.png b/chapters/book/modules/chapter_2/images/basic_dapp_screenshot.png new file mode 100644 index 000000000..eac10401a Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_screenshot.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_vercel_login.png b/chapters/book/modules/chapter_2/images/basic_dapp_vercel_login.png new file mode 100644 index 000000000..d14cd6814 Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_vercel_login.png differ diff --git a/chapters/book/modules/chapter_2/images/basic_dapp_vercel_verify.png b/chapters/book/modules/chapter_2/images/basic_dapp_vercel_verify.png new file mode 100644 index 000000000..6eea1262e Binary files /dev/null and b/chapters/book/modules/chapter_2/images/basic_dapp_vercel_verify.png differ diff --git a/chapters/book/modules/chapter_2/nav.adoc b/chapters/book/modules/chapter_2/nav.adoc new file mode 100644 index 000000000..7d59f78bd --- /dev/null +++ b/chapters/book/modules/chapter_2/nav.adoc @@ -0,0 +1,14 @@ +* xref:index.adoc[2. Starknet Tooling] + ** xref:scarb.adoc[Scarb: Package Manager] + ** xref:katana.adoc[Local Node for Development] + ** xref:hardhat.adoc[Starknet-Hardhat] + ** xref:protostar.adoc[Protostar] + ** xref:starknetjs.adoc[Starknet.js] + ** xref:starknetrs.adoc[Starknet-rs 🚧] + ** xref:starknetpy.adoc[Starknet_py 🚧] + ** xref:testing.adoc[Testing 🚧] + ** xref:starknet-react.adoc[Starknet React] + ** xref:starknet-react-basic.adoc[Starknet React Basic Example] + ** xref:bridges.adoc[Bridges 🚧] + ** xref:oracles.adoc[Oracles] + ** xref:indexers-explorers.adoc[Indexers and Explorers] diff --git a/chapters/book/modules/chapter_2/pages/starknet-react-basic.adoc b/chapters/book/modules/chapter_2/pages/starknet-react-basic.adoc new file mode 100644 index 000000000..c5aa44ce2 --- /dev/null +++ b/chapters/book/modules/chapter_2/pages/starknet-react-basic.adoc @@ -0,0 +1,613 @@ +[id="starknet-react-basic"] + += Starknet React: A basic full example. + + +This tutorial will teach you how to build an ERC20 smart contract in Cairo and integrate it into a React web application using StarkNet React. You will learn how to implement the ERC20 interface, deploy your contract to the StarkNet network, and interact with your contract from your React application. By following this tutorial, you will be able to create your own ERC20 token and deploy it to StarkNet. + +This tutorial is suitable for beginners with a basic knowledge of Cairo programming language and ReactJS. You will also need to have Node.js and NPM installed on your machine. + +We'll create an ERC20 token named MKT and build a web3 interface to interact with it, providing functionalities like checking the balance and transferring tokens. + +image::basic_dapp_screenshot.png[screenshot] + +Along this tutorial, we will be using the following tools and libraries: + +- Scarb 0.7.0 with Cairo 2.2.0 +- Starkli 0.1.9 +- Oppenzeppelin libraries v0.7.0 +- Starknet React v1.0.4 +- NodeJS v19.6.1 +- Next.js 13.1.6 +- Visual Studio Code +- Vercel + +== Create a new StarkNet project + +First, let's create a new StarkNet project named "erc20" using Scarb: + +[source,bash] +---- +~$ mkdir erc20 +~$ cd erc20 +~/erc20$ scarb init --name erc20 +Created package. +~/erc20$ +---- + +Edit your Scarb.toml and add the necessary OpenZeppelin libraries. Your Scarb.toml file should resemble the following: + +[source,rust] +---- +[package] +name = "erc20" +version = "0.1.0" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet = ">=2.2.0" +openzeppelin = { git = "https://github.com/OpenZeppelin-contracts.git", tag = "v0.7.0" } + +[[target.starknet-contract]] +---- + +== Implement the ERC20 token + +Now, create a new file named src/erc20.cairo where we will define our ERC20 token, MKT, and its functions for checking the balance and transferring tokens: + +[source,rust] +---- +#[starknet::contract] +mod erc20 { + use starknet::ContractAddress; + use openzeppelin::token::erc20::ERC20; + + #[storage] + struct Storage {} + + #[constructor] + fn constructor( + ref self: ContractState, + initial_supply: u256, + recipient: ContractAddress + ) { + let name = 'MyToken'; + let symbol = 'MTK'; + + let mut unsafe_state = ERC20::unsafe_new_contract_state(); + ERC20::InternalImpl::initializer(ref unsafe_state, name, symbol); + ERC20::InternalImpl::_mint(ref unsafe_state, recipient, initial_supply); + } + + #[external(v0)] + #[generate_trait] + impl Ierc20Impl of Ierc20 { + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + let unsafe_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20Impl::balance_of(@unsafe_state, account) + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let mut unsafe_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20Impl::transfer(ref unsafe_state, recipient, amount) + } + } +} +---- + +image::basic_dapp_erc20.png[screenshot] + +Once you've written your contract, compile it with Scarb: + +[source,yaml] +---- +~/erc20$ scarb build + Updating git repository https://github.com/openzeppelin-contracts + Compiling erc20 v0.1.0 (/home/kali/erc20/Scarb.toml) + Finished release target(s) in 6 seconds +~/erc20$ +---- + +Afterward, declare your smart contract on Starknet testnet: + +[source,yaml] +---- +~/erc20$ starkli declare target/dev/erc20_erc20.sierra.json --account ../../demo-account.json --keystore ../../demo-key.json --compiler-version 2.1.0 --network goerli-1 --watch +WARNING: you´re using the sequencer gateway instead of providing a JSON-RPC endpoint. This is strongly discouraged. See https://book.starkli.rs/providers for more details. +Enter keystore password: +Declaring Cairo 1 class: 0x04940154eae35788e899ceb0ef2794eaa5ea6818af5c1c726d6d278fd4979713 +Compiling Sierra class to CASM with compiler version 2.1.0... +CASM class hash: 0x065d12bc099e19b12d1ded4c27a21d6b5d4f9e5a95f5e2f093452d2d386e4936 +Contract declaration transaction: 0x02f111205d373fad6acfe0d14c34046283d7fa5cb30a3edac97e8bce7699cb57 +Waiting for transaction 0x02f111205d373fad6acfe0d14c34046283d7fa5cb30a3edac97e8bce7699cb57 to confirm... +Transaction not confirmed yet... +Transaction 0x02f111205d373fad6acfe0d14c34046283d7fa5cb30a3edac97e8bce7699cb57 confirmed +Class hash declared: +0x04940154eae35788e899ceb0ef2794eaa5ea6818af5c1c726d6d278fd4979713 +~/erc20$ +---- + +If you haven't made any changes to our example contract, you'll receive a note confirming that your contract has been already declared in the past in Starknet: + +[source,yaml] +---- +~/erc20$ starkli declare target/dev/erc20_erc20.sierra.json --account ../../demo-account.json --keystore ../../demo-key.json --compiler-version 2.1.0 --network goerli-1 --watch +WARNING: you´re using the sequencer gateway instead of providing a JSON-RPC endpoint. This is strongly discouraged. See https://book.starkli.rs/providers for more details. +Enter keystore password: +Not declaring class as it's already declared. Class hash: +0x04940154eae35788e899ceb0ef2794eaa5ea6818af5c1c726d6d278fd4979713 +~/erc20$ +---- + +== Deploy your ERC20 contract + +Now, let's deploy the MKT Token using Starkli. You'll need to provide the following arguments: + +- `Initial mint`: We will mint 1,000,000 tokens, but since our MKT token has 18 decimals (OpenZeppelin default), we need to pass 1,000,000 * 10^18 = 0xd3c21bcecceda1000000. Also, as our contract expects a u256 mint value, we need to pass two values for low and high values: 0xd3c21bcecceda1000000 0. +- `Receiver address`: You can use your own address. In this example, we will use: 0x0334863e3e851de87fb4b6b6113aa2a6b40ea20f22dbec55536e4eac912206fc + +[source,yaml] +---- +~/erc20$ starkli deploy 0x04940154eae35788e899ceb0ef2794eaa5ea6818af5c1c726d6d278fd4979713 --account ../../demo-account.json --keystore ../../demo-key.json --network goerli-1 --watch 0xd3c21bcecceda1000000 0 0x0334863e3e851de87fb4b6b6113aa2a6b40ea20f22dbec55536e4eac912206fc +WARNING: you re using the sequencer gateway instead of providing a JSON-RPC endpoint. This is strongly discouraged. See https://book.starkli.rs/providers for more details. +Enter keystore password: +Deploying class 0x04940154eae35788e899ceb0ef2794eaa5ea6818af5c1c726d6d278fd4979713 with salt 0x03628d1f523b9980306bf8e7226a3c24b0f618689f7a6b484439488b650ee418... +The contract will be deployed at address 0x001892d81e09cb2c2005f0112891dacb92a6f8ce571edd03ed1f3e549abcf37f +Contract deployment transaction: 0x04945fb3765ce729e0c6ef90c4b6fbba908afad9b22650d152bb7c84a65f30dc +Waiting for transaction 0x04945fb3765ce729e0c6ef90c4b6fbba908afad9b22650d152bb7c84a65f30dc to confirm... +Transaction not confirmed yet... +Transaction 0x04945fb3765ce729e0c6ef90c4b6fbba908afad9b22650d152bb7c84a65f30dc confirmed +Contract deployed: +0x001892d81e09cb2c2005f0112891dacb92a6f8ce571edd03ed1f3e549abcf37f +~/erc20$ +---- + +[NOTE] +==== +You will receive a different deployed address; keep this address to replace it in the next section's TypeScript files with your contract address. +==== + +Congratulations! You've successfully deployed your Cairo ERC20 smart contract on Starknet.. + +== Install the StarkNet React library + +With your contract deployed, it's time to start building your web application. First, install the Starknet React library: + +[source,bash] +---- +~$ npm add @starknet-react/core + +added 36 packages, and audited 621 packages in 13s + +15 packages are looking for funding + run `npm fund` for details + +56 vulnerabilities (2 low, 20 moderate, 24 high, 10 critical) + +To address issues that do not require attention, run: + npm audit fix + +To address all issues (including breaking changes), run: + npm audit fix --force + +Run `npm audit` for details. +~$ +---- + +You can check the installed version by running: + +[source,bash] +---- +~$ npm list @starknet-react/core +kali@ /home/kali +└── @starknet-react/core@1.0.4 + +~$ +---- + +== Create a new React project + +The Starknet React library includes a create-starknet script that automatically sets up a Starknet app using TypeScript: + +[source,bash] +---- +~$ npx create-starknet erc20_web --use-npm +✔ What framework would you like to use? › Next.js +Installing dependencies... +Success! Created erc20_web at /home/kali/erc20_web + +We suggest that you begin by typing: + + cd erc20_web + npm run dev +~$ +---- + +Edit erc20_web/index.tsx and replace its contents with the provided code: + +[source,typescript] +---- +import Head from 'next/head' +import { useBlock } from '@starknet-react/core' +import WalletBar from '../components/WalletBar' +import { BlockTag } from 'starknet'; + +export default function Home() { + const { data, isLoading, isError } = useBlock({ + refetchInterval: 3000, + blockIdentifier: BlockTag.latest, + }) + return ( + <> + + Create Starknet + + + + +
+

+ A basic web3 example with Starknet  +

+
+ {isLoading + ? 'Loading...' + : isError + ? 'Error while fetching the latest block hash' + : `Latest block hash: ${data?.block_hash}`} +
+ +
+ + ) +} +---- + +Run your installed web3 default application: + +[source,bash] +---- +~$ cd erc20_web/ +~/erc20_web$ npm run dev + +> erc20_web@0.1.0 dev +> next dev + +warn - Port 3000 is in use, trying 3001 instead. +warn - Port 3001 is in use, trying 3002 instead. +ready - started server on 0.0.0.0:3002, url: http://localhost:3002 +event - compiled client and server successfully in 3.2s (277 modules) +wait - compiling /_error (client and server)... +event - compiled client and server successfully in 169 ms (278 modules) +warn - Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/messages/fast-refresh-reload +wait - compiling / (client and server)... +event - compiled client and server successfully in 193 ms (285 modules) + +---- + +[NOTE] +==== +Note the server port for later testing. +==== + +== Add functionality to your React application + +Now, we will create two components for balance and transfer and update Wallet.tsx to add our new functionality: + +image::basic_dapp_react_files.png[screenshot] + +Create a balance component in components/Balance.tsx and include this code: + +[source,typescript] +---- +import { useAccount, useContractRead } from "@starknet-react/core"; +import erc20ABI from '../assets/erc20.json'; + +function Balance() { + const { address } = useAccount() + const { data, isLoading, error, refetch } = useContractRead({ + address: '0x001892d81e09cb2c2005f0112891dacb92a6f8ce571edd03ed1f3e549abcf37f', + abi: erc20ABI, + functionName: 'balance_of', + args: [address], + watch: false + }) + + if (isLoading) return Loading... + if (error) return Error: {JSON.stringify(error)} + + return ( +
+

Balance.

+

{data?data.toString(): 0}

+

+
+
+ ) +} + + +export default Balance; +---- + +[NOTE] +==== +Replace address with your deployed contract address. +==== + +Create a transfer component in components/Transfer.tsx and include this code: + +[source,typescript] +---- +import { useAccount, useContractWrite } from "@starknet-react/core"; +import React, { useState, useMemo } from "react"; + +function Transfer() { + const { address } = useAccount() + const [count] = useState(1) + const [recipient, setRecipient] = useState('0x'); + const [amount, setAmount] = useState('1000000000000000000'); + + + const calls = useMemo(() => { + const tx = { + contractAddress: '0x001892d81e09cb2c2005f0112891dacb92a6f8ce571edd03ed1f3e549abcf37f', + entrypoint: 'transfer', + calldata: [recipient, amount, 0] + } + return Array(count).fill(tx) + }, [address, count, recipient, amount]) + + const { write } = useContractWrite({ calls }) + + const handleRecipientChange = (event: React.ChangeEvent) => { + setRecipient(event.target.value); + }; + + const handleAmountChange = (event: React.ChangeEvent) => { + setAmount(event.target.value); + }; + + return ( + <> +

Transfer.

+

Recipient: + +

+

Amount (default 1 MKT with 18 decimals): + +

+

+ +

+
+ + ) +} + +export default Transfer; +---- +[NOTE] +==== +Replace contractAddress with your deployed contract address. +==== + +Finally, edit components/Wallet.tsx and replace the existing code with this new code: + +[source,typescript] +---- +import { useAccount, useConnectors } from '@starknet-react/core' +import { useMemo } from 'react' +import Balance from '../components/Balance' +import Transfer from '../components/Transfer' + +function WalletConnected() { + const { address } = useAccount() + const { disconnect } = useConnectors() + + const shortenedAddress = useMemo(() => { + if (!address) return '' + return `${address.slice(0, 6)}...${address.slice(-4)}` + }, [address]) + + return ( +
+ Connected: {shortenedAddress} +

+
+ + +
+ ) +} + +function ConnectWallet() { + const { connectors, connect } = useConnectors() + + return ( +
+ Choose a wallet: +

+ {connectors.map((connector) => { + return ( + + ) + })} +

+
+ ) +} + +export default function WalletBar() { + const { address } = useAccount() + + return address ? : +} +---- + +Our application is almost ready, but we need one more file: the ABI file for our MKT token. To create it, follow these steps: + +1. Create an assets/ folder at the root of your project. +2. Add an empty assets/erc20.json file. +3. Navigate to your previous ERC20 Cairo folder and open erc20/target/erc20_erc20_sierra.json. + +image::basic_dapp_abi.png[screenshot] + +4. Copy the ABI definition (including square brackets) and paste it into assets/erc20.json: + +image::basic_dapp_abi_new.png[screenshot] + +Congratulations! You now have a basic MKT token application running locally at http://localhost:3000 (or the port previously used by the server). With this application, you can connect your wallet, check your balance, and transfer tokens to your friends. + +image::basic_dapp_localhost.png[screenshot] + +== Publish your project to Internet + +But what if you want to transfer tokens to your friends, and they want to check their balances and make transfers too? Our last step is to publish our application on the internet so anyone can use it. To do this, we will use Vercel: + +1. Create your own account at https://vercel.com/signup[Vercel Signup] + +2. Install Vercel in your web application folder (erc20_web): + +[source,yarn] +---- +~$ cd erc20_web/ +~/erc20_web$ npm i -g vercel +npm WARN deprecated debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) +npm WARN deprecated uuid@3.3.2: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + +changed 247 packages in 5s + +18 packages are looking for funding + run `npm fund` for details +~/erc20_web$ vercel init +Vercel CLI 32.4.1 +? Select example: nextjs +> Success! Initialized "nextjs" example in ~/erc20_web/nextjs. +- To deploy, `cd nextjs` and run `vercel`. +~/erc20_web$ +---- + +Log in to your Vercel account: + +[source,yarn] +---- +~/erc20_web$ vercel login +Vercel CLI 32.4.1 +? Log in to Vercel email +? Enter your email address: +We sent an email to . Please follow the steps provided inside it and make sure the security code matches Efficient Sea Otter. +[source,yarn] +---- + +Check your email and click "Verify" to authorize login. + +image::basic_dapp_vercel_login.png[screenshot] + +image::basic_dapp_vercel_verify.png[screenshot] + +Then, you will receive this message in the console: + +[source,yarn] +---- +> Success! Email authentication complete for devnet0x@gmail.com +Congratulations! You are now logged in. In order to deploy something, run `vercel`. +💡 Connect your Git Repositories to deploy every branch push automatically (https://vercel.link/git). +---- + +Now, we need to integrate the project to vercel: + +[source,yarn] +---- +~/erc20_web$ vercel link +Vercel CLI 32.4.1 +? Set up “~/erc20_web”? [Y/n] y +? Which scope should contain your project? devnet0x-gmailcom <- Select your own default scope +? Link to existing project? [y/N] n +? What’s your project’s name? erc20-web +? In which directory is your code located? ./ +Local settings detected in vercel.json: +Auto-detected Project Settings (Next.js): +- Build Command: next build +- Development Command: next dev --port $PORT +- Install Command: `yarn install`, `pnpm install`, `npm install`, or `bun install` +- Output Directory: Next.js default +? Want to modify these settings? [y/N] n +✅ Linked to devnet0x-gmailcom/erc20-web (created .vercel) +---- + +And upload it: + +[source,bash] +---- +~/erc20_web$ vercel +Vercel CLI 32.4.1 +🔍 Inspect: https://vercel.com/devnet0x-gmailcom/erc20-web/BGKuJEcajz8pFWGupiLU8tz8jJzz [2s] +✅ Preview: https://erc20-lhrhi11ya-devnet0x-gmailcom.vercel.app [2s] +📝 To deploy to production (erc20-web.vercel.app), run `vercel --prod` +~/erc20_web$ +---- + +Finally, we publish our project to internet: + +[source,bash] +---- +~/erc20_web$ vercel --prod +Vercel CLI 32.4.1 +🔍 Inspect: https://vercel.com/devnet0x-gmailcom/erc20-web/5n5VvNiscueBLQvarTiMffRWqMEt [1s] +✅ Production: https://erc20-gpmegiyho-devnet0x-gmailcom.vercel.app [1s] +~/erc20_web$ +---- + +Congratulations! You've just shared your MKT token web3 application with the world. + +image::basic_dapp_pub1.png[screenshot] + +Now interact with it connecting your wallet: + +image::basic_dapp_pub2.png[screenshot] + +Checking your balance: + +image::basic_dapp_pub3.png[screenshot] + +And transfering tokens to your friends: + +image::basic_dapp_pub4.png[screenshot] + +== Conclusion + +In this tutorial, you've embarked on a journey to create a web3 application using React and StarkNet Cairo, building an ERC20 smart contract and integrating it into a user-friendly web interface. Here's a brief summary of what you've accomplished: + +`Project Setup`: You learned how to set up a new StarkNet project using Scarb, ensuring the inclusion of OpenZeppelin libraries. + +`ERC20 Implementation`: You implemented an ERC20 token contract in Cairo, adding functions to check balances and transfer tokens. This contract was compiled and deployed on the StarkNet network. + +`React Integration`: With the contract deployed, you created a React web application using StarkNet React, including components for balance checking and token transfers. + +`ABI Definition`: You generated the ABI (Application Binary Interface) definition for your MKT token, which is essential for interacting with the contract. + +`Publication to the Internet`: To make your application accessible to a broader audience, you deployed it to the internet using Vercel, enabling users to connect their wallets, check balances, and make token transfers. + +This tutorial has provided you with a foundation for building web3 applications using StarkNet, React, and Cairo-based smart contracts. The skills you've gained can be extended to create more complex decentralized applications and smart contracts. The world of decentralized finance and blockchain technology awaits your innovative contributions, and you now have the knowledge to get started. Happy coding! + +[NOTE] +==== +The Book is a community-driven effort created for the community. + +* If you've learned something, or not, please take a moment to provide feedback through https://a.sprig.com/WTRtdlh2VUlja09lfnNpZDo4MTQyYTlmMy03NzdkLTQ0NDEtOTBiZC01ZjAyNDU0ZDgxMzU=[this 3-question survey]. +* If you discover any errors or have additional suggestions, don't hesitate to open an https://github.com/starknet-edu/starknetbook/issues[issue on our GitHub repository]. +==== + +include::ROOT:partial$snippet_contributing_blurb.adoc[leveloffset=+1]