From 09ec25f6823b0324d67837037378845b1eb050df Mon Sep 17 00:00:00 2001 From: Onyewuchi Emmanuel Date: Mon, 30 Sep 2024 05:30:03 +0100 Subject: [PATCH 01/54] Fix/paging-ordering-filter-data-frontend (#348) * chore(): major fixes to the code examples on the course * fix(): error from chakraUI components components * feat(): add search and reload options to fetchPage method Enhanced the fetchPage method with the following updates: - Added and parameters to enable filtered prefetching and optional data reloading. - Improved error handling with try-catch during movie deserialization. - Simplified the movie accumulation logic within the reduce function. * fix/minor-fix * refactor(): explicitly define account structure in prefetchAccounts method Updated the prefetchAccounts method to: - Explicitly define the expected structure of the returned accounts from getProgramAccounts. - Retain functionality for filtered searches based on the search string. - Maintain the original logic while improving type safety and code clarity. * fix: add error handling and buffer length validation in account sorting logic Enhanced the account sorting logic to: - Prevent out-of-bounds buffer access by validating buffer lengths before slicing. - Handle potential errors during sorting with try-catch, logging errors, and defaulting to a neutral sort order in case of issues. - Improved buffer slicing using subarray for better performance. * refactor(): add error handling and improve deserialization logic in fetchPage method Enhanced the fetchPage method with: - Improved error handling during movie deserialization using try-catch. - Added detailed logging for deserialization errors to aid in debugging. - Simplified the movie accumulation logic by directly pushing valid movies to the result array. * chore(): hid the page as it was by default * fix(): minor fix * chore(): added updated repo url and screenshots. also made few fixes as stated in the PR review * refactor useffect, added updated repo links and updated previous code example with correct and updated code * chore(): added comments to explain choice of values * fix: fixed magic numbers and issues raised in the Pr review * Update content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md * Update content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md * chore(): implemented pr review changes * fix(): - Removed unnecessary type coercion on `getProgramAccounts` return value. - Updated the `offset` from 6 to 2 to match the data schema. * chore(): reverted to the best approach * fix(): removed type coercion "as" and implemented a better method to return correct type --------- Co-authored-by: Onyewuchi Emeka Co-authored-by: Mike MacCana --- ...paging-ordering-filtering-data-frontend.md | 457 ++++++++++++------ 1 file changed, 312 insertions(+), 145 deletions(-) diff --git a/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md b/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md index e4f089b0a..cf84f50d6 100644 --- a/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md +++ b/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md @@ -103,16 +103,59 @@ Once you’ve fetched accounts with the given data slice, you can use the `sort` method to sort the array before mapping it to an array of public keys. ```tsx -const accounts = await connection.getProgramAccounts(programId, { - dataSlice: { offset: 13, length: 15 }, +// account type as returned by getProgramAccounts() +type ProgramAccount = { + pubkey: PublicKey; + account: AccountInfo; +}; + +// 4 bytes are used to store the current length of a string +const STRING_LENGTH_SPACE = 4; + +// 9 bytes are reserved for some metadata related to the account structure. +const ACCOUNT_METADATA_SPACE = 9; + +// The offset where the actual data begins. +const DATA_OFFSET = STRING_LENGTH_SPACE + ACCOUNT_METADATA_SPACE; + +// Length of data we need to retrieve (15 bytes in this case, based on the expected size of the relevant data slice). +const DATA_LENGTH = 15; + +// Get readonly accounts response +const readOnlyAccounts = await connection.getProgramAccounts(programId, { + dataSlice: { offset: DATA_OFFSET, length: DATA_LENGTH }, }); +// Make a mutable copy of the readonly array +const accounts: Array = Array.from(readonlyAccounts); + accounts.sort((a, b) => { - const lengthA = a.account.data.readUInt32LE(0); - const lengthB = b.account.data.readUInt32LE(0); - const dataA = a.account.data.slice(4, 4 + lengthA); - const dataB = b.account.data.slice(4, 4 + lengthB); - return dataA.compare(dataB); + try { + // Check if buffers are long enough to avoid out-of-bounds access + const lengthA = a.account.data.readUInt32LE(0); + const lengthB = b.account.data.readUInt32LE(0); + + if ( + a.account.data.length < STRING_LENGTH_SPACE + lengthA || + b.account.data.length < STRING_LENGTH_SPACE + lengthB + ) { + throw new Error("Buffer length is insufficient"); + } + + const dataA = a.account.data.subarray( + STRING_LENGTH_SPACE, + STRING_LENGTH_SPACE + lengthA, + ); + const dataB = b.account.data.subarray( + STRING_LENGTH_SPACE, + STRING_LENGTH_SPACE + lengthB, + ); + + return dataA.compare(dataB); + } catch (error) { + console.error("Error sorting accounts: ", error); + return 0; // Default sort order in case of error + } }); const accountKeys = accounts.map(account => account.pubkey); @@ -147,21 +190,44 @@ For example, you could search through a list of contacts by including a `memcmp` filter: ```tsx +type ProgramAccount = { + pubkey: PublicKey; + account: AccountInfo; +}; + +const DATA_OFFSET = 2; // Skip the first 2 bytes, which store versioning information for the data schema of the account. This versioning ensures that changes to the account's structure can be tracked and managed over time. +const DATA_LENGTH = 18; // Retrieve 18 bytes of data, including the part of the account's data that stores the user's public key for comparison. + async function fetchMatchingContactAccounts( - connection: web3.Connection, + connection: Connection, search: string, -): Promise<(web3.AccountInfo | null)[]> { - const accounts = await connection.getProgramAccounts(programId, { - dataSlice: { offset: 0, length: 0 }, - filters: [ +): Promise | null>> { + let filters: Array<{ memcmp: { offset: number; bytes: string } }> = []; + + if (search.length > 0) { + filters = [ { memcmp: { - offset: 13, - bytes: bs58.encode(Buffer.from(search)), + offset: DATA_OFFSET, + bytes: bs58.encode(Buffer.from(search)), // Convert the search string to Base58 for comparison with the on-chain data. }, }, - ], - }); + ]; + } + + // Get readonly accounts response + const readonlyAccounts = await connection.getProgramAccounts( + new PublicKey(MOVIE_REVIEW_PROGRAM_ID), + { + dataSlice: { offset: DATA_OFFSET, length: DATA_LENGTH }, // Only retrieve the portion of data relevant to the search. + filters, + }, + ); + + // Make a mutable copy of the readonly array + const accounts: Array = Array.from(readonlyAccounts); + + return accounts.map(account => account.account); // Return the account data. } ``` @@ -183,13 +249,13 @@ you’re just jumping into this lesson without having looked at the previous ones - as long as you have the prerequisite knowledge, you should be able to follow the lab without having worked in this specific project yet. -![movie review frontend](/public/assets/courses/unboxed/movie-reviews-frontend.png) +![movie review frontend](/public/assets/courses/movie-review-frontend-dapp.png) #### **1. Download the starter code** If you didn’t complete the lab from the last lesson or just want to make sure that you didn’t miss anything, you can download the -[starter code](https://github.com/Unboxed-Software/solana-movie-frontend/tree/solution-deserialize-account-data). +[starter code](https://github.com/solana-developers/movie-review-frontend/tree/solutions-deserialize-account-data). The project is a fairly simple Next.js application. It includes the `WalletContextProvider` we created in the Wallets lesson, a `Card` component for @@ -218,25 +284,25 @@ accounts. You can, and should, do better for a production application. With that out of the way, let’s create a static property `accounts` of type `web3.PublicKey[]`, a static function `prefetchAccounts(connection: web3.Connection)`, and a static function -`fetchPage(connection: web3.Connection, page: number, perPage: number): Promise`. +`fetchPage(connection: web3.Connection, page: number, perPage: number): Promise>`. You’ll also need to import `@solana/web3.js` and `Movie`. ```tsx -import * as web3 from "@solana/web3.js"; +import { Connection, PublicKey, AccountInfo } from "@solana/web3.js"; import { Movie } from "../models/Movie"; const MOVIE_REVIEW_PROGRAM_ID = "CenYq6bDRB7p73EjsPEpiYN7uveyPUTdXkDkgUduboaN"; export class MovieCoordinator { - static accounts: web3.PublicKey[] = []; + static accounts: PublicKey[] = []; - static async prefetchAccounts(connection: web3.Connection) {} + static async prefetchAccounts(connection: Connection) {} static async fetchPage( - connection: web3.Connection, + connection: Connection, page: number, perPage: number, - ): Promise {} + ): Promise> {} } ``` @@ -245,15 +311,19 @@ the body of `prefetchAccounts` to do this and set the retrieved public keys to the static `accounts` property. ```tsx -static async prefetchAccounts(connection: web3.Connection) { - const accounts = await connection.getProgramAccounts( - new web3.PublicKey(MOVIE_REVIEW_PROGRAM_ID), - { - dataSlice: { offset: 0, length: 0 }, - } - ) +static async prefetchAccounts(connection: Connection) { + try { + const accounts = await connection.getProgramAccounts( + new PublicKey(MOVIE_REVIEW_PROGRAM_ID), + { + dataSlice: { offset: 0, length: 0 }, + } + ); - this.accounts = accounts.map(account => account.pubkey) + this.accounts = accounts.map((account) => account.pubkey); + } catch (error) { + console.error("Error prefetching accounts:", error); + } } ``` @@ -264,33 +334,43 @@ that correspond to the requested page and call and return the corresponding `Movie` objects. ```tsx -static async fetchPage(connection: web3.Connection, page: number, perPage: number): Promise { - if (this.accounts.length === 0) { - await this.prefetchAccounts(connection) +static async fetchPage( + connection: Connection, + page: number, + perPage: number, + reload = false +): Promise> { + if (this.accounts.length === 0 || reload) { + await this.prefetchAccounts(connection); } const paginatedPublicKeys = this.accounts.slice( (page - 1) * perPage, - page * perPage, - ) - - if (paginatedPublicKeys.length === 0) { - return [] - } - - const accounts = await connection.getMultipleAccountsInfo(paginatedPublicKeys) + page * perPage + ); - const movies = accounts.reduce((accum: Movie[], account) => { - const movie = Movie.deserialize(account?.data) - if (!movie) { - return accum + if (!paginatedPublicKeys.length) { + return []; } - return [...accum, movie] - }, []) + const accounts = await connection.getMultipleAccountsInfo( + paginatedPublicKeys + ); - return movies -} + const movies = accounts.reduce((accumulator: >, account) => { + try { + const movie = Movie.deserialize(account?.data); + if (movie) { + accumulator.push(movie); + } + } catch (error) { + console.error("Error deserializing movie data: ", error); + } + return accumulator; + }, []); + + return movies; + } ``` With that done, we can reconfigure `MovieList` to use these methods. In @@ -299,13 +379,22 @@ With that done, we can reconfigure `MovieList` to use these methods. In instead of fetching the accounts inline. ```tsx -const { connection } = useConnection(); -const [movies, setMovies] = useState([]); +const connection = new Connection(clusterApiUrl("devnet")); +const [movies, setMovies] = useState>([]); const [page, setPage] = useState(1); useEffect(() => { - MovieCoordinator.fetchPage(connection, page, 10).then(setMovies); -}, [page]); + const fetchMovies = async () => { + try { + const movies = await MovieCoordinator.fetchPage(connection, page, 10); + setMovies(movies); + } catch (error) { + console.error("Failed to fetch movies:", error); + } + }; + + fetchMovies(); +}, [page, connection]); ``` Lastly, we need to add buttons to the bottom of the list for navigating to @@ -313,21 +402,29 @@ different pages: ```tsx return ( -
+
{movies.map((movie, i) => ( ))} -
- - {page > 1 && ( - - )} - - {MovieCoordinator.accounts.length > page * 2 && ( - - )} - -
+ +
+ {page > 1 && ( + + )} + {movies.length === 5 && ( + + )} +
); ``` @@ -362,23 +459,56 @@ Now that we’ve thought through this, let’s modify the implementation of `prefetchAccounts` in `MovieCoordinator`: ```tsx -static async prefetchAccounts(connection: web3.Connection, filters: AccountFilter[]) { - const accounts = await connection.getProgramAccounts( - new web3.PublicKey(MOVIE_REVIEW_PROGRAM_ID), +// account type as returned by getProgramAccounts() +type ProgramAccount = { + pubkey: PublicKey; + account: AccountInfo; +}; + +const DATA_OFFSET = 2; // Skip the first 2 bytes, which store versioning information for the data schema of the account. This versioning ensures that changes to the account's structure can be tracked and managed over time. +const DATA_LENGTH = 18; // Retrieve 18 bytes of data, including the part of the account's data that stores the user's public key for comparison. +// Define a constant for the size of the header in each account buffer +const HEADER_SIZE = 4; // 4 bytes for length header + +static async prefetchAccounts(connection: Connection) { + // Get readonly accounts response + const readonlyAccounts = (await connection.getProgramAccounts( + new PublicKey(MOVIE_REVIEW_PROGRAM_ID), { - dataSlice: { offset: 2, length: 18 }, + dataSlice:{ offset: DATA_OFFSET, length: DATA_LENGTH }, + } + )) + + const accounts: Array = Array.from(readonlyAccounts); // Make a mutable copy of the readonly array + + accounts.sort((a, b) => { + try { + // Check if buffers are long enough to avoid out-of-bounds access + const lengthA = a.account.data.readUInt32LE(0); // Reads the first 4 bytes for length + const lengthB = b.account.data.readUInt32LE(0); + + if ( + a.account.data.length < HEADER_SIZE + lengthA || + b.account.data.length < HEADER_SIZE + lengthB + ) { + throw new Error('Buffer length is insufficient'); + } + + const dataA = a.account.data.subarray(HEADER_SIZE, HEADER_SIZE + lengthA); + const dataB = b.account.data.subarray(HEADER_SIZE, HEADER_SIZE + lengthB); + + return dataA.compare(dataB); + } catch (error) { + console.error('Error sorting accounts: ', error); + return 0; // Default sort order in case of error } - ) + }); - accounts.sort( (a, b) => { - const lengthA = a.account.data.readUInt32LE(0) - const lengthB = b.account.data.readUInt32LE(0) - const dataA = a.account.data.slice(4, 4 + lengthA) - const dataB = b.account.data.slice(4, 4 + lengthB) - return dataA.compare(dataB) - }) + this.accounts = accounts.map(account => account.pubkey) - this.accounts = accounts.map(account => account.pubkey) + } catch (error) { + console.error("Error prefetching accounts:", error); + } } ``` @@ -402,32 +532,50 @@ import bs58 from 'bs58' ... -static async prefetchAccounts(connection: web3.Connection, search: string) { - const accounts = await connection.getProgramAccounts( - new web3.PublicKey(MOVIE_REVIEW_PROGRAM_ID), - { - dataSlice: { offset: 2, length: 18 }, - filters: search === '' ? [] : [ - { - memcmp: - { - offset: 6, - bytes: bs58.encode(Buffer.from(search)) - } - } - ] - } - ) - - accounts.sort( (a, b) => { - const lengthA = a.account.data.readUInt32LE(0) - const lengthB = b.account.data.readUInt32LE(0) - const dataA = a.account.data.slice(4, 4 + lengthA) - const dataB = b.account.data.slice(4, 4 + lengthB) - return dataA.compare(dataB) - }) - - this.accounts = accounts.map(account => account.pubkey) +static async prefetchAccounts(connection: Connection, search: string) { + const readonlyAccounts = (await connection.getProgramAccounts( + new PublicKey(MOVIE_REVIEW_PROGRAM_ID), + { + dataSlice: { offset: DATA_OFFSET, length: DATA_LENGTH }, + filters: + search === "" + ? [] + : [ + { + memcmp: { + offset: 6, + bytes: bs58.encode(Buffer.from(search)), + }, + }, + ], + } + )); + + const accounts: Array = Array.from(readonlyAccounts); // Make a mutable copy of the readonly array + + accounts.sort((a, b) => { + try { + const lengthA = a.account.data.readUInt32LE(0); + const lengthB = b.account.data.readUInt32LE(0); + + if ( + a.account.data.length < HEADER_SIZE + lengthA || + b.account.data.length < HEADER_SIZE + lengthB + ) { + throw new Error('Buffer length is insufficient'); + } + + const dataA = a.account.data.subarray(HEADER_SIZE, HEADER_SIZE + lengthA); + const dataB = b.account.data.subarray(HEADER_SIZE, HEADER_SIZE + lengthB); + + return dataA.compare(dataB); + } catch (error) { + console.error("Error sorting accounts: ", error); + return 0; + } + }); + + this.accounts = accounts.map((account) => account.pubkey); } ``` @@ -437,33 +585,44 @@ parameter to `fetchPage` so that we can force a refresh of the account prefetching every time the search value changes. ```tsx -static async fetchPage(connection: web3.Connection, page: number, perPage: number, search: string, reload: boolean = false): Promise { +static async fetchPage( + connection: Connection, + page: number, + perPage: number, + search: string, + reload = false +): Promise> { if (this.accounts.length === 0 || reload) { - await this.prefetchAccounts(connection, search) + await this.prefetchAccounts(connection, search); } const paginatedPublicKeys = this.accounts.slice( (page - 1) * perPage, - page * perPage, - ) + page * perPage + ); if (paginatedPublicKeys.length === 0) { - return [] + return []; } - const accounts = await connection.getMultipleAccountsInfo(paginatedPublicKeys) - - const movies = accounts.reduce((accum: Movie[], account) => { - const movie = Movie.deserialize(account?.data) - if (!movie) { - return accum - } - - return [...accum, movie] - }, []) + const accounts = await connection.getMultipleAccountsInfo( + paginatedPublicKeys + ); - return movies -} + const movies = accounts.reduce((accumulator: >, account) => { + try { + const movie = Movie.deserialize(account?.data); + if (movie) { + accumulator.push(movie); + } + } catch (error) { + console.error('Error deserializing movie data: ', error); + } + return accumulator; + }, []); + + return movies; + } ``` With that in place, let’s update the code in `MovieList` to call this properly. @@ -473,34 +632,42 @@ calls. Then update the call to `MovieCoordinator.fetchPage` in the `useEffect` to pass the `search` parameter and to reload when `search !== ''`. ```tsx -const { connection } = useConnection(); -const [movies, setMovies] = useState([]); +const connection = new Connection(clusterApiUrl("devnet")); +const [movies, setMovies] = useState>([]); const [page, setPage] = useState(1); const [search, setSearch] = useState(""); useEffect(() => { - MovieCoordinator.fetchPage(connection, page, 2, search, search !== "").then( - setMovies, - ); -}, [page, search]); + const fetchMovies = async () => { + try { + const movies = await MovieCoordinator.fetchPage( + connection, + page, + 5, + search, + search !== "", + ); + setMovies(movies); + } catch (error) { + console.error("Failed to fetch movies:", error); + } + }; + + fetchMovies(); +}, [connection, page, search]); ``` Finally, add a search bar that will set the value of `search`: ```tsx return ( -
-
- setSearch(event.currentTarget.value)} - placeholder="Search" - w="97%" - mt={2} - mb={2} - /> -
+
+ setSearch(e.target.value)} + placeholder="Search" + /> ...
); @@ -511,7 +678,7 @@ And that’s it! The app now has ordered reviews, paging, and search. That was a lot to digest, but you made it through. If you need to spend some more time with the concepts, feel free to reread the sections that were most challenging for you and/or have a look at the -[solution code](https://github.com/Unboxed-Software/solana-movie-frontend/tree/solution-paging-account-data). +[solution code](https://github.com/solana-developers/movie-review-frontend/tree/solutions-paging-account-data). ## Challenge @@ -519,17 +686,17 @@ Now it’s your turn to try and do this on your own. Using the Student Intros ap from last lesson, add paging, ordering alphabetically by name, and searching by name. -![Student Intros frontend](/public/assets/courses/unboxed/student-intros-frontend.png) +![Student Intros frontend](/public/assets/courses/student-intros-frontend.png) 1. You can build this from scratch or you can download the - [starter code](https://github.com/Unboxed-Software/solana-student-intros-frontend/tree/solution-deserialize-account-data) + [starter code](https://github.com/solana-developers/solana-student-intro-frontend/tree/solution-deserialize-account-data) 2. Add paging to the project by prefetching accounts without data, then only fetching the account data for each account when it’s needed. 3. Order the accounts displayed in the app alphabetically by name. 4. Add the ability to search through introductions by a student’s name. This is challenging. If you get stuck, feel free to reference the -[solution code](https://github.com/Unboxed-Software/solana-student-intros-frontend/tree/solution-paging-account-data). +[solution code](https://github.com/solana-developers/solana-student-intro-frontend/tree/solution-paging-account-data). As always, get creative with these challenges and take them beyond the instructions if you want! From 701591cf7de6057caa764b2e6a0afc2c7998adbc Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Mon, 30 Sep 2024 14:43:48 +1000 Subject: [PATCH 02/54] Remove smart quotes (they cause problems for some markdown parsers) --- .../verifiable-randomness-functions.md | 62 +++--- .../intro-to-solana/interact-with-wallets.md | 86 ++++---- .../intro-to-solana/intro-to-cryptography.md | 6 +- .../intro-to-custom-onchain-programs.md | 22 +- .../intro-to-solana/intro-to-reading-data.md | 6 +- .../intro-to-solana/intro-to-writing-data.md | 8 +- .../courses/mobile/intro-to-solana-mobile.md | 62 +++--- content/courses/mobile/mwa-deep-dive.md | 2 +- .../mobile/solana-mobile-dapps-with-expo.md | 2 +- .../cross-program-invocations.md | 28 +-- .../deserialize-custom-data-frontend.md | 44 ++-- .../deserialize-instruction-data.md | 10 +- ...paging-ordering-filtering-data-frontend.md | 76 +++---- .../program-security.md | 36 ++-- .../program-state-management.md | 2 +- .../serialize-instruction-data-frontend.md | 74 +++---- .../courses/onchain-development/anchor-cpi.md | 16 +- .../onchain-development/anchor-pdas.md | 34 +-- .../intro-to-anchor-frontend.md | 26 +-- .../onchain-development/intro-to-anchor.md | 26 +-- .../program-architecture.md | 86 ++++---- .../program-configuration.md | 4 +- .../program-optimization/rust-macros.md | 10 +- .../program-security/account-data-matching.md | 22 +- .../courses/program-security/arbitrary-cpi.md | 14 +- .../bump-seed-canonicalization.md | 10 +- .../program-security/closing-accounts.md | 12 +- .../duplicate-mutable-accounts.md | 8 +- .../courses/program-security/owner-checks.md | 2 +- .../courses/program-security/pda-sharing.md | 14 +- .../program-security/security-intro.md | 4 +- .../courses/program-security/signer-auth.md | 12 +- content/courses/solana-pay/solana-pay.md | 4 +- .../state-compression/compressed-nfts.md | 198 +++++++++--------- .../generalized-state-compression.md | 184 ++++++++-------- .../courses/token-extensions/close-mint.md | 2 +- .../token-extensions/default-account-state.md | 6 +- .../token-extensions/immutable-owner.md | 6 +- .../interest-bearing-token.md | 4 +- .../non-transferable-token.md | 2 +- .../token-extensions/permanent-delegate.md | 2 +- .../courses/token-extensions/required-memo.md | 4 +- .../courses/token-extensions/transfer-fee.md | 2 +- .../courses/tokens-and-nfts/token-program.md | 6 +- content/guides/advanced/stake-weighted-qos.md | 6 +- content/guides/games/hello-world.md | 14 +- content/guides/games/interact-with-tokens.md | 12 +- content/guides/games/store-sol-in-pda.md | 2 +- .../guides/getstarted/cosmwasm-to-solana.md | 4 +- content/guides/getstarted/intro-to-anchor.md | 2 +- .../getstarted/local-rust-hello-world.md | 4 +- content/guides/getstarted/rust-to-solana.md | 14 +- .../getstarted/scaffold-nextjs-anchor.md | 2 +- .../getstarted/solana-test-validator.md | 2 +- .../guides/javascript/get-program-accounts.md | 6 +- .../token-extensions/getting-started.md | 4 +- .../guides/token-extensions/transfer-hook.md | 4 +- .../add-solana-wallet-adapter-to-nextjs.md | 8 +- content/workshops/solana-101.md | 40 ++-- docs/advanced/confirmation.md | 82 ++++---- docs/advanced/retry.md | 28 +-- docs/economics/index.md | 2 +- docs/economics/inflation/terminology.md | 4 +- .../quick-start/cross-program-invocation.md | 2 +- docs/more/exchange.md | 4 +- docs/programs/testing.md | 12 +- 66 files changed, 752 insertions(+), 752 deletions(-) diff --git a/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md b/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md index 3f73823e1..3f4445c28 100644 --- a/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md +++ b/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md @@ -57,10 +57,10 @@ game as a seed. Unfortunately, neither type of randomness is natively available in Solana programs, because these programs have to be deterministic. All validators need -to come to the same conclusion. There is no way they’d all draw the same random -number, and if they used a seed, it’d be prone to attacks. See the +to come to the same conclusion. There is no way they'd all draw the same random +number, and if they used a seed, it'd be prone to attacks. See the [Solana FAQs](https://solana.com/docs/programs/lang-rust#depending-on-rand) for -more. So we’ll have to look outside of the blockchain for randomness with VRFs. +more. So we'll have to look outside of the blockchain for randomness with VRFs. ### What is Verifiable Randomness? @@ -68,7 +68,7 @@ A Verifiable Random Function (VRF) is a public-key pseudorandom function that provides proofs that its outputs were calculated correctly. This means we can use a cryptographic keypair to generate a random number with a proof, which can then be validated by anyone to ensure the value was calculated correctly without -the possibility of leaking the producer’s secret key. Once validated, the random +the possibility of leaking the producer's secret key. Once validated, the random value is stored onchain in an account. VRFs are a crucial component for achieving verifiable and unpredictable @@ -100,7 +100,7 @@ Switchboard is a decentralized Oracle network that offers VRFs on Solana. Oracles are services that provide external data to a blockchain, allowing them to interact with and respond to real-world events. The Switchboard network is made up of many different individual oracles run by third parties to provide -external data and service requests onchain. To learn more about Switchboard’s +external data and service requests onchain. To learn more about Switchboard's Oracle network, please refer to our [Oracle lesson](/content/courses/connecting-to-offchain-data/oracles.md) @@ -112,13 +112,13 @@ verified, the Switchboard program will execute a onchain callback defined by the VRF Account during account creation. From there the program can consume the random data. -You might be wondering how they get paid. In Switchboard’s VRF implementation, +You might be wondering how they get paid. In Switchboard's VRF implementation, you actually pay per request. ### Requesting and Consuming VRF Now that we know what a VRF is and how it fits into the Switchboard Oracle -network, let’s take a closer look at how to actually request and consume +network, let's take a closer look at how to actually request and consume randomness from a Solana program. At a high level, the process for requesting and consuming randomness from Switchboard looks like this: @@ -137,7 +137,7 @@ and consuming randomness from Switchboard looks like this: pseudorandom number returned from the Oracle. 7. Program consumes the random number and can execute business logic with it! -There are a lot of steps here, but don’t worry, we'll be going through each step +There are a lot of steps here, but don't worry, we'll be going through each step of the process in detail. First there are a couple of accounts that we will have to create ourselves to @@ -191,8 +191,8 @@ Some important fields on this account are `authority`, `oracle_queue`, and `callback`. The `authority` should be a PDA of the program that has the ability to request randomness on this `vrf` account. That way, only that program can provide the signature needed for the vrf request. The `oracle_queue` field -allows you to specify which specific oracle queue you’d like to service the vrf -requests made with this account. If you aren’t familiar with oracle queues on +allows you to specify which specific oracle queue you'd like to service the vrf +requests made with this account. If you aren't familiar with oracle queues on Switchboard, checkout the [Oracles lesson in the Connecting to Offchain Data course](/content/courses/connecting-to-offchain-data/oracles)! Lastly, the `callback` field is where you define the callback instruction the @@ -254,7 +254,7 @@ Now, you can create the `vrf` account. Now that we have all of our needed accounts we can finally call the `request_randomness` instruction on the Switchboard program. It's important to note you can invoke the `request_randomness` in a client or within a program -with a cross program invocation (CPI). Let’s take a look at what accounts are +with a cross program invocation (CPI). Let's take a look at what accounts are required for this request by checking out the Account struct definition in the actual [Switchboard program](https://github.com/switchboard-xyz/solana-sdk/blob/fbef37e4a78cbd8b8b6346fcb96af1e20204b861/rust/switchboard-solana/src/oracle_program/instructions/vrf_request_randomness.rs#L8). @@ -296,7 +296,7 @@ pub struct VrfRequestRandomness<'info> { } ``` -That’s a lot of accounts, let’s walk through each one and give them some +That's a lot of accounts, let's walk through each one and give them some context. - `authority` - PDA derived from our program @@ -320,7 +320,7 @@ context. [Recent Blockhashes Solana program](https://docs.rs/solana-program/latest/solana_program/sysvar/recent_blockhashes/index.html) - Token Program - Solana Token Program -That’s all the accounts needed for just the randomness request, now let's see +That's all the accounts needed for just the randomness request, now let's see what it looks like in a Solana program via CPI. To do this, we make use of the `VrfRequestRandomness` data struct from the [SwitchboardV2 rust crate.](https://github.com/switchboard-xyz/solana-sdk/blob/main/rust/switchboard-solana/src/oracle_program/instructions/vrf_request_randomness.rs) @@ -367,7 +367,7 @@ Ok(()) ``` Once the Switchboard program is invoked, it does some logic on its end and -assigns an oracle in the `vrf` account’s defined oracle queue to serve the +assigns an oracle in the `vrf` account's defined oracle queue to serve the randomness request. The assigned oracle then calculates a random value and sends it back to the Switchboard program. @@ -415,7 +415,7 @@ pub fn handler(ctx: Context) -> Result <()> { ``` Now you have randomness! Hooray! But there is one last thing we have not talked -about yet and that’s how the randomness is returned. Switchboard, gives you your +about yet and that's how the randomness is returned. Switchboard, gives you your randomness calling `[get_result()](https://github.com/switchboard-xyz/solana-sdk/blob/9dc3df8a5abe261e23d46d14f9e80a7032bb346c/rust/switchboard-solana/src/oracle_program/accounts/vrf.rs#L122)`. This method returns the `current_round.result` field of the `vrf` account @@ -440,11 +440,11 @@ the steps involved in a VRF request, review this diagram. ## Lab -For this lesson’s lab, we will be picking up where we left off in the +For this lesson's lab, we will be picking up where we left off in the [Oracle lesson](/content/courses/connecting-to-offchain-data/oracles). If you haven't completed the Oracle lesson and demo, we strongly recommend you do as -there are a lot of overlapping concepts and we’ll be starting from the Oracle -lesson’s codebase. +there are a lot of overlapping concepts and we'll be starting from the Oracle +lesson's codebase. If you don't want to complete the Oracle lesson, the starter code for this lab is provided for you in @@ -475,8 +475,8 @@ following: 6. `yarn install` 7. `anchor test` -When all tests pass we’re ready to begin. We will start by filling in some -boilerplate stuff, then we’ll implement the functions. +When all tests pass we're ready to begin. We will start by filling in some +boilerplate stuff, then we'll implement the functions. #### 2. Cargo.toml @@ -681,7 +681,7 @@ pub mod consume_randomness; Lastly, let's update our `deposit.rs` and `withdraw.rs` files to reflect our soon-to-be new powers. -First, let’s initialize our `out_of_jail` flag to `false` in `deposit.rs`. +First, let's initialize our `out_of_jail` flag to `false` in `deposit.rs`. ```rust // in deposit.rs @@ -720,8 +720,8 @@ check, going straight to our withdrawal. #### 8. Using VRF -Now that we have the boilerplate out of the way, let’s move on to our first -addition: initializing our VRF Client. Let’s create a new file called +Now that we have the boilerplate out of the way, let's move on to our first +addition: initializing our VRF Client. Let's create a new file called `init_vrf_client.rs` in the `/instructions` folder. We'll add the needed crates, then create the `InitVrfClient` context. We'll need @@ -731,7 +731,7 @@ the following accounts: - `escrow_account` - the burry escrow account created when the user locked their funds up. - `vrf_client_state` - account we will be creating in this instruction to hold - state about the user’s dice rolls. + state about the user's dice rolls. - `vrf` - Our VRF owned by the Switchboard program, we will create this account client-side before we call `init_vrf_client`. - `system_program` - The system program since we use the init macro for @@ -786,7 +786,7 @@ only have one `escrow_account`. Since there is only one, If you wanted to be thorough, you might want to implement a `close_vrf_state` function to get your rent back. -Now, let’s write some basic initialization logic for this function. First we +Now, let's write some basic initialization logic for this function. First we load and initialize our `vrf_state` account by calling `load_init()`. Then we fill in the values for each field. @@ -926,7 +926,7 @@ pub struct RequestRandomness<'info> { } ``` -Lastly, we'll create a new struct `RequestRandomnessParams`. We’ll be passing in +Lastly, we'll create a new struct `RequestRandomnessParams`. We'll be passing in some account's bumps client-side. ```rust @@ -1006,7 +1006,7 @@ If doubles are rolled, set the `out_of_jail` field on `vrf_state` to true. First, let's create the `ConsumeRandomness` context. Fortunately, it only takes three accounts. -- `escrow_account` - state account for user’s escrowed funds. +- `escrow_account` - state account for user's escrowed funds. - `vrf_state` - state account to hold information about dice roll. - `vrf` - account with the random number that was just calculated by the Switchboard network. @@ -1092,7 +1092,7 @@ pub fn consume_randomness_handler(ctx: Context) -> Result <() } ``` -Now it’s time to actually use the random result. Since we only use two dice we +Now it's time to actually use the random result. Since we only use two dice we only need the first two bytes of the buffer. To convert these random values into “dice rolls”, we use modular arithmetic. For anyone not familiar with modular arithmetic, @@ -1201,12 +1201,12 @@ Please make sure your program builds successfully by running `anchor build`. #### 11. Testing -Alright, let’s test our program. Historically, we'd need to test the VRF on +Alright, let's test our program. Historically, we'd need to test the VRF on Devnet. Fortunately, the folks at Switchboard have created some really nice -functions to let us run our own VRF oracle locally. For this, we’ll need to set +functions to let us run our own VRF oracle locally. For this, we'll need to set up our local server, grab all of the right accounts, and then call our program. -The first thing we’ll do is pull in some more accounts in our `Anchor.toml` +The first thing we'll do is pull in some more accounts in our `Anchor.toml` file: ```toml diff --git a/content/courses/intro-to-solana/interact-with-wallets.md b/content/courses/intro-to-solana/interact-with-wallets.md index 924a5ea11..27738df9f 100644 --- a/content/courses/intro-to-solana/interact-with-wallets.md +++ b/content/courses/intro-to-solana/interact-with-wallets.md @@ -16,8 +16,8 @@ description: "Connect with installed browser wallets from your React apps." software wallets are often **browser extensions** that add the ability to connect to a wallet from a website. On mobile, wallet apps have their own browsers. -- Solana’s **Wallet Adapter** allows you to build websites that can request a - user’s wallet address and propose transactions for them to sign +- Solana's **Wallet Adapter** allows you to build websites that can request a + user's wallet address and propose transactions for them to sign ## Lesson @@ -26,7 +26,7 @@ description: "Connect with installed browser wallets from your React apps." In the previous two lessons, we discussed keypairs. Keypairs are used to locate accounts and sign transactions. While the public key of a keypair is perfectly safe to share, the secret key should always be kept in a secure location. If a -user’s secret key is exposed, then a malicious actor could execute transactions +user's secret key is exposed, then a malicious actor could execute transactions with the authority of that user, allowing them to transfer all the assets inside. @@ -42,7 +42,7 @@ existing device(s). Both techniques allow websites to interact easily with the wallet, for example: -1. Seeing the wallet’s wallet address (their public key) +1. Seeing the wallet's wallet address (their public key) 2. Submitting transactions for a user's approval to sign 3. Sending signed transactions to the network @@ -51,20 +51,20 @@ transaction to your wallet and having the wallet handle the signing, you ensure that you never expose your secret key to the website. Instead, you only share the secret key with the wallet application. -Unless you’re creating a wallet application yourself, your code should never +Unless you're creating a wallet application yourself, your code should never need to ask a user for their secret key. Instead, you can ask users to connect to your site using a reputable wallet. -## Solana’s Wallet Adapter +## Solana's Wallet Adapter If you build web apps, and need users to be able to connect to their wallets and -sign transactions through your apps, you'll want Solana’s Wallet Adapter. Wallet +sign transactions through your apps, you'll want Solana's Wallet Adapter. Wallet Adapter is a suite of modular packages: - The core functionality is found in `@solana/wallet-adapter-base`. - React support is added by `@solana/wallet-adapter-react`. - Additional packages provide components for common UI frameworks. In this - lesson, and throughout this course, we’ll be using components from + lesson, and throughout this course, we'll be using components from `@solana/wallet-adapter-react-ui`. Finally, some packages are adapters for specific wallet apps. These are now no @@ -73,7 +73,7 @@ longer necessary in most cases - see below. ### Install Wallet-Adapter Libraries for React When adding wallet support to an existing React app, you start by installing the -appropriate packages. You’ll need `@solana/wallet-adapter-base`, +appropriate packages. You'll need `@solana/wallet-adapter-base`, `@solana/wallet-adapter-react`. If you plan to use the provided React components, you'll also need to add `@solana/wallet-adapter-react-ui`. @@ -135,7 +135,7 @@ export const Home: NextPage = props => { ``` Note that `ConnectionProvider` requires an `endpoint` property and that -`WalletProvider` requires a `wallets` property. We’re continuing to use the +`WalletProvider` requires a `wallets` property. We're continuing to use the endpoint for the Devnet cluster, and since all major Solana wallet applications support the Wallet Standard, we don't need any wallet-specific adapters. At this point, you can connect with `wallet.connect()`, which will instruct the wallet @@ -144,9 +144,9 @@ for transactions. ![wallet connection prompt](/public/assets/courses/unboxed/wallet-connect-prompt.png) -While you could do this in a `useEffect` hook, you’ll usually want to provide +While you could do this in a `useEffect` hook, you'll usually want to provide more sophisticated functionality. For example, you may want users to be able to -choose from a list of supported wallet applications or disconnect after they’ve +choose from a list of supported wallet applications or disconnect after they've already connected. ### @solana/wallet-adapter-react-ui @@ -194,7 +194,7 @@ export default Home; ``` The `WalletModalProvider` adds functionality for presenting a modal screen for -users to select which wallet they’d like to use. The `WalletMultiButton` changes +users to select which wallet they'd like to use. The `WalletMultiButton` changes behavior to match the connection status: ![multi button select wallet option](/public/assets/courses/unboxed/multi-button-select-wallet.png) @@ -219,7 +219,7 @@ functionality: Once your site is connected to a wallet, `useConnection` will retrieve a `Connection` object and `useWallet` will get the `WalletContextState`. `WalletContextState` has a property `publicKey` that is `null` when not -connected to a wallet and has the public key of the user’s account when a wallet +connected to a wallet and has the public key of the user's account when a wallet is connected. With a public key and a connection, you can fetch account info and more. @@ -312,14 +312,14 @@ const sendSol = async event => { ``` When this function is called, the connected wallet will display the transaction -for the user’s approval. If approved, then the transaction will be sent. +for the user's approval. If approved, then the transaction will be sent. ![wallet transaction approval prompt](/public/assets/courses/unboxed/wallet-transaction-approval-prompt.png) ## Lab -Let’s take the Ping program from the last lesson and build a frontend that lets -users approve a transaction that pings the program. As a reminder, the program’s +Let's take the Ping program from the last lesson and build a frontend that lets +users approve a transaction that pings the program. As a reminder, the program's public key is `ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa` and the public key for the data account is `Ah9K7dQ8EHaZqcAsgBW8w37yN2eAy3koFmUn4x3CJtod`. @@ -341,25 +341,25 @@ Then set your wallet to use Devnet, for example: - In Solflare, click **Settings** -> **General** -> **Network** -> **DevNet** - In Backpack, click **Preferences** -> **Developer Mode** -This ensures that your wallet app will be connected to the same network we’ll be +This ensures that your wallet app will be connected to the same network we'll be using in this lab. ### Download the starter code Download the [starter code for this project](https://github.com/Unboxed-Software/solana-ping-frontend/tree/starter). -This project is a simple Next.js application. It’s mostly empty except for the -`AppBar` component. We’ll build the rest throughout this lab. +This project is a simple Next.js application. It's mostly empty except for the +`AppBar` component. We'll build the rest throughout this lab. You can see its current state with the command `npm run dev` in the console. ### Wrap the app in context providers -To start, we’re going to create a new component to contain the various -Wallet-Adapter providers that we’ll be using. Create a new file inside the +To start, we're going to create a new component to contain the various +Wallet-Adapter providers that we'll be using. Create a new file inside the `components` folder called `WalletContextProvider.tsx`. -Let’s start with some of the boilerplate for a functional component: +Let's start with some of the boilerplate for a functional component: ```tsx import { FC, ReactNode } from "react"; @@ -373,7 +373,7 @@ const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => { export default WalletContextProvider; ``` -To properly connect to the user’s wallet, we’ll need a `ConnectionProvider`, +To properly connect to the user's wallet, we'll need a `ConnectionProvider`, `WalletProvider`, and `WalletModalProvider`. Start by importing these components from `@solana/wallet-adapter-react` and `@solana/wallet-adapter-react-ui`. Then add them to the `WalletContextProvider` component. Note that @@ -405,9 +405,9 @@ export default WalletContextProvider; The last things we need are an actual endpoint for `ConnectionProvider` and the supported wallets for `WalletProvider`. -For the endpoint, we’ll use the same `clusterApiUrl` function from the -`@solana/web3.js` library that we’ve used before so you’ll need to import it. -For the array of wallets you’ll also need to import the +For the endpoint, we'll use the same `clusterApiUrl` function from the +`@solana/web3.js` library that we've used before so you'll need to import it. +For the array of wallets you'll also need to import the `@solana/wallet-adapter-wallets` library. After importing these libraries, create a constant `endpoint` that uses the @@ -450,10 +450,10 @@ export default WalletContextProvider; ### Add wallet multi-button -Next, let’s set up the Connect button. The current button is just a placeholder +Next, let's set up the Connect button. The current button is just a placeholder because rather than using a standard button or creating a custom component, -we’ll be using Wallet-Adapter’s “multi-button.” This button interfaces with the -providers we set up in `WalletContextProvider` and let’s users choose a wallet, +we'll be using Wallet-Adapter's “multi-button.” This button interfaces with the +providers we set up in `WalletContextProvider` and let's users choose a wallet, connect to a wallet, and disconnect from a wallet. If you ever need more custom functionality, you can create a custom component to handle this. @@ -492,7 +492,7 @@ export default Home; If you run the app, everything should still look the same since the current button on the top right is still just a placeholder. To remedy this, open `AppBar.tsx` and replace `` with ``. -You’ll need to import `WalletMultiButton` from +You'll need to import `WalletMultiButton` from `@solana/wallet-adapter-react-ui`. ```tsx @@ -519,17 +519,17 @@ button to connect your wallet to the site. ### Create button to ping program -Now that our app can connect to our wallet, let’s make the “Ping!” button +Now that our app can connect to our wallet, let's make the “Ping!” button actually do something. -Start by opening the `PingButton.tsx` file. We’re going to replace the +Start by opening the `PingButton.tsx` file. We're going to replace the `console.log` inside of `onClick` with code that will create a transaction and -submit it to the wallet app for the end user’s approval. +submit it to the wallet app for the end user's approval. -First, we need a connection, the wallet’s public key, and Wallet-Adapter’s +First, we need a connection, the wallet's public key, and Wallet-Adapter's `sendTransaction` function. To get this, we need to import `useConnection` and -`useWallet` from `@solana/wallet-adapter-react`. While we’re here, let’s also -import `@solana/web3.js` since we’ll need it to create our transaction. +`useWallet` from `@solana/wallet-adapter-react`. While we're here, let's also +import `@solana/web3.js` since we'll need it to create our transaction. ```tsx import { useConnection, useWallet } from "@solana/wallet-adapter-react"; @@ -588,7 +588,7 @@ export const PingButton: FC = () => { With that, we can fill in the body of `onClick`. First, check that both `connection` and `publicKey` exist (if either does not -then the user’s wallet isn’t connected yet). +then the user's wallet isn't connected yet). Next, construct two instances of `PublicKey`, one for the program ID `ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa` and one for the data account @@ -633,17 +633,17 @@ const onClick = async () => { }; ``` -And that’s it! If you refresh the page, connect your wallet, and click the ping +And that's it! If you refresh the page, connect your wallet, and click the ping button, your wallet should present you with a popup to confirm the transaction. ### Add some polish -There’s a lot you could do to make the user experience here even better. For +There's a lot you could do to make the user experience here even better. For example, you could change the UI to only show you the Ping button when a wallet is connected and display some other prompt otherwise. You could link to the transaction on Solana Explorer after a user confirms a transaction so they can easily go look at the transaction details. The more you experiment with it, the -more comfortable you’ll get, so get creative! +more comfortable you'll get, so get creative! You can also download the [full source code from this lab](https://github.com/Unboxed-Software/solana-ping-frontend) @@ -651,7 +651,7 @@ to understand all of this in context. ## Challenge -Now it’s your turn to build something independently. Create an application that +Now it's your turn to build something independently. Create an application that lets a user connect their wallet and send SOL to another account. ![Send SOL App](/public/assets/courses/unboxed/solana-send-sol-app.png) @@ -659,7 +659,7 @@ lets a user connect their wallet and send SOL to another account. 1. You can build this from scratch or you can [download the starter code](https://github.com/Unboxed-Software/solana-send-sol-frontend/tree/starter). 2. Wrap the starter application in the appropriate context providers. -3. In the form component, set up the transaction and send it to the user’s +3. In the form component, set up the transaction and send it to the user's wallet for approval. 4. Get creative with the user experience. Add a link to let the user view the transaction on Solana Explorer or something else that seems cool to you! diff --git a/content/courses/intro-to-solana/intro-to-cryptography.md b/content/courses/intro-to-solana/intro-to-cryptography.md index 795850f41..6f1c3d83b 100644 --- a/content/courses/intro-to-solana/intro-to-cryptography.md +++ b/content/courses/intro-to-solana/intro-to-cryptography.md @@ -102,7 +102,7 @@ to install `@solana/web3.js` npm i @solana/web3.js ``` -We’ll cover a lot of +We'll cover a lot of [web3.js](https://solana.com/docs/clients/javascript-reference) gradually throughout this course, but you can also check out the [official web3.js documentation](https://solana.com/docs/clients/javascript-reference). @@ -135,7 +135,7 @@ store secret keys in source code. Instead, we: ### Loading an existing keypair -If you already have a keypair you’d like to use, you can load a `Keypair` from +If you already have a keypair you'd like to use, you can load a `Keypair` from an existing secret key stored in the filesystem or an `.env` file. In node.js, the `@solana-developers/helpers` npm package includes some extra functions: @@ -153,7 +153,7 @@ import { getKeypairFromEnvironment } from "@solana-developers/helpers"; const keypair = getKeypairFromEnvironment("SECRET_KEY"); ``` -You know how to make and load keypairs! Let’s practice what we’ve learned. +You know how to make and load keypairs! Let's practice what we've learned. ## Lab diff --git a/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md b/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md index c939d6c6b..c0b0b3400 100644 --- a/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md +++ b/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md @@ -27,7 +27,7 @@ In previous chapters, we used: `@metaplex-foundation/mpl-token-metadata@2` to make instructions to Metaplex to create token Metadata. -When working with other programs, however, you’ll need to create instructions +When working with other programs, however, you'll need to create instructions manually. With `@solana/web3.js`, you can create instructions with the `TransactionInstruction` constructor: @@ -47,7 +47,7 @@ const instruction = new TransactionInstruction({ `TransactionInstruction()` takes 3 fields: -- The `programId` field is fairly self-explanatory: it’s the public key (also +- The `programId` field is fairly self-explanatory: it's the public key (also called the 'address' or 'program ID') of the program. - `keys` is an array of accounts and how they will be used during the @@ -60,7 +60,7 @@ const instruction = new TransactionInstruction({ - `isWritable` - a boolean representing whether or not the account is written to during the transaction's execution -- an optional `Buffer` containing data to pass to the program. We’ll be ignoring +- an optional `Buffer` containing data to pass to the program. We'll be ignoring the `data` field for now, but we will revisit it in a future lesson. After making our instruction, we add it to a transaction, send the transaction @@ -99,7 +99,7 @@ for that signature in Solana Explorer, then see: ### Writing transactions for the ping counter program -We’re going to create a script to ping an onchain program that increments a +We're going to create a script to ping an onchain program that increments a counter each time it has been pinged. This program exists on the Solana Devnet at address `ChT1B39WKLS8qUrkLvFDXMhEJ4F1XZzwUNHUt4AU9aVa`. The program stores its data in a specific account at the address @@ -147,7 +147,7 @@ Now let's talk to the Ping program! To do this, we need to: Remember, the most challenging piece here is including the right information in the instructions. We know the address of the program that we are calling. We also know that the program writes data to a separate account whose address we -also have. Let’s add the string versions of both of those as constants at the +also have. Let's add the string versions of both of those as constants at the top of the file: ```typescript @@ -159,7 +159,7 @@ const PING_PROGRAM_DATA_ADDRESS = new web3.PublicKey( ); ``` -Now let’s create a new transaction, then initialize a `PublicKey` for the +Now let's create a new transaction, then initialize a `PublicKey` for the program account, and another for the data account. ```typescript @@ -168,7 +168,7 @@ const programId = new web3.PublicKey(PING_PROGRAM_ADDRESS); const pingProgramDataId = new web3.PublicKey(PING_PROGRAM_DATA_ADDRESS); ``` -Next, let’s create the instruction. Remember, the instruction needs to include +Next, let's create the instruction. Remember, the instruction needs to include the public key for the Ping program and it also needs to include an array with all the accounts that will be read from or written to. In this example program, only the data account referenced above is needed. @@ -190,9 +190,9 @@ const instruction = new web3.TransactionInstruction({ }); ``` -Next, let’s add this instruction to the transaction we created. Then, call +Next, let's add this instruction to the transaction we created. Then, call `sendAndConfirmTransaction()` by passing in the connection, transaction, and -payer. Finally, let’s log the result of that function call so we can look it up +payer. Finally, let's log the result of that function call so we can look it up on Solana Explorer. ```typescript @@ -251,10 +251,10 @@ console.log( ); ``` -And just like that you’re calling programs on the Solana network and writing +And just like that you're calling programs on the Solana network and writing data onchain! -In the next few lessons, you’ll learn how to +In the next few lessons, you'll learn how to 1. Send transactions safely from the browser instead of running a script 2. Add custom data to your instructions diff --git a/content/courses/intro-to-solana/intro-to-reading-data.md b/content/courses/intro-to-solana/intro-to-reading-data.md index 9ea60e35c..11c129b67 100644 --- a/content/courses/intro-to-solana/intro-to-reading-data.md +++ b/content/courses/intro-to-solana/intro-to-reading-data.md @@ -10,9 +10,9 @@ description: ## Summary -- **SOL** is the name of Solana’s native token. Each SOL is made from 1 billion +- **SOL** is the name of Solana's native token. Each SOL is made from 1 billion **Lamports**. -- **Accounts** store tokens, NFTs, programs, and data. For now, we’ll focus on +- **Accounts** store tokens, NFTs, programs, and data. For now, we'll focus on accounts that store SOL. - **Addresses** point to accounts on the Solana network. Anyone can read the data at a given address. Most addresses are also **public keys**. @@ -127,7 +127,7 @@ The balance of the account at CenYq6bDRB7p73EjsPEpiYN7uveyPUTdXkDkgUduboaN is 0. ## Lab -Let’s practice what we’ve learned, and check the balance at a particular +Let's practice what we've learned, and check the balance at a particular address. ### Load a keypair diff --git a/content/courses/intro-to-solana/intro-to-writing-data.md b/content/courses/intro-to-solana/intro-to-writing-data.md index 5e2999a12..deede360e 100644 --- a/content/courses/intro-to-solana/intro-to-writing-data.md +++ b/content/courses/intro-to-solana/intro-to-writing-data.md @@ -120,8 +120,8 @@ dropped with an error like: > Transaction simulation failed: Attempt to debit an account but found no record of a prior credit. ``` -If you get this error, it’s because your keypair is brand new and doesn’t have -any SOL to cover the transaction fees. Let’s fix this by adding the following +If you get this error, it's because your keypair is brand new and doesn't have +any SOL to cover the transaction fees. Let's fix this by adding the following lines just after we've set up the connection: ```typescript @@ -134,7 +134,7 @@ await airdropIfRequired( ``` This will deposit 1 SOL into your account which you can use for testing. This -won’t work on Mainnet where it would have value. But it's incredibly convenient +won't work on Mainnet where it would have value. But it's incredibly convenient for testing locally and on Devnet. You can also use the Solana CLI command `solana airdrop 1` to get free test SOL @@ -158,7 +158,7 @@ for that signature in the Solana Explorer, then see: ## Lab -We’re going to create a script to send SOL to other students. +We're going to create a script to send SOL to other students. ### Basic scaffolding diff --git a/content/courses/mobile/intro-to-solana-mobile.md b/content/courses/mobile/intro-to-solana-mobile.md index 956e139b1..cf6a9002b 100644 --- a/content/courses/mobile/intro-to-solana-mobile.md +++ b/content/courses/mobile/intro-to-solana-mobile.md @@ -54,7 +54,7 @@ you hold your own keys. **Mobile Gaming with Solana Micropayments** -Mobile games account for roughly 50% of the video game industry’s total value, +Mobile games account for roughly 50% of the video game industry's total value, largely due to small in-game purchases. However, payment processing fees usually mean these in-game purchases have a minimum of $0.99 USD. With Solana, it's possible to unlock true micropayments. Need an extra life? That'll be 0.0001 @@ -66,7 +66,7 @@ SMS can enable a new wave of mobile e-commerce shoppers to pay directly from their favorite Solana wallet. Imagine a world where you can use your Solana wallet as seamlessly as you can use Apple Pay. -To summarize, mobile crypto opens up many doors. Let’s dive in and learn how we +To summarize, mobile crypto opens up many doors. Let's dive in and learn how we can be part of it: #### How Solana development differs between native mobile apps and web @@ -106,7 +106,7 @@ is pushed to the background. This kills the MWA WebSocket connection. This is an inherent design difference between iOS and Android (probably made to preserve battery, network usage, etc). -However, this doesn’t mean that Solana dApps can’t run on iOS at all. You can +However, this doesn't mean that Solana dApps can't run on iOS at all. You can still create a Mobile Web App using the [standard wallet adapter](https://github.com/solana-labs/wallet-adapter) library. Your users can then install a mobile-friendly wallet like @@ -197,7 +197,7 @@ about soon. Reading data from a Solana cluster in React Native is the exact same as in React. You use the `useConnection` hook to grab the `Connection` object. Using -that, you can get account info. Since reading is free, we don’t need to actually +that, you can get account info. Since reading is free, we don't need to actually connect to the wallet. ```tsx @@ -279,7 +279,7 @@ const sendTransactions = (transaction: Transaction) => { #### Debugging Since two applications are involved in sending transactions, debugging can be -tricky. Specifically, you won’t be able to see the wallet's debug logs the way +tricky. Specifically, you won't be able to see the wallet's debug logs the way you can see your dApps logs. Fortunately, @@ -288,7 +288,7 @@ makes it possible to see logs from all applications on your device. If you prefer not to use Logcat, the other method you could try is to only use the wallet to sign transactions, and then send them in your code. This allows -you to better debug the transaction if you’re running into problems. +you to better debug the transaction if you're running into problems. #### Releasing @@ -300,8 +300,8 @@ First, most of the mobile app marketplaces have policies restricting blockchain involvement. Crypto is new enough that it's a regulatory wildcard. Platforms feel they're protecting users by being strict with blockchain-related apps. -Second, if you use crypto for "purchases" in-app, you’ll be seen as -circumnavigating the platform’s fee (anywhere from 15-30%). This is explicitly +Second, if you use crypto for "purchases" in-app, you'll be seen as +circumnavigating the platform's fee (anywhere from 15-30%). This is explicitly against app store policies as the platform is trying to protect its revenue stream. @@ -345,8 +345,8 @@ with React Native. The app will interact with the Anchor counter program that we made in the [Intro to client-side Anchor development](https://www.soldev.app/course/intro-to-anchor-frontend) lesson. This dApp simply displays a counter and allows users to increment the -count through a Solana program. In this app, we’ll be able to see the current -count, connect our wallet, and increment the count. We’ll be doing this all on +count through a Solana program. In this app, we'll be able to see the current +count, connect our wallet, and increment the count. We'll be doing this all on Devnet and will be compiling only for Android. This program already exists and is already deployed on Devnet. Feel free to @@ -354,7 +354,7 @@ check out the [deployed program's code](https://github.com/Unboxed-Software/anchor-ping-frontend/tree/solution-decrement) if you want more context. -We’ll write this application in vanilla React Native without a starting +We'll write this application in vanilla React Native without a starting template. Solana Mobile provides a [React Native template](https://docs.solanamobile.com/react-native/react-native-scaffold) that shortcuts some of the boilerplate, but there's no better way to learn than @@ -410,16 +410,16 @@ few prerequisite setup items: ![Fake Wallet](/public/assets/courses/unboxed/basic-solana-mobile-fake-wallet.png) - 4. For debugging, you’ll want to use `Logcat`. Now that your fake wallet is + 4. For debugging, you'll want to use `Logcat`. Now that your fake wallet is running on the emulator, go to `View -> Tool Windows -> Logcat`. This will - open up a console logging out what’s happening with fake wallet. + open up a console logging out what's happening with fake wallet. 3. (Optional) Install other [Solana wallets](https://solana.com/ecosystem/explore?categories=wallet) on the Google Play store. -Lastly, if you run into Java versioning issues - you’ll want to be on Java -version 11. To check what you’re currently running type `java --version` in your +Lastly, if you run into Java versioning issues - you'll want to be on Java +version 11. To check what you're currently running type `java --version` in your terminal. #### 1. Plan out the App's Structure @@ -439,7 +439,7 @@ files we'll be creating and working with. #### 2. Create the App -Now that we've got some of the basic setup and structure down, let’s scaffold a +Now that we've got some of the basic setup and structure down, let's scaffold a new app with the following command: ```bash @@ -457,12 +457,12 @@ npm run android ``` This should open and run the app in your Android emulator. If you run into -problems, check to make sure you’ve accomplished everything in the +problems, check to make sure you've accomplished everything in the [prerequisites section](#0-prerequisites). #### 3. Install Dependencies -We’ll need to add in our Solana dependencies. +We'll need to add in our Solana dependencies. [The Solana Mobile docs provide a nice list of packages](https://docs.solanamobile.com/react-native/setup) and explanations for why we need them: @@ -484,8 +484,8 @@ In addition to this list, we'll add two more packages: - `assert`: A polyfill that lets Anchor do its thing. - `text-encoding-polyfill`: A polyfill needed to create the `Program` object -If you’re not familiar: polyfills actively replace Node-native libraries to make -them work anywhere Node is not running. We’ll finish our polyfill setup shortly. +If you're not familiar: polyfills actively replace Node-native libraries to make +them work anywhere Node is not running. We'll finish our polyfill setup shortly. For now, install dependencies using the following command: ```bash @@ -502,7 +502,7 @@ npm install \ #### 4. Create ConnectionProvider.tsx -Let’s start adding our Solana functionality. Create a new folder called +Let's start adding our Solana functionality. Create a new folder called `components` and within it, a file called `ConnectionProvider.tsx`. This provider will wrap the entire application and make our `Connection` object available throughout. Hopefully, you're noticing a pattern: this is identical to @@ -550,9 +550,9 @@ export const useConnection = (): ConnectionContextState => #### 5. Create AuthProvider.tsx -The next Solana provision we’ll need is the auth provider. This is one of the -main differences between mobile and web development. What we’re implementing -here is roughly equivalent to the `WalletProvider` that we’re used to in web +The next Solana provision we'll need is the auth provider. This is one of the +main differences between mobile and web development. What we're implementing +here is roughly equivalent to the `WalletProvider` that we're used to in web apps. However, since we're using Android and its natively installed wallets, the flow to connect and utilize them is a bit different. Most notably, we need to follow the MWA protocol. @@ -570,14 +570,14 @@ We do this by providing the following in our `AuthProvider`: - `deauthorizeSession(wallet)`: Deauthorizes the `wallet`. - `onChangeAccount`: Acts as a handler when `selectedAccount` is changed. -We’re also going to throw in some utility methods: +We're also going to throw in some utility methods: - `getPublicKeyFromAddress(base64Address)`: Creates a new Public Key object from the Base64 address given from the `wallet` object - `getAuthorizationFromAuthResult`: Handles the authorization result, extracts relevant data from the result, and returns the `Authorization` context object -We’ll expose all of this through a `useAuthorization` hook. +We'll expose all of this through a `useAuthorization` hook. Since this provider is the same across virtually all apps, we're going to give you the full implementation that you can copy/paste. We'll dig into the details @@ -877,7 +877,7 @@ export const useProgram = () => useContext(ProgramContext); #### 7. Modify App.tsx -Now that we have all our providers, let’s wrap our app with them. We're going to +Now that we have all our providers, let's wrap our app with them. We're going to re-write the default `App.tsx` with the following changes: - Import our providers and add in our polyfills @@ -923,7 +923,7 @@ export default function App() { #### 8. Create MainScreen.tsx -Now, let’s put everything together to create our UI. Create a new folder called +Now, let's put everything together to create our UI. Create a new folder called `screens` and a new file called `MainScreen.tsx` inside of it. In this file, we are only structuring the screen to display two yet-to-be-created components: `CounterView` and `CounterButton`. @@ -979,7 +979,7 @@ export function MainScreen() { The `CounterView` is the first of our two program-specific files. `CounterView`'s only job is to fetch and listen for updates on our `Counter` -account. Since we’re only listening here, we don’t have to do anything +account. Since we're only listening here, we don't have to do anything MWA-related. It should look identical to a web application. We'll use our `Connection` object to listen for the `programAddress` specified in `ProgramProvider.tsx`. When the account is changed, we update the UI. @@ -1183,7 +1183,7 @@ export function CounterButton() { #### 11. Build and Run -Now it’s time to test that everything works! Build and run with the following +Now it's time to test that everything works! Build and run with the following command: ```bash @@ -1205,7 +1205,7 @@ to fix them: wallet installed ( like the fake wallet we installed in Prerequisites ) - You get stuck in a forever loop while calling `increment` → This is likely due to you reaching a Devnet airdrop rate limit. Take out the airdrop section in - `CounterButton` and manually send some Devnet sol to your wallet’s address + `CounterButton` and manually send some Devnet sol to your wallet's address (printed in the console) That's it! You've made your first Solana Mobile dApp. If you get stuck, feel diff --git a/content/courses/mobile/mwa-deep-dive.md b/content/courses/mobile/mwa-deep-dive.md index db3ed3a55..08d232afe 100644 --- a/content/courses/mobile/mwa-deep-dive.md +++ b/content/courses/mobile/mwa-deep-dive.md @@ -255,7 +255,7 @@ transact(async (wallet: Web3MobileWallet) => { Every time you want to call these methods, you will have to call `wallet.authorize()` or `wallet.reauthorize()`. -When invoking `wallet.signAndSendTransactions(...)`, it’s essential to handle +When invoking `wallet.signAndSendTransactions(...)`, it's essential to handle transaction failures gracefully. Transactions can fail due to various reasons such as network issues, signature mismatches, or insufficient funds. Proper error handling ensures a smooth user experience, even when the transaction diff --git a/content/courses/mobile/solana-mobile-dapps-with-expo.md b/content/courses/mobile/solana-mobile-dapps-with-expo.md index bf876a873..16ee816f3 100644 --- a/content/courses/mobile/solana-mobile-dapps-with-expo.md +++ b/content/courses/mobile/solana-mobile-dapps-with-expo.md @@ -304,7 +304,7 @@ eas login #### 2. Create the app scaffold -Let’s create our app with the following: +Let's create our app with the following: ```bash npx create-expo-app -t expo-template-blank-typescript solana-expo diff --git a/content/courses/native-onchain-development/cross-program-invocations.md b/content/courses/native-onchain-development/cross-program-invocations.md index a20e7999c..dff3df609 100644 --- a/content/courses/native-onchain-development/cross-program-invocations.md +++ b/content/courses/native-onchain-development/cross-program-invocations.md @@ -223,7 +223,7 @@ if a signature is required on behalf of a PDA. For that, you'll need to use Using `invoke_signed` is a little different just because there is an additional field that requires the seeds used to derive any PDAs that must sign the transaction. You may recall from previous lessons that PDAs do not lie on the -Ed25519 curve and, therefore, do not have a corresponding secret key. You’ve +Ed25519 curve and, therefore, do not have a corresponding secret key. You've been told that programs can provide signatures for their PDAs, but have not learned how that actually happens - until now. Programs provide signatures for their PDAs with the `invoke_signed` function. The first two fields of @@ -259,9 +259,9 @@ signer. #### Security checks There are some common mistakes and things to remember when utilizing CPIs that -are important to your program’s security and robustness. The first thing to +are important to your program's security and robustness. The first thing to remember is that, as we know by now, we have no control over what information is -passed into our programs. For this reason, it’s important to always verify the +passed into our programs. For this reason, it's important to always verify the `program_id`, accounts, and data passed into the CPI. Without these security checks, someone could submit a transaction that invokes an instruction on a completely different program than was expected, which is not ideal. @@ -269,7 +269,7 @@ completely different program than was expected, which is not ideal. Fortunately, there are inherent checks on the validity of any PDAs that are marked as signers within the `invoke_signed` function. All other accounts and `instruction_data` should be verified somewhere in your program code before -making the CPI. It's also important to make sure you’re targeting the intended +making the CPI. It's also important to make sure you're targeting the intended instruction on the program you are invoking. The easiest way to do this is to read the source code of the program you will be invoking just as you would if you were constructing an instruction from the client side. @@ -319,11 +319,11 @@ To see this in action, view this CPIs are a very important feature of the Solana ecosystem and they make all programs deployed interoperable with each other. With CPIs there is no need to re-invent the wheel when it comes to development. This creates the opportunity -for building new protocols and applications on top of what’s already been built, -just like building blocks or Lego bricks. It’s important to remember that CPIs +for building new protocols and applications on top of what's already been built, +just like building blocks or Lego bricks. It's important to remember that CPIs are a two-way street and the same is true for any programs that you deploy! If you build something cool and useful, developers have the ability to build on top -of what you’ve done or just plug your protocol into whatever it is that they are +of what you've done or just plug your protocol into whatever it is that they are building. Composability is a big part of what makes crypto so unique and CPIs are what makes this possible on Solana. @@ -345,7 +345,7 @@ gone through prior lessons, the Movie Review program allows users to submit movie reviews and have them stored in PDA accounts. Last lesson, we added the ability to leave comments on other movie reviews using -PDAs. In this lesson, we’re going to work on having the program mint tokens to +PDAs. In this lesson, we're going to work on having the program mint tokens to the reviewer or commenter anytime a review or comment is submitted. To implement this, we'll have to invoke the SPL Token Program's `MintTo` @@ -357,7 +357,7 @@ forward with this lab. #### 1. Get starter code and add dependencies To get started, we will be using the final state of the Movie Review program -from the previous PDA lesson. So, if you just completed that lesson then you’re +from the previous PDA lesson. So, if you just completed that lesson then you're all set and ready to go. If you are just jumping in here, no worries, you can [download the starter code here](https://github.com/Unboxed-Software/solana-movie-program/tree/solution-add-comments). We'll be using the `solution-add-comments` branch as our starting point. @@ -386,7 +386,7 @@ to be passed in: - `token_mint` - the mint address of the token - `mint_auth` - address of the authority of the token mint -- `user_ata` - user’s associated token account for this mint (where the tokens +- `user_ata` - user's associated token account for this mint (where the tokens will be minted) - `token_program` - address of the token program @@ -414,7 +414,7 @@ let token_program = next_account_info(account_info_iter)?; There is no additional `instruction_data` required for the new functionality, so no changes need to be made to how data is deserialized. The only additional -information that’s needed is the extra accounts. +information that's needed is the extra accounts. #### 4. Mint tokens to the reviewer in `add_movie_review` @@ -429,7 +429,7 @@ use spl_token::{instruction::initialize_mint, ID as TOKEN_PROGRAM_ID}; ``` Now we can move on to the logic that handles the actual minting of the tokens! -We’ll be adding this to the very end of the `add_movie_review` function right +We'll be adding this to the very end of the `add_movie_review` function right before `Ok(())` is returned. Minting tokens requires a signature by the mint authority. Since the program @@ -538,7 +538,7 @@ will mint ten tokens to the reviewer when a review is created. #### 5. Repeat for `add_comment` Our updates to the `add_comment` function will be almost identical to what we -did for the `add_movie_review` function above. The only difference is that we’ll +did for the `add_movie_review` function above. The only difference is that we'll change the amount of tokens minted for a comment from ten to five so that adding reviews are weighted above commenting. First, update the accounts with the same four additional accounts as in the `add_movie_review` function. @@ -799,7 +799,7 @@ pub fn initialize_token_mint(program_id: &Pubkey, accounts: &[AccountInfo]) -> P #### 7. Build and deploy -Now we’re ready to build and deploy our program! You can build the program by +Now we're ready to build and deploy our program! You can build the program by running `cargo build-bpf` and then running the command that is returned, it should look something like `solana program deploy `. diff --git a/content/courses/native-onchain-development/deserialize-custom-data-frontend.md b/content/courses/native-onchain-development/deserialize-custom-data-frontend.md index 69fc63fd4..a9a57d73a 100644 --- a/content/courses/native-onchain-development/deserialize-custom-data-frontend.md +++ b/content/courses/native-onchain-development/deserialize-custom-data-frontend.md @@ -3,7 +3,7 @@ title: Deserialize Program Data objectives: - Explain Program Derived Accounts - Derive PDAs given specific seeds - - Fetch a program’s accounts + - Fetch a program's accounts - Use Borsh to deserialize custom data description: Deserialize instructions in JS/TS clients to send to your native program. @@ -23,7 +23,7 @@ description: ## Lesson In the last lesson, we serialized program data that was subsequently stored -onchain by a Solana program. In this lesson, we’ll cover in greater detail how +onchain by a Solana program. In this lesson, we'll cover in greater detail how programs store data on the chain, how to retrieve data, and how to deserialize the data they store. @@ -51,7 +51,7 @@ can be signed for by the program address used to create them. PDAs and the data inside them can be consistently found based on the program address, bump, and seeds. To find a PDA, the program ID and seeds of the -developer’s choice (like a string of text) are passed through the +developer's choice (like a string of text) are passed through the [`findProgramAddress()`](https://solana-labs.github.io/solana-web3.js/classes/PublicKey.html#findProgramAddress) function. @@ -75,10 +75,10 @@ const [pda, bump] = await findProgramAddress( ##### Example: program with user-specific data -In programs that store user-specific data, it’s common to use a user’s public -key as the seed. This separates each user’s data into its own PDA. The -separation makes it possible for the client to locate each user’s data by -finding the address using the program ID and the user’s public key. +In programs that store user-specific data, it's common to use a user's public +key as the seed. This separates each user's data into its own PDA. The +separation makes it possible for the client to locate each user's data by +finding the address using the program ID and the user's public key. ```typescript import { PublicKey } from "@solana/web3.js"; @@ -94,8 +94,8 @@ const [pda, bump] = await PublicKey.findProgramAddressSync( When there are multiple data items per user, a program may use more seeds to create and identify accounts. For example, in a note-taking app there may be one -account per note where each PDA is derived with the user’s public key and the -note’s title. +account per note where each PDA is derived with the user's public key and the +note's title. ```typescript const [pda, bump] = await PublicKey.findProgramAddressSync( @@ -138,9 +138,9 @@ fetchProgramAccounts(); ### Deserializing program data The `data` property on an `AccountInfo` object is a buffer. To use it -efficiently, you’ll need to write code that deserializes it into something more +efficiently, you'll need to write code that deserializes it into something more usable. This is similar to the serialization process we covered last lesson. -Just as before, we’ll use [Borsh](https://borsh.io/) and `@coral-xyz/borsh`. If +Just as before, we'll use [Borsh](https://borsh.io/) and `@coral-xyz/borsh`. If you need a refresher on either of these, have a look at the previous lesson. Deserializing requires knowledge of the account layout ahead of time. When @@ -180,22 +180,22 @@ const { playerId, name } = borshAccountSchema.decode(buffer); ## Lab -Let’s practice this together by continuing to work on the Movie Review app from -the last lesson. No worries if you’re just jumping into this lesson - it should +Let's practice this together by continuing to work on the Movie Review app from +the last lesson. No worries if you're just jumping into this lesson - it should be possible to follow either way. As a refresher, this project uses a Solana program deployed on Devnet which lets users review movies. Last lesson, we added functionality to the frontend skeleton letting users submit movie reviews but the list of reviews is still -showing mock data. Let’s fix that by fetching the program’s storage accounts and +showing mock data. Let's fix that by fetching the program's storage accounts and deserializing the data stored there. ![movie review frontend](/public/assets/courses/movie-review-frontend-dapp.png) #### 1. Download the starter code -If you didn’t complete the lab from the last lesson or just want to make sure -that you didn’t miss anything, you can download the +If you didn't complete the lab from the last lesson or just want to make sure +that you didn't miss anything, you can download the [starter code](https://github.com/solana-developers/movie-review-frontend/tree/solution-serialize-instruction-data). The project is a fairly simple Next.js application. It includes the @@ -205,7 +205,7 @@ list, a `Form` component for submitting a new review, and a `Movie.ts` file that contains a class definition for a `Movie` object. Note that when you run `npm run dev`, the reviews displayed on the page are -mocks. We’ll be swapping those out for the real deal. +mocks. We'll be swapping those out for the real deal. #### 2. Create the buffer layout @@ -226,7 +226,7 @@ PDA's `data`: 3. `title` as a string representing the title of the reviewed movie. 4. `description` as a string representing the written portion of the review. -Let’s configure a `borsh` layout in the `Movie` class to represent the movie +Let's configure a `borsh` layout in the `Movie` class to represent the movie account data layout. Start by importing `@coral-xyz/borsh`. Next, create a `borshAccountSchema` static property and set it to the appropriate `borsh` struct containing the properties listed above. @@ -255,7 +255,7 @@ structured. #### 3. Create a method to deserialize data -Now that we have the buffer layout set up, let’s create a static method in +Now that we have the buffer layout set up, let's create a static method in `Movie` called `deserialize` that will take an optional `Buffer` and return a `Movie` object or `null`. @@ -296,7 +296,7 @@ export class Movie { ``` The method first checks whether or not the buffer exists and returns `null` if -it doesn’t. Next, it uses the layout we created to decode the buffer, then uses +it doesn't. Next, it uses the layout we created to decode the buffer, then uses the data to construct and return an instance of `Movie`. If the decoding fails, the method logs the error and returns `null`. @@ -352,7 +352,7 @@ At this point, you should be able to run the app and see the list of movie reviews retrieved from the program! Depending on how many reviews have been submitted, this may take a long time to -load or may lock up your browser entirely. But don’t worry — next lesson we’ll +load or may lock up your browser entirely. But don't worry — next lesson we'll learn how to page and filter accounts so you can be more surgical with what you load. @@ -363,7 +363,7 @@ before continuing. ## Challenge -Now it’s your turn to build something independently. Last lesson, you worked on +Now it's your turn to build something independently. Last lesson, you worked on the Student Intros app to serialize instruction data and send a new intro to the network. Now, it's time to fetch and deserialize the program's account data. Remember, the Solana program that supports this is at diff --git a/content/courses/native-onchain-development/deserialize-instruction-data.md b/content/courses/native-onchain-development/deserialize-instruction-data.md index 5cf2b27b4..ddd5aa918 100644 --- a/content/courses/native-onchain-development/deserialize-instruction-data.md +++ b/content/courses/native-onchain-development/deserialize-instruction-data.md @@ -66,7 +66,7 @@ let mut mutable_age = 33; mutable_age = 34; ``` -The Rust compiler ensures that immutable variables cannot change, so you don’t +The Rust compiler ensures that immutable variables cannot change, so you don't have to track it yourself. This makes your code easier to reason through and simplifies debugging. @@ -86,7 +86,7 @@ struct User { } ``` -To use a struct after it’s defined, create an instance of the struct by +To use a struct after it's defined, create an instance of the struct by specifying concrete values for each of the fields. ```rust @@ -364,8 +364,8 @@ There is Rust syntax in this function that we haven't explained yet. The leaves the `Ok` value unchanged. - [`?` operator](https://doc.rust-lang.org/rust-by-example/error/result/enter_question_mark.html): - Unwraps a `Result` or `Option`. If it’s `Ok` or `Some`, it returns the value. - If it’s an `Err` or `None`, it propagates the error up to the calling + Unwraps a `Result` or `Option`. If it's `Ok` or `Some`, it returns the value. + If it's an `Err` or `None`, it propagates the error up to the calling function. ### Program logic @@ -441,7 +441,7 @@ pub enum NoteInstruction { ... } ## Lab -For this lesson’s lab, you'll build the first half of the Movie Review program +For this lesson's lab, you'll build the first half of the Movie Review program from Module 1, focusing on deserializing instruction data. The next lesson will cover the remaining implementation. diff --git a/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md b/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md index cf84f50d6..2d9a4137d 100644 --- a/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md +++ b/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md @@ -3,7 +3,7 @@ title: Page, Order, and Filter Program Data objectives: - Page, order, and filter accounts - Prefetch accounts without data - - Determine where in an account’s buffer layout specific data is stored + - Determine where in an account's buffer layout specific data is stored - Prefetch accounts with a subset of data that can be used to order accounts - Fetch only accounts whose data matches specific criteria - Fetch a subset of total accounts using `getMultipleAccounts` @@ -22,8 +22,8 @@ description: "Learn how to efficiently query account data from Solana." ## Lesson You may have noticed in the last lesson that while we could fetch and display a -list of account data, we didn’t have any granular control over how many accounts -to fetch or their order. In this lesson, we’ll learn about some configuration +list of account data, we didn't have any granular control over how many accounts +to fetch or their order. In this lesson, we'll learn about some configuration options for the `getProgramAccounts` function that will enable things like paging, ordering accounts, and filtering. @@ -47,7 +47,7 @@ only return the subset of the data buffer that you specified. #### Paging Accounts One area where this becomes helpful is with paging. If you want to have a list -that displays all accounts but there are so many accounts that you don’t want to +that displays all accounts but there are so many accounts that you don't want to pull all the data at once, you can fetch all of the accounts but not fetch their data by using a `dataSlice` of `{ offset: 0, length: 0 }`. You can then map the result to a list of account keys whose data you can fetch only when needed. @@ -74,7 +74,7 @@ const deserializedObjects = accountInfos.map(accountInfo => { #### Ordering Accounts The `dataSlice` option is also helpful when you need to order a list of accounts -while paging. You still don’t want to fetch all the data at once, but you do +while paging. You still don't want to fetch all the data at once, but you do need all of the keys and a way to order them upfront. In this case, you need to understand the layout of the account data and configure the data slice to only be the data you need to use for ordering. @@ -86,7 +86,7 @@ For example, you might have an account that stores contact information like so: - `firstName` as a string - `secondName` as a string -If you want to order all of the account keys alphabetically based on the user’s +If you want to order all of the account keys alphabetically based on the user's first name, you need to find out the offset where the name starts. The first field, `initialized`, takes the first byte, then `phoneNumber` takes another 8, so the `firstName` field starts at offset `1 + 8 = 9`. However, dynamic data @@ -94,12 +94,12 @@ fields in borsh use the first 4 bytes to record the length of the data, so we can skip an additional 4 bytes, making the offset 13. You then need to determine the length to make the data slice. Since the length -is variable, we can’t know for sure before fetching the data. But you can choose +is variable, we can't know for sure before fetching the data. But you can choose a length that is large enough to cover most cases and short enough to not be too much of a burden to fetch. 15 bytes is plenty for most first names but would result in a small enough download even with a million users. -Once you’ve fetched accounts with the given data slice, you can use the `sort` +Once you've fetched accounts with the given data slice, you can use the `sort` method to sort the array before mapping it to an array of public keys. ```tsx @@ -161,7 +161,7 @@ accounts.sort((a, b) => { const accountKeys = accounts.map(account => account.pubkey); ``` -Note that in the snippet above we don’t compare the data as given. This is +Note that in the snippet above we don't compare the data as given. This is because for dynamically sized types like strings, Borsh places an unsigned, 32-bit (4 byte) integer at the start to indicate the length of the data representing that field. So to compare the first names directly, we need to get @@ -171,7 +171,7 @@ proper length. ### Use `filters` to only retrieve specific accounts Limiting the data received per account is great, but what if you only want to -return accounts that match a specific criteria rather than all of them? That’s +return accounts that match a specific criteria rather than all of them? That's where the `filters` configuration option comes in. This option is an array that can have objects matching the following: @@ -233,19 +233,19 @@ async function fetchMatchingContactAccounts( Two things to note in the example above: -1. We’re setting the offset to 13 because we determined previously that the +1. We're setting the offset to 13 because we determined previously that the offset for `firstName` in the data layout is 9 and we want to additionally skip the first 4 bytes indicating the length of the string. -2. We’re using a third-party library +2. We're using a third-party library `bs58`` to perform base-58 encoding on the search term. You can install it using `npm install bs58`. ## Lab -Remember that Movie Review app we worked on in the last two lessons? We’re going +Remember that Movie Review app we worked on in the last two lessons? We're going to spice it up a little by paging the review list, ordering the reviews so they -aren’t so random, and adding some basic search functionality. No worries if -you’re just jumping into this lesson without having looked at the previous +aren't so random, and adding some basic search functionality. No worries if +you're just jumping into this lesson without having looked at the previous ones - as long as you have the prerequisite knowledge, you should be able to follow the lab without having worked in this specific project yet. @@ -253,8 +253,8 @@ follow the lab without having worked in this specific project yet. #### **1. Download the starter code** -If you didn’t complete the lab from the last lesson or just want to make sure -that you didn’t miss anything, you can download the +If you didn't complete the lab from the last lesson or just want to make sure +that you didn't miss anything, you can download the [starter code](https://github.com/solana-developers/movie-review-frontend/tree/solutions-deserialize-account-data). The project is a fairly simple Next.js application. It includes the @@ -265,10 +265,10 @@ contains a class definition for a `Movie` object. #### 2. Add paging to the reviews -First things first, let’s create a space to encapsulate the code for fetching +First things first, let's create a space to encapsulate the code for fetching account data. Create a new file `MovieCoordinator.ts` and declare a -`MovieCoordinator` class. Then let’s move the `MOVIE_REVIEW_PROGRAM_ID` constant -from `MovieList` into this new file since we’ll be moving all references to it +`MovieCoordinator` class. Then let's move the `MOVIE_REVIEW_PROGRAM_ID` constant +from `MovieList` into this new file since we'll be moving all references to it ```tsx const MOVIE_REVIEW_PROGRAM_ID = "CenYq6bDRB7p73EjsPEpiYN7uveyPUTdXkDkgUduboaN"; @@ -281,11 +281,11 @@ note before we dive in: this will be as simple a paging implementation as possible so that we can focus on the complex part of interacting with Solana accounts. You can, and should, do better for a production application. -With that out of the way, let’s create a static property `accounts` of type +With that out of the way, let's create a static property `accounts` of type `web3.PublicKey[]`, a static function `prefetchAccounts(connection: web3.Connection)`, and a static function `fetchPage(connection: web3.Connection, page: number, perPage: number): Promise>`. -You’ll also need to import `@solana/web3.js` and `Movie`. +You'll also need to import `@solana/web3.js` and `Movie`. ```tsx import { Connection, PublicKey, AccountInfo } from "@solana/web3.js"; @@ -306,7 +306,7 @@ export class MovieCoordinator { } ``` -The key to paging is to prefetch all the accounts without data. Let’s fill in +The key to paging is to prefetch all the accounts without data. Let's fill in the body of `prefetchAccounts` to do this and set the retrieved public keys to the static `accounts` property. @@ -327,8 +327,8 @@ static async prefetchAccounts(connection: Connection) { } ``` -Now, let’s fill in the `fetchPage` method. First, if the accounts haven’t been -prefetched yet, we’ll need to do that. Then, we can get the account public keys +Now, let's fill in the `fetchPage` method. First, if the accounts haven't been +prefetched yet, we'll need to do that. Then, we can get the account public keys that correspond to the requested page and call `connection.getMultipleAccountsInfo`. Finally, we deserialize the account data and return the corresponding `Movie` objects. @@ -433,7 +433,7 @@ At this point, you should be able to run the project and click between pages! #### 3. Order reviews alphabetically by title -If you look at the reviews, you might notice they aren’t in any specific order. +If you look at the reviews, you might notice they aren't in any specific order. We can fix this by adding back just enough data into our data slice to help us do some sorting. The various properties in the movie review data buffer are laid out as follows @@ -445,17 +445,17 @@ out as follows Based on this, the offset we need to provide to the data slice to access `title` is 2. The length, however, is indeterminate, so we can just provide what seems -to be a reasonable length. I’ll stick with 18 as that will cover the length of +to be a reasonable length. I'll stick with 18 as that will cover the length of most titles without fetching too much data every time. -Once we’ve modified the data slice in `getProgramAccounts`, we then need to +Once we've modified the data slice in `getProgramAccounts`, we then need to actually sort the returned array. To do this, we need to compare the part of the data buffer that actually corresponds to `title`. The first 4 bytes of a dynamic field in Borsh are used to store the length of the field in bytes. So in any given buffer `data` that is sliced the way we discussed above, the string portion is `data.slice(4, 4 + data[0])`. -Now that we’ve thought through this, let’s modify the implementation of +Now that we've thought through this, let's modify the implementation of `prefetchAccounts` in `MovieCoordinator`: ```tsx @@ -517,15 +517,15 @@ reviews ordered alphabetically. #### 4. Add search -The last thing we’ll do to improve this app is to add some basic search -capability. Let’s add a `search` parameter to `prefetchAccounts` and reconfigure +The last thing we'll do to improve this app is to add some basic search +capability. Let's add a `search` parameter to `prefetchAccounts` and reconfigure the body of the function to use it. We can use the `filters` property of the `config` parameter of `getProgramAccounts` to filter accounts by specific data. The offset to the `title` fields is 2, but the first 4 bytes are the length of the title so the actual offset to the string itself is 6. Remember that the bytes need to be base -58 encoded, so let’s install and import `bs58`. +58 encoded, so let's install and import `bs58`. ```tsx import bs58 from 'bs58' @@ -580,7 +580,7 @@ static async prefetchAccounts(connection: Connection, search: string) { ``` Now, add a `search` parameter to `fetchPage` and update its call to -`prefetchAccounts` to pass it along. We’ll also need to add a `reload` boolean +`prefetchAccounts` to pass it along. We'll also need to add a `reload` boolean parameter to `fetchPage` so that we can force a refresh of the account prefetching every time the search value changes. @@ -625,7 +625,7 @@ static async fetchPage( } ``` -With that in place, let’s update the code in `MovieList` to call this properly. +With that in place, let's update the code in `MovieList` to call this properly. First, add `const [search, setSearch] = useState('')` near the other `useState` calls. Then update the call to `MovieCoordinator.fetchPage` in the `useEffect` @@ -673,7 +673,7 @@ return ( ); ``` -And that’s it! The app now has ordered reviews, paging, and search. +And that's it! The app now has ordered reviews, paging, and search. That was a lot to digest, but you made it through. If you need to spend some more time with the concepts, feel free to reread the sections that were most @@ -682,7 +682,7 @@ challenging for you and/or have a look at the ## Challenge -Now it’s your turn to try and do this on your own. Using the Student Intros app +Now it's your turn to try and do this on your own. Using the Student Intros app from last lesson, add paging, ordering alphabetically by name, and searching by name. @@ -691,9 +691,9 @@ name. 1. You can build this from scratch or you can download the [starter code](https://github.com/solana-developers/solana-student-intro-frontend/tree/solution-deserialize-account-data) 2. Add paging to the project by prefetching accounts without data, then only - fetching the account data for each account when it’s needed. + fetching the account data for each account when it's needed. 3. Order the accounts displayed in the app alphabetically by name. -4. Add the ability to search through introductions by a student’s name. +4. Add the ability to search through introductions by a student's name. This is challenging. If you get stuck, feel free to reference the [solution code](https://github.com/solana-developers/solana-student-intro-frontend/tree/solution-paging-account-data). diff --git a/content/courses/native-onchain-development/program-security.md b/content/courses/native-onchain-development/program-security.md index bdf694fcd..1bbba4de8 100644 --- a/content/courses/native-onchain-development/program-security.md +++ b/content/courses/native-onchain-development/program-security.md @@ -39,7 +39,7 @@ trying to exploit your program, anticipating failure points is essential to secure program development. Remember, **you have no control over the transactions that will be sent to your -program once it’s deployed**. You can only control how your program handles +program once it's deployed**. You can only control how your program handles them. While this lesson is far from a comprehensive overview of program security, we'll cover some of the basic pitfalls to look out for. @@ -281,8 +281,8 @@ To avoid integer overflow and underflow, either: ## Lab -Let’s practice together with the Movie Review program we've worked on in -previous lessons. No worries if you’re just jumping into this lesson without +Let's practice together with the Movie Review program we've worked on in +previous lessons. No worries if you're just jumping into this lesson without having done the previous lesson - it should be possible to follow along either way. @@ -325,7 +325,7 @@ Since we'll be allowing updates to movie reviews, we also changed `account_len` in the `add_movie_review` function (now in `processor.rs`). Instead of calculating the size of the review and setting the account length to only as large as it needs to be, we're simply going to allocate 1000 bytes to each -review account. This way, we don’t have to worry about reallocating size or +review account. This way, we don't have to worry about reallocating size or re-calculating rent when a user updates their movie review. We went from this: @@ -343,7 +343,7 @@ let account_len: usize = 1000; The [realloc](https://docs.rs/solana-sdk/latest/solana_sdk/account_info/struct.AccountInfo.html#method.realloc) method was just recently enabled by Solana Labs which allows you to dynamically change the size of your accounts. We will not be using this method for this lab, but -it’s something to be aware of. +it's something to be aware of. Finally, we've also implemented some additional functionality for our `MovieAccountState` struct in `state.rs` using the `impl` keyword. @@ -418,7 +418,7 @@ Note that in addition to adding the error cases, we also added the implementation that lets us convert our error into a `ProgramError` type as needed. -Before moving on, let’s bring `ReviewError` into scope in the `processor.rs`. We +Before moving on, let's bring `ReviewError` into scope in the `processor.rs`. We will be using these errors shortly when we add our security checks. ```rust @@ -455,8 +455,8 @@ if !initializer.is_signer { Next, let's make sure the `pda_account` passed in by the user is the `pda` we expect. Recall we derived the `pda` for a movie review using the `initializer` -and `title` as seeds. Within our instruction we’ll derive the `pda` again and -then check if it matches the `pda_account`. If the addresses do not match, we’ll +and `title` as seeds. Within our instruction we'll derive the `pda` again and +then check if it matches the `pda_account`. If the addresses do not match, we'll return our custom `InvalidPDA` error. ```rust @@ -474,7 +474,7 @@ if pda != *pda_account.key { Now let's perform some data validation. We'll start by making sure `rating` falls within the 1 to 5 scale. If the rating -provided by the user outside of this range, we’ll return our custom +provided by the user outside of this range, we'll return our custom `InvalidRating` error. ```rust @@ -484,8 +484,8 @@ if rating > 5 || rating < 1 { } ``` -Next, let’s check that the content of the review does not exceed the 1000 bytes -we’ve allocated for the account. If the size exceeds 1000 bytes, we’ll return +Next, let's check that the content of the review does not exceed the 1000 bytes +we've allocated for the account. If the size exceeds 1000 bytes, we'll return our custom `InvalidDataLength` error. ```rust @@ -597,7 +597,7 @@ pub fn add_movie_review( Now that `add_movie_review` is more secure, let's turn our attention to supporting the ability to update a movie review. -Let’s begin by updating `instruction.rs`. We’ll start by adding an +Let's begin by updating `instruction.rs`. We'll start by adding an `UpdateMovieReview` variant to `MovieInstruction` that includes embedded data for the new title, rating, and description. @@ -730,11 +730,11 @@ if pda_account.owner != program_id { #### Signer Check -Next, let’s perform a signer check to verify that the `initializer` of the +Next, let's perform a signer check to verify that the `initializer` of the update instruction has also signed the transaction. Since we are updating the data for a movie review, we want to ensure that the original `initializer` of the review has approved the changes by signing the transaction. If the -`initializer` did not sign the transaction, we’ll return an error. +`initializer` did not sign the transaction, we'll return an error. ```rust if !initializer.is_signer { @@ -745,9 +745,9 @@ if !initializer.is_signer { #### Account Validation -Next, let’s check that the `pda_account` passed in by the user is the PDA we +Next, let's check that the `pda_account` passed in by the user is the PDA we expect by deriving the PDA using `initializer` and `title` as seeds. If the -addresses do not match, we’ll return our custom `InvalidPDA` error. We'll +addresses do not match, we'll return our custom `InvalidPDA` error. We'll implement this the same way we did in the `add_movie_review` function. ```rust @@ -787,7 +787,7 @@ if !account_data.is_initialized() { Next, we need to validate the `rating`, `title`, and `description` data just like in the `add_movie_review` function. We want to limit the `rating` to a scale of 1 to 5 and limit the overall size of the review to be fewer than 1000 -bytes. If the rating provided by the user outside of this range, then we’ll +bytes. If the rating provided by the user outside of this range, then we'll return our custom `InvalidRating` error. If the review is too long, then we'll return our custom `InvalidDataLength` error. @@ -912,7 +912,7 @@ continuing. ## Challenge -Now it’s your turn to build something independently by building on top of the +Now it's your turn to build something independently by building on top of the Student Intro program that you've used in previous lessons. If you haven't been following along or haven't saved your code from before, feel free to use [this starter code](https://beta.solpg.io/62b11ce4f6273245aca4f5b2). diff --git a/content/courses/native-onchain-development/program-state-management.md b/content/courses/native-onchain-development/program-state-management.md index 1f858b9f6..f8dd25ff8 100644 --- a/content/courses/native-onchain-development/program-state-management.md +++ b/content/courses/native-onchain-development/program-state-management.md @@ -554,7 +554,7 @@ program. `movieProgramId` in the `index.ts` component with the public key of the program you've deployed. - If you use the frontend, simply replace the `MOVIE_REVIEW_PROGRAM_ID` in the - `review-form.tsx` components with the address of the program you’ve deployed. + `review-form.tsx` components with the address of the program you've deployed. Then run the frontend, submit a view, and refresh the browser to see the review. If you need more time with this project to feel comfortable with these concepts, diff --git a/content/courses/native-onchain-development/serialize-instruction-data-frontend.md b/content/courses/native-onchain-development/serialize-instruction-data-frontend.md index 798a3ac0d..410b2abf2 100644 --- a/content/courses/native-onchain-development/serialize-instruction-data-frontend.md +++ b/content/courses/native-onchain-development/serialize-instruction-data-frontend.md @@ -27,7 +27,7 @@ description: How to deserialize data fetched from Solana accounts. buffer. To facilitate this process of serialization, we will be using [Borsh](https://borsh.io/). - Transactions can fail to be processed by the blockchain for any number of - reasons, we’ll discuss some of the most common ones here. + reasons, we'll discuss some of the most common ones here. ## Lesson @@ -86,14 +86,14 @@ if every instruction succeeds then the transaction as a whole will be successful, but if a single instruction fails then the entire transaction will fail immediately with no side-effects. -The account array is not just an array of the accounts’ public keys. Each object -in the array includes the account’s public key, whether or not it is a signer on +The account array is not just an array of the accounts' public keys. Each object +in the array includes the account's public key, whether or not it is a signer on the transaction, and whether or not it is writable. Including whether or not an account is writable during the execution of an instruction allows the runtime to facilitate parallel processing of smart contracts. Because you must define which accounts are read-only and which you will write to, the runtime can determine which transactions are non-overlapping or read-only and allow them to execute -concurrently. To learn more about Solana’s runtime, check out this +concurrently. To learn more about Solana's runtime, check out this [blog post on Sealevel](https://solana.com/news/sealevel---parallel-processing-thousands-of-smart-contracts). #### Instruction Data @@ -104,17 +104,17 @@ an HTTP request lets you build dynamic and flexible REST APIs. Just as the structure of the body of an HTTP request is dependent on the endpoint you intend to call, the structure of the byte buffer used as -instruction data is entirely dependent on the recipient program. If you’re -building a full-stack dApp on your own, then you’ll need to copy the same +instruction data is entirely dependent on the recipient program. If you're +building a full-stack dApp on your own, then you'll need to copy the same structure that you used when building the program over to the client-side code. -If you’re working with another developer who is handling the program +If you're working with another developer who is handling the program development, you can coordinate to ensure matching buffer layouts. -Let’s think about a concrete example. Imagine working on a Web3 game and being +Let's think about a concrete example. Imagine working on a Web3 game and being responsible for writing client-side code that interacts with a player inventory program. The program was designed to allow the client to: -- Add inventory based on a player’s game-play results +- Add inventory based on a player's game-play results - Transfer inventory from one player to another - Equip a player with selected inventory items @@ -125,11 +125,11 @@ Each program, however, only has one entry point. You would instruct the program on which of these functions to run through the instruction data. You would also include in the instruction data any information the function -needs to execute properly, e.g. an inventory item’s ID, a player to transfer +needs to execute properly, e.g. an inventory item's ID, a player to transfer inventory to, etc. Exactly _how_ this data would be structured would depend on how the program was -written, but it’s common to have the first field in instruction data be a number +written, but it's common to have the first field in instruction data be a number that the program can map to a function, after which additional fields act as function arguments. @@ -145,10 +145,10 @@ in Solana is [Borsh](https://borsh.io). Per the website: Borsh maintains a [JS library](https://github.com/near/borsh-js) that handles serializing common types into a buffer. There are also other packages built on -top of Borsh that try to make this process even easier. We’ll be using the +top of Borsh that try to make this process even easier. We'll be using the `@coral-xyz/borsh` library which can be installed using `npm`. -Building off of the previous game inventory example, let’s look at a +Building off of the previous game inventory example, let's look at a hypothetical scenario where we are instructing the program to equip a player with a given item. Assume the program is designed to accept a buffer that represents a struct with the following properties: @@ -176,9 +176,9 @@ const equipPlayerSchema = borsh.struct([ You can then encode data using this schema with the `encode` method. This method accepts as arguments an object representing the data to be serialized and a -buffer. In the below example, we allocate a new buffer that’s much larger than +buffer. In the below example, we allocate a new buffer that's much larger than needed, then encode the data into that buffer and slice the original buffer down -into a new buffer that’s only as large as needed. +into a new buffer that's only as large as needed. ```typescript import * as borsh from "@coral-xyz/borsh"; @@ -198,13 +198,13 @@ equipPlayerSchema.encode( const instructionBuffer = buffer.subarray(0, equipPlayerSchema.getSpan(buffer)); ``` -Once a buffer is properly created and the data serialized, all that’s left is -building the transaction. This is similar to what you’ve done in previous +Once a buffer is properly created and the data serialized, all that's left is +building the transaction. This is similar to what you've done in previous lessons. The example below assumes that: - `player`, `playerInfoAccount`, and `PROGRAM_ID` are already defined somewhere outside the code snippet -- `player` is a user’s public key +- `player` is a user's public key - `playerInfoAccount` is the public key of the account where inventory changes will be written - `SystemProgram` will be used in the process of executing the instruction. @@ -277,8 +277,8 @@ try { ## Lab -Let’s practice this together by building a Movie Review app that lets users -submit a movie review and have it stored on Solana’s network. We’ll build this +Let's practice this together by building a Movie Review app that lets users +submit a movie review and have it stored on Solana's network. We'll build this app a little bit at a time over the next few lessons, adding new functionality each lesson. @@ -288,7 +288,7 @@ Here's a quick diagram of the program we'll build: ![Solana stores data items in PDAs, which can be found using their seeds](/public/assets/courses/unboxed/movie-review-program.svg) -The public key of the Solana program we’ll use for this application is +The public key of the Solana program we'll use for this application is `CenYq6bDRB7p73EjsPEpiYN7uveyPUTdXkDkgUduboaN`. #### 1. Download the starter code @@ -303,8 +303,8 @@ list, a `Form` component for submitting a new review, and a `Movie.ts` file that contains a class definition for a `Movie` object. Note that for now, the movies displayed on the page when you run `npm run dev` -are mocks. In this lesson, we’ll focus on adding a new review but we won’t be -able to see that review displayed. Next lesson, we’ll focus on deserializing +are mocks. In this lesson, we'll focus on adding a new review but we won't be +able to see that review displayed. Next lesson, we'll focus on deserializing custom data from onchain accounts. #### 2. Create the buffer layout @@ -322,7 +322,7 @@ data to contain: 4. `description` as a string representing the written portion of the review you are leaving for the movie. -Let’s configure a `borsh` layout in the `Movie` class. Start by importing +Let's configure a `borsh` layout in the `Movie` class. Start by importing `@coral-xyz/borsh`. Next, create a `borshInstructionSchema` property and set it to the appropriate `borsh` struct containing the properties listed above. @@ -350,8 +350,8 @@ how the program is structured, the transaction will fail. #### 3. Create a method to serialize data -Now that we have the buffer layout set up, let’s create a method in `Movie` -called `serialize()` that will return a `Buffer` with a `Movie` object’s +Now that we have the buffer layout set up, let's create a method in `Movie` +called `serialize()` that will return a `Buffer` with a `Movie` object's properties encoded into the appropriate layout. Instead of allocating a fixed buffer size, we'll calculate the size dynamically @@ -437,7 +437,7 @@ send the transaction when a user submits the form. Open `Form.tsx` and locate the `handleTransactionSubmit` function. This gets called by `handleSubmit` each time a user submits the Movie Review form. -Inside this function, we’ll be creating and sending the transaction that +Inside this function, we'll be creating and sending the transaction that contains the data submitted through the form. Start by importing `@solana/web3.js` and importing `useConnection` and @@ -495,7 +495,7 @@ export const Form: FC = () => { } ``` -Before we implement `handleTransactionSubmit`, let’s talk about what needs to be +Before we implement `handleTransactionSubmit`, let's talk about what needs to be done. We need to: 1. Check that `publicKey` exists to ensure that the user has connected their @@ -506,12 +506,12 @@ done. We need to: 4. Get all of the accounts that the transaction will read or write. 5. Create a new `Instruction` object that includes all of these accounts in the `keys` argument, includes the buffer in the `data` argument, and includes the - program’s public key in the `programId` argument. + program's public key in the `programId` argument. 6. Add the instruction from the last step to the transaction. 7. Call `sendTransaction`, passing in the assembled transaction. -That’s quite a lot to process! But don’t worry, it gets easier the more you do -it. Let’s start with the first 3 steps from above: +That's quite a lot to process! But don't worry, it gets easier the more you do +it. Let's start with the first 3 steps from above: ```typescript const handleTransactionSubmit = async (movie: Movie) => { @@ -527,8 +527,8 @@ const handleTransactionSubmit = async (movie: Movie) => { The next step is to get all of the accounts that the transaction will read or write. In past lessons, the account where data will be stored has been given to -you. This time, the account’s address is more dynamic, so it needs to be -computed. We’ll cover this in-depth in the next lesson, but for now, you can use +you. This time, the account's address is more dynamic, so it needs to be +computed. We'll cover this in-depth in the next lesson, but for now, you can use the following, where `pda` is the address to the account where data will be stored: @@ -598,9 +598,9 @@ const handleTransactionSubmit = async (movie: Movie) => { }; ``` -And that’s it! You should now be able to use the form on the site to submit a -movie review. While you won’t see the UI update to reflect the new review, you -can look at the transaction’s program logs on Solana Explorer to see that it was +And that's it! You should now be able to use the form on the site to submit a +movie review. While you won't see the UI update to reflect the new review, you +can look at the transaction's program logs on Solana Explorer to see that it was successful. If you need a bit more time with this project to feel comfortable, have a look @@ -609,7 +609,7 @@ at the complete ## Challenge -Now it’s your turn to build something independently. Create an application that +Now it's your turn to build something independently. Create an application that lets students of this course introduce themselves! The Solana program that supports this is at `HdE95RSVsdb315jfJtaykXhXY478h53X6okDupVfY9yf`. diff --git a/content/courses/onchain-development/anchor-cpi.md b/content/courses/onchain-development/anchor-cpi.md index 7202f7c16..5c96a7456 100644 --- a/content/courses/onchain-development/anchor-cpi.md +++ b/content/courses/onchain-development/anchor-cpi.md @@ -330,10 +330,10 @@ pub enum MyError { ## Lab -Let’s practice the concepts we’ve gone over in this lesson by building on top of +Let's practice the concepts we've gone over in this lesson by building on top of the Movie Review program from previous lessons. -In this lab we’ll update the program to mint tokens to users when they submit a +In this lab we'll update the program to mint tokens to users when they submit a new movie review. @@ -342,7 +342,7 @@ new movie review. To get started, we will be using the final state of the Anchor Movie Review program from the previous lesson. So, if you just completed that lesson then -you’re all set and ready to go. If you are just jumping in here, no worries, you +you're all set and ready to go. If you are just jumping in here, no worries, you can [download the starter code](https://github.com/Unboxed-Software/anchor-movie-review-program/tree/solution-pdas). We'll be using the `solution-pdas` branch as our starting point. @@ -410,7 +410,7 @@ pub fn initialize_token_mint(_ctx: Context) -> Result<()> { ### Anchor Error -Next, let’s create an Anchor Error that we’ll use to validate the following: +Next, let's create an Anchor Error that we'll use to validate the following: - The `rating` passed to either the `add_movie_review` or `update_movie_review` instruction. @@ -432,7 +432,7 @@ enum MovieReviewError { ### Update add_movie_review instruction -Now that we've done some setup, let’s update the `add_movie_review` instruction +Now that we've done some setup, let's update the `add_movie_review` instruction and `AddMovieReview` context type to mint tokens to the reviewer. Next, update the `AddMovieReview` context type to add the following accounts: @@ -485,7 +485,7 @@ been initialized, it will be initialized as an associated token account for the specified mint and authority. Also, the payer for the costs related with the account initialization will be set under the constraint `payer`. -Next, let’s update the `add_movie_review` instruction to do the following: +Next, let's update the `add_movie_review` instruction to do the following: - Check that `rating` is valid. If it is not a valid rating, return the `InvalidRating` error. @@ -493,7 +493,7 @@ Next, let’s update the `add_movie_review` instruction to do the following: `TitleTooLong` error. - Check that `description` length is valid. If it is not a valid length, return the `DescriptionTooLong` error. -- Make a CPI to the token program’s `mint_to` instruction using the mint +- Make a CPI to the token program's `mint_to` instruction using the mint authority PDA as a signer. Note that we'll mint 10 tokens to the user but need to adjust for the mint decimals by making it `10*10^6`. @@ -608,7 +608,7 @@ pub fn update_movie_review( ### Test -Those are all of the changes we need to make to the program! Now, let’s update +Those are all of the changes we need to make to the program! Now, let's update our tests. Start by making sure your imports and `describe` function look like this: diff --git a/content/courses/onchain-development/anchor-pdas.md b/content/courses/onchain-development/anchor-pdas.md index 9f7d80ecb..b2a55c9e3 100644 --- a/content/courses/onchain-development/anchor-pdas.md +++ b/content/courses/onchain-development/anchor-pdas.md @@ -235,7 +235,7 @@ To use `init_if_needed`, you must first enable the feature in `Cargo.toml`. anchor-lang = { version = "0.30.1", features = ["init-if-needed"] } ``` -Once you’ve enabled the feature, you can include the constraint in the +Once you've enabled the feature, you can include the constraint in the `#[account(…)]` attribute macro. The example below demonstrates using the `init_if_needed` constraint to initialize a new associated token account if one does not already exist. @@ -348,7 +348,7 @@ The `close` constraint provides a simple and secure way to close an existing account. The `close` constraint marks the account as closed at the end of the -instruction’s execution by setting its discriminator to a _special value_ called +instruction's execution by setting its discriminator to a _special value_ called `CLOSED_ACCOUNT_DISCRIMINATOR` and sends its lamports to a specified account. This _special value_ prevents the account from being reopened because any attempt to reinitialize the account will fail the discriminator check. @@ -372,7 +372,7 @@ pub struct Close<'info> { ## Lab -Let’s practice the concepts we’ve gone over in this lesson by creating a Movie +Let's practice the concepts we've gone over in this lesson by creating a Movie Review program using the Anchor framework. This program will allow users to: @@ -385,7 +385,7 @@ This program will allow users to: ### Create a new Anchor project -To begin, let’s create a new project using `anchor init`. +To begin, let's create a new project using `anchor init`. ```bash anchor init anchor-movie-review-program @@ -428,14 +428,14 @@ pub mod anchor_movie_review_program { ### MovieAccountState -First, let’s use the `#[account]` attribute macro to define the +First, let's use the `#[account]` attribute macro to define the `MovieAccountState` that will represent the data structure of the movie review accounts. As a reminder, the `#[account]` attribute macro implements various traits that help with serialization and deserialization of the account, set the discriminator for the account, and set the owner of a new account as the program ID defined in the `declare_id!` macro. -Within each movie review account, we’ll store the: +Within each movie review account, we'll store the: - `reviewer` - user creating the review - `rating` - rating for the movie @@ -504,8 +504,8 @@ more detail in the next chapter. ### Add Movie Review -Next, let’s implement the `add_movie_review` instruction. The `add_movie_review` -instruction will require a `Context` of type `AddMovieReview` that we’ll +Next, let's implement the `add_movie_review` instruction. The `add_movie_review` +instruction will require a `Context` of type `AddMovieReview` that we'll implement shortly. The instruction will require three additional arguments as instruction data @@ -515,8 +515,8 @@ provided by a reviewer: - `description` - details of the review as a `String` - `rating` - rating for the movie as a `u8` -Within the instruction logic, we’ll populate the data of the new `movie_review` -account with the instruction data. We’ll also set the `reviewer` field as the +Within the instruction logic, we'll populate the data of the new `movie_review` +account with the instruction data. We'll also set the `reviewer` field as the `initializer` account from the instruction context. We will also perform some checks, using the `require!` macro, to make sure that: @@ -568,7 +568,7 @@ pub mod anchor_movie_review_program{ } ``` -Next, let’s create the `AddMovieReview` struct that we used as the generic in +Next, let's create the `AddMovieReview` struct that we used as the generic in the instruction's context. This struct will list the accounts the `add_movie_review` instruction requires. @@ -610,7 +610,7 @@ pub struct AddMovieReview<'info> { ### Update Movie Review -Next, let’s implement the `update_movie_review` instruction with a context whose +Next, let's implement the `update_movie_review` instruction with a context whose generic type is `UpdateMovieReview`. Just as before, the instruction will require three additional arguments as @@ -620,7 +620,7 @@ instruction data provided by a reviewer: - `description` - details of the review - `rating` - rating for the movie -Within the instruction logic we’ll update the `rating` and `description` stored +Within the instruction logic we'll update the `rating` and `description` stored on the `movie_review` account. While the `title` doesn't get used in the instruction function itself, we'll @@ -654,7 +654,7 @@ pub mod anchor_movie_review_program { } ``` -Next, let’s create the `UpdateMovieReview` struct to define the accounts that +Next, let's create the `UpdateMovieReview` struct to define the accounts that the `update_movie_review` instruction needs. Since the `movie_review` account will have already been initialized by this @@ -698,7 +698,7 @@ expanding the space allocated to the account. ### Delete Movie Review -Lastly, let’s implement the `delete_movie_review` instruction to close an +Lastly, let's implement the `delete_movie_review` instruction to close an existing `movie_review` account. We'll use a context whose generic type is `DeleteMovieReview` and won't include @@ -722,7 +722,7 @@ pub mod anchor_movie_review_program { } ``` -Next, let’s implement the `DeleteMovieReview` struct. +Next, let's implement the `DeleteMovieReview` struct. ```rust #[derive(Accounts)] @@ -869,7 +869,7 @@ continuing. ## Challenge -Now it’s your turn to build something independently. Equipped with the concepts +Now it's your turn to build something independently. Equipped with the concepts introduced in this lesson, try to recreate the Student Intro program that we've used before using the Anchor framework. diff --git a/content/courses/onchain-development/intro-to-anchor-frontend.md b/content/courses/onchain-development/intro-to-anchor-frontend.md index 78443f151..7d2a419bb 100644 --- a/content/courses/onchain-development/intro-to-anchor-frontend.md +++ b/content/courses/onchain-development/intro-to-anchor-frontend.md @@ -16,7 +16,7 @@ description: - An **IDL** is a file representing the structure of a Solana program. Programs written and built using Anchor automatically generate a corresponding IDL. IDL stands for Interface Description Language. -- `@coral-xyz/anchor` is a Typescript client that includes everything you’ll +- `@coral-xyz/anchor` is a Typescript client that includes everything you'll need to interact with Anchor programs - An **Anchor `Provider`** object combines a `connection` to a cluster and a specified `wallet` to enable transaction signing @@ -288,7 +288,7 @@ The `Provider` object combines two things: - `Wallet` - a specified address used to pay for and sign transactions The `Provider` is then able to send transactions to the Solana blockchain on -behalf of a `Wallet` by including the wallet’s signature to outgoing +behalf of a `Wallet` by including the wallet's signature to outgoing transactions. When using a frontend with a Solana wallet provider, all outgoing transactions must still be approved by the user via their wallet browser extension. @@ -361,7 +361,7 @@ The `AnchorProvider` constructor takes three parameters: - `opts` - optional parameter that specifies the confirmation options, using a default setting if one is not provided -Once you’ve created the `Provider` object, you then set it as the default +Once you've created the `Provider` object, you then set it as the default provider using `setProvider`. ```typescript @@ -559,7 +559,7 @@ const accounts = await program.account.counter.fetchMultiple([ ## Lab -Let’s practice this together by building a frontend for the Counter program from +Let's practice this together by building a frontend for the Counter program from last lesson. As a reminder, the Counter program has two instructions: - `initialize` - initializes a new `Counter` account and sets the `count` to `0` @@ -576,14 +576,14 @@ This project is a simple Next.js application, created using `npx create-next-dapp` The `idl.json` file for the Counter program, and the `Initialize` and -`Increment` components we’ll be building throughout this lab. +`Increment` components we'll be building throughout this lab. #### 2. `Initialize` -To begin, let’s complete the setup to create the `useCounterProgram` hook in +To begin, let's complete the setup to create the `useCounterProgram` hook in `components/counter/counter-data-access.tsx` component. -Remember, we’ll need an instance of `Program` to use the Anchor `MethodsBuilder` +Remember, we'll need an instance of `Program` to use the Anchor `MethodsBuilder` to invoke the instructions on our program. `create-solana-dapp` already creates a `getCounterProgram` for us, which will return us the `Program` instance. @@ -607,7 +607,7 @@ const program = getCounterProgram(provider); Now that we've the program instance, we can actually invoke the program's `initialize` instruction. We'll do this using `useMutation`. -Remember, We’ll need to generate a new `Keypair` for the new `Counter` account +Remember, We'll need to generate a new `Keypair` for the new `Counter` account since we are initializing an account for the first time. ```typescript @@ -658,7 +658,7 @@ created. This method internally calls, `getProgramAccounts`. #### 4. `Increment` -Next, let’s move on the the `useCounterProgramAccount` hook. As we have earlier +Next, let's move on the the `useCounterProgramAccount` hook. As we have earlier already created `program` and `accounts` function in previous hook, we'll call the hooks to access them and not redefine them. @@ -673,7 +673,7 @@ export function useCounterProgramAccount({ account }: { account: PublicKey }) { ``` -Next, let’s use the Anchor `MethodsBuilder` to build a new instruction to invoke +Next, let's use the Anchor `MethodsBuilder` to build a new instruction to invoke the `increment` instruction. Again, Anchor can infer the `user` account from the wallet so we only need to include the `counter` account. @@ -732,11 +732,11 @@ continuing. ## Challenge -Now it’s your turn to build something independently. Building on top of what -we’ve done in the lab, try to create a new component in the frontend that +Now it's your turn to build something independently. Building on top of what +we've done in the lab, try to create a new component in the frontend that implements a button to decrements the counter. -Before building the component in the frontend, you’ll first need to: +Before building the component in the frontend, you'll first need to: 1. Build and deploy a new program that implements a `decrement` instruction 2. Update the IDL file in the frontend with the one from your new program diff --git a/content/courses/onchain-development/intro-to-anchor.md b/content/courses/onchain-development/intro-to-anchor.md index 2d3305a34..efda0cd27 100644 --- a/content/courses/onchain-development/intro-to-anchor.md +++ b/content/courses/onchain-development/intro-to-anchor.md @@ -42,9 +42,9 @@ Anchor uses macros and traits to generate boilerplate Rust code for you. These provide a clear structure to your program so you can more easily reason about your code. The main high-level macros and attributes are: -- `declare_id` - a macro for declaring the program’s onchain address +- `declare_id` - a macro for declaring the program's onchain address - `#[program]` - an attribute macro used to denote the module containing the - program’s instruction logic + program's instruction logic - `Accounts` - a trait applied to structs representing the list of accounts required for an instruction - `#[account]` - an attribute macro used to define custom account types for the @@ -183,7 +183,7 @@ You may have noticed in the previous example that one of the accounts in was of type `Program`. Anchor provides a number of account types that can be used to represent -accounts. Each type implements different account validation. We’ll go over a few +accounts. Each type implements different account validation. We'll go over a few of the common types you may encounter, but be sure to look through the [full list of account types](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/index.html). @@ -329,8 +329,8 @@ from the first 8 bytes of the SHA256 hash of the account type's name. The first 8 bytes are reserved for the account discriminator when implementing account serialization traits (which is almost always in an Anchor program). -As a result, any calls to `AccountDeserialize`’s `try_deserialize` will check -this discriminator. If it doesn’t match, an invalid account was given, and the +As a result, any calls to `AccountDeserialize`'s `try_deserialize` will check +this discriminator. If it doesn't match, an invalid account was given, and the account deserialization will exit with an error. The `#[account]` attribute also implements the `Owner` trait for a struct using @@ -500,7 +500,7 @@ pub struct Counter { #### 3. Implement `Context` type `Initialize` -Next, using the `#[derive(Accounts)]` macro, let’s implement the `Initialize` +Next, using the `#[derive(Accounts)]` macro, let's implement the `Initialize` type that lists and validates the accounts used by the `initialize` instruction. It'll need the following accounts: @@ -522,10 +522,10 @@ pub struct Initialize<'info> { #### 4. Add the `initialize` instruction -Now that we have our `Counter` account and `Initialize` type , let’s implement +Now that we have our `Counter` account and `Initialize` type , let's implement the `initialize` instruction within `#[program]`. This instruction requires a `Context` of type `Initialize` and takes no additional instruction data. In the -instruction logic, we are simply setting the `counter` account’s `count` field +instruction logic, we are simply setting the `counter` account's `count` field to `0`. ```rust @@ -540,14 +540,14 @@ pub fn initialize(ctx: Context) -> Result<()> { #### 5. Implement `Context` type `Update` -Now, using the `#[derive(Accounts)]` macro again, let’s create the `Update` type +Now, using the `#[derive(Accounts)]` macro again, let's create the `Update` type that lists the accounts that the `increment` instruction requires. It'll need the following accounts: - `counter` - an existing counter account to increment - `user` - payer for the transaction fee -Again, we’ll need to specify any constraints using the `#[account(..)]` +Again, we'll need to specify any constraints using the `#[account(..)]` attribute: ```rust @@ -561,11 +561,11 @@ pub struct Update<'info> { #### 6. Add `increment` instruction -Lastly, within `#[program]`, let’s implement an `increment` instruction to +Lastly, within `#[program]`, let's implement an `increment` instruction to increment the `count` once a `counter` account is initialized by the first instruction. This instruction requires a `Context` of type `Update` (implemented in the next step) and takes no additional instruction data. In the instruction -logic, we are simply incrementing an existing `counter` account’s `count` field +logic, we are simply incrementing an existing `counter` account's `count` field by `1`. ```rust @@ -713,7 +713,7 @@ if you need some more time with it. ## Challenge -Now it’s your turn to build something independently. Because we're starting with +Now it's your turn to build something independently. Because we're starting with simple programs, yours will look almost identical to what we just created. It's useful to try and get to the point where you can write it from scratch without referencing prior code, so try not to copy and paste here. diff --git a/content/courses/program-optimization/program-architecture.md b/content/courses/program-optimization/program-architecture.md index f18289284..52056d9a7 100644 --- a/content/courses/program-optimization/program-architecture.md +++ b/content/courses/program-optimization/program-architecture.md @@ -31,7 +31,7 @@ with the code. And you, as the designer, need to think about: These questions are even more important when developing for a blockchain. Not only are resources more limited than in a typical computing environment, you're -also dealing with people’s assets; code has a cost now. +also dealing with people's assets; code has a cost now. We'll leave most of the asset handling discussion to [security course lesson](/content/courses/program-security/security-intro), but @@ -46,10 +46,10 @@ considerations that should be taken when creating Solana programs. ### Dealing With Large Accounts -In modern application programming, we don’t often have to think about the size +In modern application programming, we don't often have to think about the size of the data structures we are using. You want to make a string? You can put a 4000 character limit on it if you want to avoid abuse, but it's probably not an -issue. Want an integer? They’re pretty much always 32-bit for convenience. +issue. Want an integer? They're pretty much always 32-bit for convenience. In high level languages, you are in the data-land-o-plenty! Now, in Solana land, we pay per byte stored (rent) and have limits on heap, stack and account sizes. @@ -63,7 +63,7 @@ we are going to be looking at in this section: 2. When operating on larger data, we run into [Stack](https://solana.com/docs/onchain-programs/faq#stack) and [Heap](https://solana.com/docs/onchain-programs/faq#heap-size) constraints - - to get around these, we’ll look at using Box and Zero-Copy. + to get around these, we'll look at using Box and Zero-Copy. #### Sizes @@ -77,10 +77,10 @@ used to be an actual thing, but now there's an enforced minimum rent exemption. You can read about it in [the Solana documentation](https://solana.com/docs/intro/rent). -Rent etymology aside, putting data on the blockchain can be expensive. It’s why +Rent etymology aside, putting data on the blockchain can be expensive. It's why NFT attributes and associated files, like the image, are stored offchain. You ultimately want to strike a balance that leaves your program highly functional -without becoming so expensive that your users don’t want to pay to open the data +without becoming so expensive that your users don't want to pay to open the data account. The first thing you need to know before you can start optimizing for space in @@ -109,7 +109,7 @@ from the Knowing these, start thinking about little optimizations you might take in a program. For example, if you have an integer field that will only ever reach -100, don’t use a u64/i64, use a u8. Why? Because a u64 takes up 8 bytes, with a +100, don't use a u64/i64, use a u8. Why? Because a u64 takes up 8 bytes, with a max value of 2^64 or 1.84 \* 10^19. Thats a waste of space since you only need to accommodate numbers up to 100. A single byte will give you a max value of 255 which, in this case, would be sufficient. Similarly, there's no reason to use i8 @@ -127,8 +127,8 @@ If you want to read more about Anchor sizes, take a look at #### Box -Now that you know a little bit about data sizes, let’s skip forward and look at -a problem you’ll run into if you want to deal with larger data accounts. Say you +Now that you know a little bit about data sizes, let's skip forward and look at +a problem you'll run into if you want to deal with larger data accounts. Say you have the following data account: ```rust @@ -144,7 +144,7 @@ pub struct SomeFunctionContext<'info> { ``` If you try to pass `SomeBigDataStruct` into the function with the -`SomeFunctionContext` context, you’ll run into the following compiler warning: +`SomeFunctionContext` context, you'll run into the following compiler warning: `// Stack offset of XXXX exceeded max offset of 4096 by XXXX bytes, please minimize large stack variables` @@ -174,7 +174,7 @@ pub struct SomeFunctionContext<'info> { In Anchor, **`Box`** is used to allocate the account to the Heap, not the Stack. Which is great since the Heap gives us 32KB to work with. The best part -is you don’t have to do anything different within the function. All you need to +is you don't have to do anything different within the function. All you need to do is add `Box<…>` around all of your big data accounts. But Box is not perfect. You can still overflow the stack with sufficiently large @@ -224,7 +224,7 @@ To understand what's happening here, take a look at the > heap size. When using borsh, the account has to be copied and deserialized > into a new data structure and thus is constrained by stack and heap limits > imposed by the BPF VM. With zero copy deserialization, all bytes from the -> account’s backing `RefCell<&mut [u8]>` are simply re-interpreted as a +> account's backing `RefCell<&mut [u8]>` are simply re-interpreted as a > reference to the data structure. No allocations or copies necessary. Hence the > ability to get around stack and heap limitations. @@ -244,7 +244,7 @@ pub struct ConceptZeroCopy<'info> { } ``` -Instead, your client has to create the large account and pay for it’s rent in a +Instead, your client has to create the large account and pay for it's rent in a separate instruction. ```typescript @@ -275,12 +275,12 @@ The second caveat is that your'll have to call one of the following methods from inside your rust instruction function to load the account: - `load_init` when first initializing an account (this will ignore the missing - account discriminator that gets added only after the user’s instruction code) + account discriminator that gets added only after the user's instruction code) - `load` when the account is not mutable - `load_mut` when the account is mutable For example, if you wanted to init and manipulate the `SomeReallyBigDataStruct` -from above, you’d call the following in the function +from above, you'd call the following in the function ```rust let some_really_big_data = &mut ctx.accounts.some_really_big_data.load_init()?; @@ -296,7 +296,7 @@ Box and Zero-Copy in vanilla Solana. ### Dealing with Accounts -Now that you know the nuts and bolts of space consideration on Solana, let’s +Now that you know the nuts and bolts of space consideration on Solana, let's look at some higher level considerations. In Solana, everything is an account, so for the next couple sections we'll look at some account architecture concepts. @@ -320,7 +320,7 @@ the location of `id` on the memory map. To make this more clear, observe what this account's data looks like onchain when `flags` has four items in the vector vs eight items. If you were to call -`solana account ACCOUNT_KEY` you’d get a data dump like the following: +`solana account ACCOUNT_KEY` you'd get a data dump like the following: ```rust 0000: 74 e4 28 4e d9 ec 31 0a -> Account Discriminator (8) @@ -367,7 +367,7 @@ const states = await program.account.badState.all([ However, if you wanted to query by the `id`, you wouldn't know what to put for the `offset` since the location of `id` is variable based on the length of -`flags`. That doesn’t seem very helpful. IDs are usually there to help with +`flags`. That doesn't seem very helpful. IDs are usually there to help with queries! The simple fix is to flip the order. ```rust @@ -453,7 +453,7 @@ add in some `for_future_use` bytes. #### Data Optimization The idea here is to be aware of wasted bits. For example, if you have a field -that represents the month of the year, don’t use a `u64`. There will only ever +that represents the month of the year, don't use a `u64`. There will only ever be 12 months. Use a `u8`. Better yet, use a `u8` Enum and label the months. To get even more aggressive on bit savings, be careful with booleans. Look at @@ -536,10 +536,10 @@ Depending on the seeding you can create all sorts of relationships: program. For example, if your program needs a lookup table, you could seed it with `seeds=[b"Lookup"]`. Just be careful to provide appropriate access restrictions. -- One-Per-Owner - Say you’re creating a video game player account and you only - want one player account per wallet. Then you’d seed the account with - `seeds=[b"PLAYER", owner.key().as_ref()]`. This way, you’ll always know where - to look for a wallet’s player account **and** there can only ever be one of +- One-Per-Owner - Say you're creating a video game player account and you only + want one player account per wallet. Then you'd seed the account with + `seeds=[b"PLAYER", owner.key().as_ref()]`. This way, you'll always know where + to look for a wallet's player account **and** there can only ever be one of them. - Multiple-Per-Owner - Okay, but what if you want multiple accounts per wallet? Say you want to mint podcast episodes. Then you could seed your `Podcast` @@ -555,8 +555,8 @@ From there you can mix and match in all sorts of clever ways! But the preceding list should give you enough to get started. The big benefit of really paying attention to this aspect of design is answering -the ‘indexing’ problem. Without PDAs and seeds, all users would have to keep -track of all of the addresses of all of the accounts they’ve ever used. This +the ‘indexing' problem. Without PDAs and seeds, all users would have to keep +track of all of the addresses of all of the accounts they've ever used. This isn't feasible for users, so they'd have to depend on a centralized entity to store their addresses in a database. In many ways that defeats the purpose of a globally distributed network. PDAs are a much better solution. @@ -604,11 +604,11 @@ you can avoid concurrency issues and really boost your program's performance. #### Shared Accounts -If you’ve been around crypto for a while, you may have experienced a big NFT +If you've been around crypto for a while, you may have experienced a big NFT mint event. A new NFT project is coming out, everyone is really excited for it, -and then the candymachine goes live. It’s a mad dash to click +and then the candymachine goes live. It's a mad dash to click `accept transaction` as fast as you can. If you were clever, you may have -written a bot to enter in the transactions faster that the website’s UI could. +written a bot to enter in the transactions faster that the website's UI could. This mad rush to mint creates a lot of failed transactions. But why? Because everyone is trying to write data to the same Candy Machine account. @@ -634,7 +634,7 @@ Bob -- pays --- | ``` Since both of these transactions write to Carol's token account, only one of -them can go through at a time. Fortunately, Solana is wicked fast, so it’ll +them can go through at a time. Fortunately, Solana is wicked fast, so it'll probably seem like they get paid at the same time. But what happens if more than just Alice and Bob try to pay Carol? @@ -658,7 +658,7 @@ trying to write data to the same account all at once. Imagine you create a super popular program and you want to take a fee on every transaction you process. For accounting reasons, you want all of those fees to go to one wallet. With that setup, on a surge of users, your protocol will -become slow and or become unreliable. Not great. So what’s the solution? +become slow and or become unreliable. Not great. So what's the solution? Separate the data transaction from the fee transaction. For example, imagine you have a data account called `DonationTally`. Its only @@ -675,7 +675,7 @@ pub struct DonationTally { } ``` -First let’s look at the suboptimal solution. +First let's look at the suboptimal solution. ```rust pub fn run_concept_shared_account_bottleneck(ctx: Context, lamports_to_donate: u64) -> Result<()> { @@ -707,7 +707,7 @@ pub fn run_concept_shared_account_bottleneck(ctx: Context) -> Result<()> { #### 8. Attack Monster -Now! Let’s attack those monsters and start gaining some exp! +Now! Let's attack those monsters and start gaining some exp! The logic here is as follows: @@ -1273,10 +1273,10 @@ incrementing experience and kill counts. The `saturating_add` function ensures the number will never overflow. Say the `kills` was a u8 and my current kill count was 255 (0xFF). If I killed another and added normally, e.g. `255 + 1 = 0 (0xFF + 0x01 = 0x00) = 0`, the kill count -would end up as 0. `saturating_add` will keep it at its max if it’s about to +would end up as 0. `saturating_add` will keep it at its max if it's about to roll over, so `255 + 1 = 255`. The `checked_add` function will throw an error if -it’s about to overflow. Keep this in mind when doing math in Rust. Even though -`kills` is a u64 and will never roll with it’s current programming, it’s good +it's about to overflow. Keep this in mind when doing math in Rust. Even though +`kills` is a u64 and will never roll with it's current programming, it's good practice to use safe math and consider roll-overs. ```rust @@ -1452,9 +1452,9 @@ anchor build #### Testing -Now, let’s see this baby work! +Now, let's see this baby work! -Let’s set up the `tests/rpg.ts` file. We will be filling out each test in turn. +Let's set up the `tests/rpg.ts` file. We will be filling out each test in turn. But first, we needed to set up a couple of different accounts. Mainly the `gameMaster` and the `treasury`. @@ -1531,7 +1531,7 @@ anchor test some `.pnp.*` files and no `node_modules`, you may want to call `rm -rf .pnp.*` followed by `npm i` and then `yarn install`. That should work. -Now that everything is running, let’s implement the `Create Player`, +Now that everything is running, let's implement the `Create Player`, `Spawn Monster`, and `Attack Monster` tests. Run each test as you complete them to make sure things are running smoothly. @@ -1753,7 +1753,7 @@ optimization adds up! ## Challenge -Now it’s your turn to practice independently. Go back through the lab code +Now it's your turn to practice independently. Go back through the lab code looking for additional optimizations and/or expansion you can make. Think through new systems and features you would add and how you would optimize them. diff --git a/content/courses/program-optimization/program-configuration.md b/content/courses/program-optimization/program-configuration.md index ac844a33c..58760c4b0 100644 --- a/content/courses/program-optimization/program-configuration.md +++ b/content/courses/program-optimization/program-configuration.md @@ -27,7 +27,7 @@ description: [`cfg!` **macro**](https://doc.rust-lang.org/std/macro.cfg.html) to compile different code paths based on the enabled features. - For environment-like variables post-deployment, create program accounts and - admin-only instructions accessible by the program’s upgrade authority. + admin-only instructions accessible by the program's upgrade authority. ## Lesson @@ -59,7 +59,7 @@ effective solution involves a combination of two techniques: ### Native Rust Feature Flags One of the simplest ways to create environments is to use Rust features. -Features are defined in the `[features]` table of the program’s `Cargo.toml` +Features are defined in the `[features]` table of the program's `Cargo.toml` file. You may define multiple features for different use cases. ```toml diff --git a/content/courses/program-optimization/rust-macros.md b/content/courses/program-optimization/rust-macros.md index 37dd55142..6f9e106a1 100644 --- a/content/courses/program-optimization/rust-macros.md +++ b/content/courses/program-optimization/rust-macros.md @@ -798,9 +798,9 @@ pub struct Config { ### 3. Define the custom macro Now, in the `custom-macro/src/lib.rs` file, let's add our new macro's -declaration. In this file, we’ll use the `parse_macro_input!` macro to parse the +declaration. In this file, we'll use the `parse_macro_input!` macro to parse the input `TokenStream` and extract the `ident` and `data` fields from a -`DeriveInput` struct. Then, we’ll use the `eprintln!` macro to print the values +`DeriveInput` struct. Then, we'll use the `eprintln!` macro to print the values of `ident` and `data`. We will now use `TokenStream::new()` to return an empty `TokenStream`. @@ -834,7 +834,7 @@ input `TokenStream` parses correctly, remove the `eprintln!` statements. ### 4. Get the struct's fields -Next, let’s use `match` statements to get the named fields from the `data` of +Next, let's use `match` statements to get the named fields from the `data` of the struct. Then we'll use the `eprintln!` macro to print the values of the fields. @@ -867,7 +867,7 @@ correctly, you can remove the `eprintln!` statement. ### 5. Build update instructions -Next, let’s iterate over the fields of the struct and generate an update +Next, let's iterate over the fields of the struct and generate an update instruction for each field. The instruction will be generated using the `quote!` macro, including the field's name and type and a new function name for the update instruction. @@ -909,7 +909,7 @@ pub fn instruction_builder(input: TokenStream) -> TokenStream { ### 6. Return new `TokenStream` -Lastly, let’s use the `quote!` macro to generate an implementation for the +Lastly, let's use the `quote!` macro to generate an implementation for the struct with the name specified by the `ident` variable. The implementation includes the update instructions generated for each field in the struct. The generated code is then converted to a `TokenStream` using the `into()` method diff --git a/content/courses/program-security/account-data-matching.md b/content/courses/program-security/account-data-matching.md index f9963a123..baff3b5bf 100644 --- a/content/courses/program-security/account-data-matching.md +++ b/content/courses/program-security/account-data-matching.md @@ -80,7 +80,7 @@ pub struct AdminConfig { The basic Rust approach to solve this problem is to simply compare the passed in `admin` key to the `admin` key stored in the `admin_config` account, throwing an -error if they don’t match. +error if they don't match. ```rust if ctx.accounts.admin.key() != ctx.accounts.admin_config.admin { @@ -172,7 +172,7 @@ pub struct AdminConfig { Alternatively, you can use `constraint` to manually add an expression that must evaluate to true in order for execution to continue. This is useful when for -some reason naming can’t be consistent or when you need a more complex +some reason naming can't be consistent or when you need a more complex expression to fully validate the incoming data. ```rust @@ -191,9 +191,9 @@ pub struct UpdateAdmin<'info> { ## Lab -For this lab we’ll create a simple “vault” program similar to the program we +For this lab we'll create a simple “vault” program similar to the program we used in the Signer Authorization lesson and the Owner Check lesson. Similar to -those labs, we’ll show in this lab how a missing data validation check could +those labs, we'll show in this lab how a missing data validation check could allow the vault to be drained. #### 1. Starter @@ -212,7 +212,7 @@ program. This allows the `vault` account to sign for the transfer of tokens from the token account. The `insecure_withdraw` instruction transfers all the tokens in the `vault` -account’s token account to a `withdraw_destination` token account. +account's token account to a `withdraw_destination` token account. Notice that this instruction \***\*does\*\*** have a signer check for `authority` and an owner check for `vault`. However, nowhere in the account @@ -315,8 +315,8 @@ pub struct Vault { #### 2. Test `insecure_withdraw` instruction -To prove that this is a problem, let’s write a test where an account other than -the vault’s `authority` tries to withdraw from the vault. +To prove that this is a problem, let's write a test where an account other than +the vault's `authority` tries to withdraw from the vault. The test file includes the code to invoke the `initialize_vault` instruction using the provider wallet as the `authority` and then mints 100 tokens to the @@ -363,14 +363,14 @@ account-data-matching #### 3. Add `secure_withdraw` instruction -Let’s go implement a secure version of this instruction called +Let's go implement a secure version of this instruction called `secure_withdraw`. This instruction will be identical to the `insecure_withdraw` instruction, -except we’ll use the `has_one` constraint in the account validation struct +except we'll use the `has_one` constraint in the account validation struct (`SecureWithdraw`) to check that the `authority` account passed into the instruction matches the `authority` account on the `vault` account. That way -only the correct authority account can withdraw the vault’s tokens. +only the correct authority account can withdraw the vault's tokens. ```rust use anchor_lang::prelude::*; @@ -429,7 +429,7 @@ pub struct SecureWithdraw<'info> { #### 4. Test `secure_withdraw` instruction -Now let’s test the `secure_withdraw` instruction with two tests: one that uses +Now let's test the `secure_withdraw` instruction with two tests: one that uses `walletFake` as the authority and one that uses `wallet` as the authority. We expect the first invocation to return an error and the second to succeed. diff --git a/content/courses/program-security/arbitrary-cpi.md b/content/courses/program-security/arbitrary-cpi.md index b5302e7d8..edb15793d 100644 --- a/content/courses/program-security/arbitrary-cpi.md +++ b/content/courses/program-security/arbitrary-cpi.md @@ -3,7 +3,7 @@ title: Arbitrary CPI objectives: - Explain the security risks associated with invoking a CPI to an unknown program - - Showcase how Anchor’s CPI module prevents this from happening when making a + - Showcase how Anchor's CPI module prevents this from happening when making a CPI from one Anchor program to another - Safely and securely make a CPI from an Anchor program to an arbitrary non-anchor program @@ -35,7 +35,7 @@ program results in your program performing CPIs to arbitrary programs. This lack of program checks creates an opportunity for a malicious user to pass in a different program than expected, causing the original program to call an -instruction handler on this mystery program. There’s no telling what the +instruction handler on this mystery program. There's no telling what the consequences of this CPI could be. It depends on the program logic (both that of the original program and the unexpected program), as well as what other accounts are passed into the original instruction handler. @@ -120,8 +120,8 @@ pub fn cpi_secure(ctx: Context, amount: u64) -> ProgramResult { Now, if an attacker passes in a different token program, the instruction handler will return the `ProgramError::IncorrectProgramId` error. -Depending on the program you’re invoking with your CPI, you can either hard code -the address of the expected program ID or use the program’s Rust crate to get +Depending on the program you're invoking with your CPI, you can either hard code +the address of the expected program ID or use the program's Rust crate to get the address of the program, if available. In the example above, the `spl_token` crate provides the address of the SPL Token Program. @@ -133,7 +133,7 @@ learned in a [previous lesson of Anchor CPI](/content/courses/onchain-development/anchor-cpi.md) that Anchor can automatically generate CPI modules to make CPIs into the program simpler. These modules also enhance security by verifying the public key of the -program that’s passed into one of its public instructions. +program that's passed into one of its public instructions. Every Anchor program uses the `declare_id()` macro to define the address of the program. When a CPI module is generated for a specific program, it uses the @@ -190,8 +190,8 @@ Like the example above, Anchor has created a few that allow you to issue CPIs into them as if they were Anchor programs. -Additionally and depending on the program you’re making the CPI to, you may be -able to use Anchor’s +Additionally and depending on the program you're making the CPI to, you may be +able to use Anchor's [`Program` account type](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/program/struct.Program.html) to validate the passed-in program in your account validation struct. Between the [`anchor_lang`](https://docs.rs/anchor-lang/latest/anchor_lang) and [`anchor_spl`](https://docs.rs/anchor_spl/latest/) crates, diff --git a/content/courses/program-security/bump-seed-canonicalization.md b/content/courses/program-security/bump-seed-canonicalization.md index e87e288fe..e2ea4d93f 100644 --- a/content/courses/program-security/bump-seed-canonicalization.md +++ b/content/courses/program-security/bump-seed-canonicalization.md @@ -3,7 +3,7 @@ title: Bump Seed Canonicalization objectives: - Explain the vulnerabilities associated with using PDAs derived without the canonical bump - - Initialize a PDA using Anchor’s `seeds` and `bump` constraints to + - Initialize a PDA using Anchor's `seeds` and `bump` constraints to automatically use the canonical bump - Use Anchor's `seeds` and `bump` constraints to ensure the canonical bump is always used in future instructions when deriving a PDA @@ -30,7 +30,7 @@ description: - Anchor allows you to **specify a bump** with the `bump = ` constraint when verifying the address of a PDA - Because `find_program_address` can be expensive, best practice is to store the - derived bump in an account’s data field to be referenced later on when + derived bump in an account's data field to be referenced later on when re-deriving the address for verification ```rust #[derive(Accounts)] @@ -151,12 +151,12 @@ pub fn set_value_secure( } ``` -### Use Anchor’s `seeds` and `bump` constraints +### Use Anchor's `seeds` and `bump` constraints Anchor provides a convenient way to derive PDAs in the account validation struct using the `seeds` and `bump` constraints. These can even be combined with the `init` constraint to initialize the account at the intended address. To protect -the program from the vulnerability we’ve been discussing throughout this lesson, +the program from the vulnerability we've been discussing throughout this lesson, Anchor does not even allow you to initialize an account at a PDA using anything but the canonical bump. Instead, it uses `find_program_address` to derive the PDA and subsequently performs the initialization. @@ -280,7 +280,7 @@ If you don't specify the bump on the `bump` constraint, Anchor will still use `find_program_address` to derive the PDA using the canonical bump. As a consequence, your instruction will incur a variable amount of compute budget. Programs that are already at risk of exceeding their compute budget should use -this with care since there is a chance that the program’s budget may be +this with care since there is a chance that the program's budget may be occasionally and unpredictably exceeded. On the other hand, if you only need to verify the address of a PDA passed in diff --git a/content/courses/program-security/closing-accounts.md b/content/courses/program-security/closing-accounts.md index 2f62f0c9c..d4a9b28de 100644 --- a/content/courses/program-security/closing-accounts.md +++ b/content/courses/program-security/closing-accounts.md @@ -33,7 +33,7 @@ While it sounds simple, closing accounts properly can be tricky. There are a number of ways an attacker could circumvent having the account closed if you don't follow specific steps. -To get a better understanding of these attack vectors, let’s explore each of +To get a better understanding of these attack vectors, let's explore each of these scenarios in depth. ### Insecure account closing @@ -45,10 +45,10 @@ account. This resets the owner from the owning program to the system program. Take a look at the example below. The instruction requires two accounts: 1. `account_to_close` - the account to be closed -2. `destination` - the account that should receive the closed account’s lamports +2. `destination` - the account that should receive the closed account's lamports The program logic is intended to close an account by simply increasing the -`destination` account’s lamports by the amount stored in the `account_to_close` +`destination` account's lamports by the amount stored in the `account_to_close` and setting the `account_to_close` lamports to 0. With this program, after a full transaction is processed, the `account_to_close` will be garbage collected by the runtime. @@ -238,7 +238,7 @@ Fortunately, Anchor makes all of this much simpler with the `#[account(close = )]` constraint. This constraint handles everything required to securely close an account: -1. Transfers the account’s lamports to the given `` +1. Transfers the account's lamports to the given `` 2. Zeroes out the account data 3. Sets the account discriminator to the `CLOSED_ACCOUNT_DISCRIMINATOR` variant @@ -258,8 +258,8 @@ pub struct CloseAccount { } ``` -The `force_defund` instruction is an optional addition that you’ll have to -implement on your own if you’d like to utilize it. +The `force_defund` instruction is an optional addition that you'll have to +implement on your own if you'd like to utilize it. ## Lab diff --git a/content/courses/program-security/duplicate-mutable-accounts.md b/content/courses/program-security/duplicate-mutable-accounts.md index c82a49f09..b52f1e29e 100644 --- a/content/courses/program-security/duplicate-mutable-accounts.md +++ b/content/courses/program-security/duplicate-mutable-accounts.md @@ -172,7 +172,7 @@ pub struct User { ## Lab -Let’s practice by creating a simple Rock Paper Scissors program to demonstrate +Let's practice by creating a simple Rock Paper Scissors program to demonstrate how failing to check for duplicate mutable accounts can cause undefined behavior within your program. @@ -356,9 +356,9 @@ pub struct RockPaperScissorsSecure<'info> { ### Test rock_paper_scissors_shoot_secure instruction -To test the `rock_paper_scissors_shoot_secure` instruction, we’ll invoke the -instruction twice. First, we’ll invoke the instruction using two different -player accounts to check that the instruction works as intended. Then, we’ll +To test the `rock_paper_scissors_shoot_secure` instruction, we'll invoke the +instruction twice. First, we'll invoke the instruction using two different +player accounts to check that the instruction works as intended. Then, we'll invoke the instruction using the `playerOne.publicKey` as both player accounts, which we expect to fail. diff --git a/content/courses/program-security/owner-checks.md b/content/courses/program-security/owner-checks.md index a30103dc8..99466ac52 100644 --- a/content/courses/program-security/owner-checks.md +++ b/content/courses/program-security/owner-checks.md @@ -645,7 +645,7 @@ debugging. Ensuring account ownership checks is critical to avoid security vulnerabilities. This example demonstrates how simple it is to implement proper validation, but -it’s vital to always verify which accounts are owned by specific programs. +it's vital to always verify which accounts are owned by specific programs. If you'd like to review the final solution code, it's available on the [`solution` branch of the repository](https://github.com/solana-developers/owner-checks/tree/solution). diff --git a/content/courses/program-security/pda-sharing.md b/content/courses/program-security/pda-sharing.md index a57bf4a95..7b70de4a7 100644 --- a/content/courses/program-security/pda-sharing.md +++ b/content/courses/program-security/pda-sharing.md @@ -3,7 +3,7 @@ title: PDA Sharing objectives: - Explain the security risks associated with PDA sharing - Derive PDAs that have discrete authority domains - - Use Anchor’s `seeds` and `bump` constraints to validate PDA accounts + - Use Anchor's `seeds` and `bump` constraints to validate PDA accounts description: "Understand the potential problems of reusing PDAs by using user and domain specific PDAs." @@ -15,7 +15,7 @@ description: possibility of users accessing data and funds that don't belong to them - Prevent the same PDA from being used for multiple accounts by using seeds that are user and/or domain-specific -- Use Anchor’s `seeds` and `bump` constraints to validate that a PDA is derived +- Use Anchor's `seeds` and `bump` constraints to validate that a PDA is derived using the expected seeds and bump ## Lesson @@ -155,7 +155,7 @@ pub struct TokenPool { } ``` -### Anchor’s seeds and bump Constraints +### Anchor's seeds and bump Constraints PDAs can be used as both the address of an account and allow programs to sign for the PDAs they own. @@ -165,7 +165,7 @@ the address of the `pool` account and the owner of the `vault` token account. This means that only the `pool` account associated with the correct `vault` and `withdraw_destination` can be used in the `withdraw_tokens` instruction handler. -You can use Anchor’s `seeds` and `bump` constraints with the +You can use Anchor's `seeds` and `bump` constraints with the [`#[account(...)]`](https://www.anchor-lang.com/docs/account-constraints) attribute to validate the `pool` account PDA. Anchor derives a PDA using the `seeds` and `bump` specified and compares it against the account passed into the @@ -234,8 +234,8 @@ pub struct TokenPool { ## Lab -Let’s practice by creating a simple program to demonstrate how PDA sharing can -allow an attacker to withdraw tokens that don’t belong to them. This lab expands +Let's practice by creating a simple program to demonstrate how PDA sharing can +allow an attacker to withdraw tokens that don't belong to them. This lab expands on the examples above by including the instruction handlers to initialize the required program accounts. @@ -517,7 +517,7 @@ it("prevents secure withdrawal to incorrect destination", async () => { ``` Lastly, since the `pool` account is a PDA derived using the -`withdraw_destination` token account, we can’t create a fake `pool` account +`withdraw_destination` token account, we can't create a fake `pool` account using the same PDA. Add one more test showing that the new `initialize_pool_secure` instruction handler won't let an attacker put in the wrong vault. diff --git a/content/courses/program-security/security-intro.md b/content/courses/program-security/security-intro.md index 468be61db..f3a17c022 100644 --- a/content/courses/program-security/security-intro.md +++ b/content/courses/program-security/security-intro.md @@ -10,7 +10,7 @@ description: ## Overview This course aims to introduce you to a range of common security exploits unique -to Solana development. We’ve modeled this course heavily on Coral's +to Solana development. We've modeled this course heavily on Coral's [Sealevel Attacks](https://github.com/coral-xyz/sealevel-attacks) repository. Program security is covered in our @@ -32,7 +32,7 @@ While the first few lessons in this course cover topics similar to those in the [Anchor course](/content/courses/onchain-development/intro-to-anchor.md) or [Program Security lesson](/content/courses/native-onchain-development/program-security.md) in the [Native Course](/content/courses/native-onchain-development.md), but as -you progress, you’ll encounter new types of attacks. We encourage you to explore +you progress, you'll encounter new types of attacks. We encourage you to explore all of them. diff --git a/content/courses/program-security/signer-auth.md b/content/courses/program-security/signer-auth.md index 897291264..34777b07f 100644 --- a/content/courses/program-security/signer-auth.md +++ b/content/courses/program-security/signer-auth.md @@ -3,8 +3,8 @@ title: Signer Authorization objectives: - Explain the security risks of not performing appropriate signer checks. - Implement signer checks using native Rust - - Implement signer checks using Anchor’s `Signer` type - - Implement signer checks using Anchor’s `#[account(signer)]` constraint + - Implement signer checks using Anchor's `Signer` type + - Implement signer checks using Anchor's `#[account(signer)]` constraint description: "Ensure instructions are only executed by authorized accounts by implementing signer checks." @@ -52,7 +52,7 @@ the instruction handler matches the `authority` field on the `vault` account, there is no verification that the `authority` account actually authorized the transaction. -This omission allows an attacker to pass in the `authority` account’s public key +This omission allows an attacker to pass in the `authority` account's public key and their own public key as the `new_authority` account, effectively reassigning themselves as the new authority of the `vault` account. Once they have control, they can interact with the program as the new authority. @@ -146,7 +146,7 @@ pub struct Vault { } ``` -### Use Anchor’s Signer Account Type +### Use Anchor's Signer Account Type Incorporating the [`signer`](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/signer/struct.Signer.html) @@ -195,7 +195,7 @@ When you use the `Signer` type, no other ownership or type checks are performed. -### Using Anchor’s `#[account(signer)]` Constraint +### Using Anchor's `#[account(signer)]` Constraint While the `Signer` account type is useful, it doesn't perform other ownership or type checks, limiting its use in instruction handler logic. The @@ -277,7 +277,7 @@ authority. The `vault` PDA will be the authority of the token account, enabling the program to sign off on token transfers. The `insecure_withdraw` instruction handler transfers tokens from the `vault` -account’s token account to a `withdraw_destination` token account. However, the +account's token account to a `withdraw_destination` token account. However, the `authority` account in the `InsecureWithdraw` struct is of type `UncheckedAccount`, a wrapper around `AccountInfo` that explicitly indicates the account is unchecked. diff --git a/content/courses/solana-pay/solana-pay.md b/content/courses/solana-pay/solana-pay.md index 7cd0baebe..b8b8a0cf9 100644 --- a/content/courses/solana-pay/solana-pay.md +++ b/content/courses/solana-pay/solana-pay.md @@ -603,7 +603,7 @@ async function buildTransaction( #### 6. Implement the `buildTransaction` function -Next, let’s implement the `buildTransaction` function. It should build, +Next, let's implement the `buildTransaction` function. It should build, partially sign, and return the check-in transaction. The sequence of items it needs to perform is: @@ -743,7 +743,7 @@ async function fetchUserState(account: PublicKey): Promise { #### 8. Implement `verifyCorrectLocation` function -Next, let’s implement the `verifyCorrectLocation` helper function. This function +Next, let's implement the `verifyCorrectLocation` helper function. This function is used to verify that a user is at the correct location in a scavenger hunt game. diff --git a/content/courses/state-compression/compressed-nfts.md b/content/courses/state-compression/compressed-nfts.md index 461bec325..e0a7ba59d 100644 --- a/content/courses/state-compression/compressed-nfts.md +++ b/content/courses/state-compression/compressed-nfts.md @@ -1,7 +1,7 @@ --- title: Compressed NFTs objectives: - - Create a compressed NFT collection using Metaplex’s Bubblegum program + - Create a compressed NFT collection using Metaplex's Bubblegum program - Mint compressed NFTs using the Bubblegum TS SDK - Transfer compressed NFTs using the Bubblegum TS SDK - Read compressed NFT data using the Read API @@ -15,8 +15,8 @@ description: - **Compressed NFTs (cNFTs)** use **State Compression** to hash NFT data and store the hash onchain in an account using a **concurrent Merkle tree** structure -- The cNFT data hash can’t be used to infer the cNFT data, but it can be used to - **verify** if the cNFT data you’re seeing is correct +- The cNFT data hash can't be used to infer the cNFT data, but it can be used to + **verify** if the cNFT data you're seeing is correct - Supporting RPC providers **index** cNFT data offchain when the cNFT is minted so that you can use the **Read API** to access the data - The **Metaplex Bubblegum program** is an abstraction on top of the **State @@ -30,7 +30,7 @@ structure takes up less account storage than traditional NFTs. Compressed NFTs leverage a concept called **State Compression** to store data in a way that drastically reduces costs. -Solana’s transaction costs are so cheap that most users never think about how +Solana's transaction costs are so cheap that most users never think about how expensive minting NFTs can be at scale. The cost to set up and mint 1 million traditional NFTs is approximately 24,000 SOL. By comparison, cNFTs can be structured to where the same setup and mint costs 10 SOL or less. That means @@ -40,14 +40,14 @@ over traditional NFTs. However, cNFTs can be tricky to work with. Eventually, the tooling required to work with them will be sufficiently abstracted from the underlying technology that the developer experience between traditional NFTs and cNFTs will be -negligible. But for now, you’ll still need to understand the low level puzzle -pieces, so let’s dig in! +negligible. But for now, you'll still need to understand the low level puzzle +pieces, so let's dig in! ### A theoretical overview of cNFTs Most of the costs associated with traditional NFTs come down to account storage space. Compressed NFTs use a concept called State Compression to store data in -the blockchain’s cheaper **ledger state**, using more expensive account space +the blockchain's cheaper **ledger state**, using more expensive account space only to store a “fingerprint”, or **hash**, of the data. This hash allows you to cryptographically verify that data has not been tampered with. @@ -71,20 +71,20 @@ are: truth” can go through the same process and compare the final hash without having to store all the data onchain -One problem not addressed in the above is how to make data available if it can’t +One problem not addressed in the above is how to make data available if it can't be fetched from an account. Since this hashing process occurs onchain, all the data exists in the ledger state and could theoretically be retrieved from the original transaction by replaying the entire chain state from origin. However, -it’s much more straightforward (though still complicated) to have an **indexer** +it's much more straightforward (though still complicated) to have an **indexer** track and index this data as the transactions occur. This ensures there is an offchain “cache” of the data that anyone can access and subsequently verify against the onchain root hash. -This process is _very complex_. We’ll cover some of the key concepts below but -don’t worry if you don’t understand it right away. We’ll talk more theory in the +This process is _very complex_. We'll cover some of the key concepts below but +don't worry if you don't understand it right away. We'll talk more theory in the state compression lesson and focus primarily on application to NFTs in this -lesson. You’ll be able to work with cNFTs by the end of this lesson even if you -don’t fully understand every piece of the state compression puzzle. +lesson. You'll be able to work with cNFTs by the end of this lesson even if you +don't fully understand every piece of the state compression puzzle. #### Concurrent Merkle trees @@ -168,7 +168,7 @@ forever exist on the ledger state. #### Index data for easy lookup Under normal conditions, you would typically access onchain data by fetching the -appropriate account. When using state compression, however, it’s not so +appropriate account. When using state compression, however, it's not so straightforward. As mentioned above, the data now exists in the ledger state rather than in an @@ -177,37 +177,37 @@ instruction, but while this data will in a sense exist in the ledger state forever, it will likely be inaccessible through validators after a certain period of time. -To save space and be more performant, validators don’t retain every transaction -back to the genesis block. The specific amount of time you’ll be able to access +To save space and be more performant, validators don't retain every transaction +back to the genesis block. The specific amount of time you'll be able to access the Noop instruction logs related to your data will vary based on the validator, -but eventually you’ll lose access to it if you’re relying directly on +but eventually you'll lose access to it if you're relying directly on instruction logs. Technically, you _can_ replay transaction state back to the genesis block but -the average team isn’t going to do that, and it certainly won’t be performant. +the average team isn't going to do that, and it certainly won't be performant. Instead, you should use an indexer that will observe the events sent to the Noop -program and store the relevant data off chain. That way you don’t need to worry +program and store the relevant data off chain. That way you don't need to worry about old data becoming inaccessible. ### Create a cNFT Collection -With the theoretical background out of the way, let’s turn our attention to the +With the theoretical background out of the way, let's turn our attention to the main point of this lesson: how to create a cNFT collection. Fortunately, you can use tools created by Solana Foundation, the Solana -developer community, and Metaplex to simplify the process. Specifically, we’ll +developer community, and Metaplex to simplify the process. Specifically, we'll be using the `@solana/spl-account-compression` SDK, the Metaplex Bubblegum -program, and the Bubblegum program’s corresponding TS SDK +program, and the Bubblegum program's corresponding TS SDK `@metaplex-foundation/mpl-bugglegum`. #### Prepare metadata -Prior to starting, you’ll prepare your NFT metadata similarly to how you would +Prior to starting, you'll prepare your NFT metadata similarly to how you would if you were using a Candy Machine. At its core, an NFT is simply a token with metadata that follows the NFT standard. In other words, it should be shaped something like this: @@ -237,13 +237,13 @@ something like this: ``` Depending on your use case, you may be able to generate this dynamically or you -might want to have a JSON file prepared for each cNFT beforehand. You’ll also +might want to have a JSON file prepared for each cNFT beforehand. You'll also need any other assets referenced by the JSON, such as the `image` url shown in the example above. #### Create Collection NFT -If you want your cNFTs to be part of a collection, you’ll need to create a +If you want your cNFTs to be part of a collection, you'll need to create a Collection NFT **before** you start minting cNFTs. This is a traditional NFT that acts as the reference binding your cNFTs together into a single collection. You can create this NFT using the `@metaplex-foundation/js` library. Just make @@ -315,19 +315,19 @@ the max depth, while the buffer size will determine the number of concurrent changes (mints, transfers, etc.) within the same slot that can occur to the tree. In other words, choose the max depth that corresponds to the number of NFTs you need the tree to hold, then choose one of the options for max buffer -size based on the traffic you expect you’ll need to support. +size based on the traffic you expect you'll need to support. Next, choose the canopy depth. Increasing the canopy depth increases the -composability of your cNFTs. Any time your or another developer’s code attempts +composability of your cNFTs. Any time your or another developer's code attempts to verify a cNFT down the road, the code will have to pass in as many proof -nodes as there are “layers” in your tree. So for a max depth of 20, you’ll need +nodes as there are “layers” in your tree. So for a max depth of 20, you'll need to pass in 20 proof nodes. Not only is this tedious, but since each proof node -is 32 bytes it’s possible to max out transaction sizes very quickly. +is 32 bytes it's possible to max out transaction sizes very quickly. For example, if your tree has a very low canopy depth, an NFT marketplace may only be able to support simple NFTs transfers rather than support an onchain bidding system for your cNFTs. The canopy effectively caches proof nodes onchain -so you don’t have to pass all of them into the transaction, allowing for more +so you don't have to pass all of them into the transaction, allowing for more complex transactions. Increasing any of these three values increases the size of the account, thereby @@ -354,7 +354,7 @@ const allocTreeIx = await createAllocTreeIx( Note that this is simply a helper function for calculating the size required by the account and creating the instruction to send to the System Program for -allocating the account. This function doesn’t interact with any +allocating the account. This function doesn't interact with any compression-specific programs yet. #### Use Bubblegum to Initialize Your Tree @@ -365,8 +365,8 @@ tree config account to add cNFT-specific tracking and functionality. Version 0.7 of the `@metaplex-foundation/mpl-bubblegum` TS SDK provides the helper function `createCreateTreeInstruction` for calling the `create_tree` -instruction on the Bubblegum program. As part of the call, you’ll need to derive -the `treeAuthority` PDA expected by the program. This PDA uses the tree’s +instruction on the Bubblegum program. As part of the call, you'll need to derive +the `treeAuthority` PDA expected by the program. This PDA uses the tree's address as a seed. ```typescript @@ -442,7 +442,7 @@ Feel free to take a look at the program code #### Mint cNFTs With the Merkle tree account and its corresponding Bubblegum tree config account -initialized, it’s possible to mint cNFTs to the tree. The Bubblegum instruction +initialized, it's possible to mint cNFTs to the tree. The Bubblegum instruction to use will be either `mint_v1` or `mint_to_collection_v1`, depending on whether or not you want to the minted cNFT to be part of a collection. @@ -537,7 +537,7 @@ const mintWithoutCollectionIx = createMintV1Instruction( ### Interact with cNFTs -It’s important to note that cNFTs _are not_ SPL tokens. That means your code +It's important to note that cNFTs _are not_ SPL tokens. That means your code needs to follow different conventions to handle cNFT functionality like fetching, querying, transferring, etc. @@ -546,24 +546,24 @@ fetching, querying, transferring, etc. The simplest way to fetch data from an existing cNFT is to use the [Digital Asset Standard Read API](https://solana.com/developers/guides/javascript/compressed-nfts#reading-compressed-nfts-metadata) (Read API). Note that this is separate from the standard JSON RPC. To use the -Read API, you’ll need to use a supporting RPC Provider. Metaplex maintains a +Read API, you'll need to use a supporting RPC Provider. Metaplex maintains a (likely non-exhaustive) [list of RPC providers](https://developers.metaplex.com/bubblegum/rpcs) that -support the Read API. In this lesson we’ll be using +support the Read API. In this lesson we'll be using [Helius](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api) as they have free support for Devnet. -To use the Read API to fetch a specific cNFT, you need to have the cNFT’s asset -ID. However, after minting cNFTs, you’ll have at most two pieces of information: +To use the Read API to fetch a specific cNFT, you need to have the cNFT's asset +ID. However, after minting cNFTs, you'll have at most two pieces of information: 1. The transaction signature 2. The leaf index (possibly) -The only real guarantee is that you’ll have the transaction signature. It is +The only real guarantee is that you'll have the transaction signature. It is **possible** to locate the leaf index from there, but it involves some fairly complex parsing. The short story is you must retrieve the relevant instruction -logs from the Noop program and parse them to find the leaf index. We’ll cover -this more in depth in a future lesson. For now, we’ll assume you know the leaf +logs from the Noop program and parse them to find the leaf index. We'll cover +this more in depth in a future lesson. For now, we'll assume you know the leaf index. This is a reasonable assumption for most mints given that the minting will be @@ -571,7 +571,7 @@ controlled by your code and can be set up sequentially so that your code can track which index is going to be used for each mint. I.e. the first mint will use index 0, the second index 1, etc. -Once you have the leaf index, you can derive the cNFT’s corresponding asset ID. +Once you have the leaf index, you can derive the cNFT's corresponding asset ID. When using Bubblegum, the asset ID is a PDA derived using the Bubblegum program ID and the following seeds: @@ -607,7 +607,7 @@ const { result } = await response.json(); console.log(JSON.stringify(result, null, 2)); ``` -This will return a JSON object that is comprehensive of what a traditional NFT’s +This will return a JSON object that is comprehensive of what a traditional NFT's on- and offchain metadata would look like combined. For example, you can find the cNFT attributes at `content.metadata.attributes` or the image at `content.files.uri`. @@ -626,30 +626,30 @@ and more. For example, Helius supports the following methods: - `getAssetsByCreator` - `getAssetsByGroup` -We won’t go over most of these directly, but be sure to look through the +We won't go over most of these directly, but be sure to look through the [Helius docs](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api) to learn how to use them correctly. #### Transfer cNFTs Just as with a standard SPL token transfer, security is paramount. An SPL token -transfer, however, makes verifying transfer authority very easy. It’s built into -the SPL Token program and standard signing. A compressed token’s ownership is +transfer, however, makes verifying transfer authority very easy. It's built into +the SPL Token program and standard signing. A compressed token's ownership is more difficult to verify. The actual verification will happen program-side, but your client-side code needs to provide additional information to make it possible. While there is a Bubblegum `createTransferInstruction` helper function, there is more assembly required than usual. Specifically, the Bubblegum program needs to -verify that the entirety of the cNFT’s data is what the client asserts before a +verify that the entirety of the cNFT's data is what the client asserts before a transfer can occur. The entirety of the cNFT data has been hashed and stored as a single leaf on the Merkle tree, and the Merkle tree is simply a hash of all -the tree’s leafs and branches. Because of this, you can’t simply tell the -program what account to look at and have it compare that account’s `authority` +the tree's leafs and branches. Because of this, you can't simply tell the +program what account to look at and have it compare that account's `authority` or `owner` field to the transaction signer. Instead, you need to provide the entirety of the cNFT data and any of the Merkle -tree’s proof information that isn’t stored in the canopy. That way, the program +tree's proof information that isn't stored in the canopy. That way, the program can independently prove that the provided cNFT data, and therefore the cNFT owner, is accurate. Only then can the program safely determine if the transaction signer should, in fact, be allowed to transfer the cNFT. @@ -710,7 +710,7 @@ const treeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( ``` Step four is the most conceptually challenging step. Using the three pieces of -information gathered, you’ll need to assemble the proof path for the cNFT’s +information gathered, you'll need to assemble the proof path for the cNFT's corresponding leaf. The proof path is represented as accounts passed to the program instruction. The program uses each of the account addresses as proof nodes to prove the leaf data is what you say it is. @@ -758,7 +758,7 @@ function, `createTransferInstruction`, requires the following arguments: first - `nonce` - used to ensure that no two leafs have the same hash; this value should be the same as `index` - - `index` - the index where the cNFT’s leaf is located on the Merkle tree + - `index` - the index where the cNFT's leaf is located on the Merkle tree An example of this is shown below. Note that the first 3 lines of code grab additional information nested in the objects shown previously so they are ready @@ -798,23 +798,23 @@ const transferIx = createTransferInstruction( ### Conclusion -We’ve covered the primary skills needed to interact with cNFTs, but haven’t been +We've covered the primary skills needed to interact with cNFTs, but haven't been fully comprehensive. You can also use Bubblegum to do things like burn, verify, -delegate, and more. We won’t go through these, but these instructions are +delegate, and more. We won't go through these, but these instructions are similar to the mint and transfer process. If you need this additional functionality, take a look at the [Bubblegum client source code](https://github.com/metaplex-foundation/mpl-bubblegum/tree/main/clients/js-solita) and leverage the helper functions it provides. Keep in mind that compression is fairly new. Available tooling will evolve -rapidly but the principles you’ve learned in this lesson will likely remain the +rapidly but the principles you've learned in this lesson will likely remain the same. These principles can also be broadened to arbitrary state compression, so -be sure to master them here so you’re ready for more fun stuff in future +be sure to master them here so you're ready for more fun stuff in future lessons! ## Lab -Let’s jump in and practice creating and working with cNFTs. Together, we’ll +Let's jump in and practice creating and working with cNFTs. Together, we'll build as simple a script as possible that will let us mint a cNFT collection from a Merkle tree. @@ -835,24 +835,24 @@ in `uri.ts`. The `uri.ts` file provides 10k URIs that you can use for the offchain portion of your NFT metadata. You can, of course, create your own metadata. But this lesson -isn’t explicitly about preparing metadata so we’ve provided some for you. +isn't explicitly about preparing metadata so we've provided some for you. The `utils.ts` file has a few helper functions to keep you from writing more unnecessary boilerplate than you need to. They are as follows: - `getOrCreateKeypair` will create a new keypair for you and save it to a `.env` - file, or if there’s already a private key in the `.env` file it will + file, or if there's already a private key in the `.env` file it will initialize a keypair from that. - `airdropSolIfNeeded` will airdrop some Devnet SOL to a specified address if - that address’s balance is below 1 SOL. + that address's balance is below 1 SOL. - `createNftMetadata` will create the NFT metadata for a given creator public - key and index. The metadata it’s getting is just dummy metadata using the URI + key and index. The metadata it's getting is just dummy metadata using the URI corresponding to the provided index from the `uri.ts` list of URIs. - `getOrCreateCollectionNFT` will fetch the collection NFT from the address specified in `.env` or if there is none it will create a new one and add the address to `.env`. -Finally, there’s some boilerplate in `index.ts` that calls creates a new Devnet +Finally, there's some boilerplate in `index.ts` that calls creates a new Devnet connection, calls `getOrCreateKeypair` to initialize a “wallet,” and calls `airdropSolIfNeeded` to fund the wallet if its balance is low. @@ -860,20 +860,20 @@ We will be writing all of our code in the `index.ts`. #### 2. Create the Merkle tree account -We’ll start by creating the Merkle tree account. Let’s encapsulate this in a -function that will eventually create _and_ initialize the account. We’ll put it -below our `main` function in `index.ts`. Let’s call it +We'll start by creating the Merkle tree account. Let's encapsulate this in a +function that will eventually create _and_ initialize the account. We'll put it +below our `main` function in `index.ts`. Let's call it `createAndInitializeTree`. For this function to work, it will need the following parameters: - `connection` - a `Connection` to use for interacting with the network. - `payer` - a `Keypair` that will pay for transactions. - `maxDepthSizePair` - a `ValidDepthSizePair`. This type comes from - `@solana/spl-account-compression`. It’s a simple object with properties + `@solana/spl-account-compression`. It's a simple object with properties `maxDepth` and `maxBufferSize` that enforces a valid combination of the two values. - `canopyDepth` - a number for the canopy depth In the body of the function, - we’ll generate a new address for the tree, then create the instruction for + we'll generate a new address for the tree, then create the instruction for allocating a new Merkle tree account by calling `createAllocTreeIx` from `@solana/spl-account-compression`. @@ -910,15 +910,15 @@ This instruction needs us to provide the following: and the Bubblegum program - `merkleTree` - the address of the Merkle tree - `payer` - the transaction fee payer - - `treeCreator` - the address of the tree creator; we’ll make this the same as + - `treeCreator` - the address of the tree creator; we'll make this the same as `payer` - `logWrapper` - make this the `SPL_NOOP_PROGRAM_ID` - `compressionProgram` - make this the `SPL_ACCOUNT_COMPRESSION_PROGRAM_ID` - `args` - a list of instruction arguments; this includes: - - `maxBufferSize` - the buffer size from our function’s `maxDepthSizePair` + - `maxBufferSize` - the buffer size from our function's `maxDepthSizePair` parameter - - `maxDepth` - the max depth from our function’s `maxDepthSizePair` parameter - - `public` - whether or no the tree should be public; we’ll set this to + - `maxDepth` - the max depth from our function's `maxDepthSizePair` parameter + - `public` - whether or no the tree should be public; we'll set this to `false` Finally, we can add both instructions to a transaction and submit the @@ -1023,15 +1023,15 @@ run the following: #### 4. Mint cNFTs to your tree -Believe it or not, that’s all you needed to do to set up your tree to compressed -NFTs! Now let’s turn our attention to minting. +Believe it or not, that's all you needed to do to set up your tree to compressed +NFTs! Now let's turn our attention to minting. -First, let’s declare a function called `mintCompressedNftToCollection`. It will +First, let's declare a function called `mintCompressedNftToCollection`. It will need the following parameters: - `connection` - a `Connection` to use for interacting with the network. - `payer` - a `Keypair` that will pay for transactions. -- `treeAddress` - the Merkle tree’s address +- `treeAddress` - the Merkle tree's address - `collectionDetails` - the details of the collection as type `CollectionDetails` from `utils.ts` - `amount` - the number of cNFTs to mint @@ -1052,7 +1052,7 @@ The body of this function will do the following: The `createMintToCollectionV1Instruction` takes two arguments: `accounts` and `args`. The latter is simply the NFT metadata. As with all complex instructions, -the primary hurdle is knowing which accounts to provide. So let’s go through +the primary hurdle is knowing which accounts to provide. So let's go through them real quick: - `payer` - the account that will pay for the transaction fees, rent, etc. @@ -1082,7 +1082,7 @@ them real quick: - `tokenMetadataProgram` - the token metadata program that was used for the collection NFT; this is usually always the Metaplex Token Metadata program -When you put it all together, this is what it’ll look like: +When you put it all together, this is what it'll look like: ```typescript async function mintCompressedNftToCollection( @@ -1200,16 +1200,16 @@ Again, to run, in your terminal type: `npm run start` #### 5. Read existing cNFT data -Now that we’ve written code to mint cNFTs, let’s see if we can actually fetch +Now that we've written code to mint cNFTs, let's see if we can actually fetch their data. This is tricky because the onchain data is just the Merkle tree account, the data from which can be used to verify existing information as accurate but is useless in conveying what the information is. -Let’s start by declaring a function `logNftDetails` that takes as parameters +Let's start by declaring a function `logNftDetails` that takes as parameters `treeAddress` and `nftsMinted`. -At this point we don’t actually have a direct identifier of any kind that points -to our cNFT. To get that, we’ll need to know the leaf index that was used when +At this point we don't actually have a direct identifier of any kind that points +to our cNFT. To get that, we'll need to know the leaf index that was used when we minted our cNFT. We can then use that to derive the asset ID used by the Read API and subsequently use the Read API to fetch our cNFT data. @@ -1219,9 +1219,9 @@ function from `@metaplex-foundation/mpl-bubblegum` to get the asset ID. Finally, we can use an RPC that supports the [Read API](https://solana.com/developers/guides/javascript/compressed-nfts) to -fetch the asset. We’ll be using +fetch the asset. We'll be using [Helius](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api), -but feel free to choose your own RPC provider. To use Helius, you’ll need to get +but feel free to choose your own RPC provider. To use Helius, you'll need to get a free API Key from [the Helius website](https://dev.helius.xyz/). Then add your `RPC_URL` to your `.env` file. For example: @@ -1262,8 +1262,8 @@ surface that data when requested. If we add a call to this function at the end of `main` and re-run your script, the data we get back in the console is very comprehensive. It includes all of -the data you’d expect in both the onchain and offchain portion of a traditional -NFT. You can find the cNFT’s attributes, files, ownership and creator +the data you'd expect in both the onchain and offchain portion of a traditional +NFT. You can find the cNFT's attributes, files, ownership and creator information, and more. ```json @@ -1360,11 +1360,11 @@ information, and more. Remember, the Read API also includes ways to get multiple assets, query by owner, creator, etc., and more. Be sure to look through the [Helius docs](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api) -to see what’s available. +to see what's available. #### 6. Transfer a cNFT -The last thing we’re going to add to our script is a cNFT transfer. Just as with +The last thing we're going to add to our script is a cNFT transfer. Just as with a standard SPL token transfer, security is paramount. Unlike with a standard SPL token transfer, however, to build a secure transfer with state compression of any kind, the program performing the transfer needs the entire asset data. @@ -1382,15 +1382,15 @@ Remember, the general steps are: 4. Prepare the asset proof as a list of `AccountMeta` objects 5. Build and send the Bubblegum transfer instruction -Let’s start by declaring a `transferNft` function that takes the following: +Let's start by declaring a `transferNft` function that takes the following: - `connection` - a `Connection` object - `assetId` - a `PublicKey` object - `sender` - a `Keypair` object so we can sign the transaction - `receiver` - a `PublicKey` object representing the new owner -Inside that function, let’s fetch the asset data again then also fetch the asset -proof. For good measure, let’s wrap everything in a `try catch`. +Inside that function, let's fetch the asset data again then also fetch the asset +proof. For good measure, let's wrap everything in a `try catch`. ```typescript async function transferNft( @@ -1434,7 +1434,7 @@ async function transferNft( } ``` -Next, let’s fetch the Merkle tree account from the chain, get the canopy depth, +Next, let's fetch the Merkle tree account from the chain, get the canopy depth, and assemble the proof path. We do this by mapping the asset proof we got from Helius to a list of `AccountMeta` objects, then removing any proof nodes at the end that are already cached onchain in the canopy. @@ -1578,10 +1578,10 @@ async function transferNft( } ``` -Lets transfer our first compressed NFT at index 0 to someone else. First we’ll +Lets transfer our first compressed NFT at index 0 to someone else. First we'll need to spin up another wallet with some funds, then grab the assetID at index 0 -using `getLeafAssetId`. Then we’ll do the transfer. Finally, we’ll print out the -entire collection using our function `logNftDetails`. You’ll note that the NFT +using `getLeafAssetId`. Then we'll do the transfer. Finally, we'll print out the +entire collection using our function `logNftDetails`. You'll note that the NFT at index zero will now belong to our new wallet in the `ownership` field. ```typescript @@ -1646,12 +1646,12 @@ take a look at the solution code on the `solution` branch of the ### Challenge -It’s your turn to take these concepts for a spin on your own! We’re not going to +It's your turn to take these concepts for a spin on your own! We're not going to be overly prescriptive at this point, but here are some ideas: 1. Create your own production cNFT collection -2. Build a UI for this lesson’s lab that will let you mint a cNFT and display it -3. See if you can replicate some of the lab script’s functionality in an onchain +2. Build a UI for this lesson's lab that will let you mint a cNFT and display it +3. See if you can replicate some of the lab script's functionality in an onchain program, i.e. write a program that can mint cNFTs diff --git a/content/courses/state-compression/generalized-state-compression.md b/content/courses/state-compression/generalized-state-compression.md index 4333e308d..f91169e38 100644 --- a/content/courses/state-compression/generalized-state-compression.md +++ b/content/courses/state-compression/generalized-state-compression.md @@ -27,8 +27,8 @@ description: Previously, we discussed state compression in the context of compressed NFTs. At the time of writing, compressed NFTs represent the most common use case for -state compression, but it’s possible to use state compression within any -program. In this lesson, we’ll discuss state compression in more generalized +state compression, but it's possible to use state compression within any +program. In this lesson, we'll discuss state compression in more generalized terms so that you can apply it to any of your programs. ### A theoretical overview of state compression @@ -36,7 +36,7 @@ terms so that you can apply it to any of your programs. In traditional programs, data is serialized (typically using borsh) and then stored directly in an account. This allows the data to be easily read and written through Solana programs. You can “trust” the data stored in the accounts -because it can’t be modified except through the mechanisms surfaced by the +because it can't be modified except through the mechanisms surfaced by the program. State compression effectively asserts that the most important piece of this @@ -113,7 +113,7 @@ subsequent writes to successfully occur. This includes: successful. 3. A canopy - When performing an update action on any given leaf, you need the entire proof path from that leaf to the root hash. The canopy stores - intermediate proof nodes along that path so they don’t all have to be passed + intermediate proof nodes along that path so they don't all have to be passed into the program from the client. As a program architect, you control three values directly related to these three @@ -163,17 +163,17 @@ The answer is 20. Choosing a max buffer size is effectively a question of throughput: how many concurrent writes do you need? The larger the buffer, the higher the throughput. -Lastly, the canopy depth will determine your program’s composability. State +Lastly, the canopy depth will determine your program's composability. State compression pioneers have made it clear that omitting a canopy is a bad idea. -Program A can’t call your state-compressed program B if doing so maxes out the +Program A can't call your state-compressed program B if doing so maxes out the transaction size limits. Remember, program A also has required accounts and data in addition to required proof paths, each of which take up transaction space. #### Data access on a state-compressed program -A state-compressed account doesn’t store the data itself. Rather, it stores the +A state-compressed account doesn't store the data itself. Rather, it stores the concurrent Merkle tree structure discussed above. The raw data itself lives only -in the blockchain’s cheaper **ledger state.** This makes data access somewhat +in the blockchain's cheaper **ledger state.** This makes data access somewhat more difficult, but not impossible. The Solana ledger is a list of entries containing signed transactions. In @@ -183,7 +183,7 @@ data that has ever been put into a transaction exists in the ledger. Since the state compression hashing process occurs onchain, all the data exists in the ledger state and could theoretically be retrieved from the original transaction by replaying the entire chain state from the beginning. However, -it’s much more straightforward (though still complicated) to have +it's much more straightforward (though still complicated) to have an **indexer** track and index this data as the transactions occur. This ensures there is an offchain “cache” of the data that anyone can access and subsequently verify against the onchain root hash. @@ -193,7 +193,7 @@ This process is complex, but it will make sense after some practice. ### State compression tooling The theory described above is essential to properly understanding state -compression. But you don’t have to implement any of it from scratch. Brilliant +compression. But you don't have to implement any of it from scratch. Brilliant engineers have laid most of the groundwork for you in the form of the SPL State Compression Program and the Noop Program. @@ -209,12 +209,12 @@ primary purpose is to make leaf data easier to index by logging it to the ledger state. When you want to store compressed data, you pass it to the State Compression program where it gets hashed and emitted as an “event” to the Noop program. The hash gets stored in the corresponding concurrent Merkle tree, but -the raw data remains accessible through the Noop program’s transaction logs. +the raw data remains accessible through the Noop program's transaction logs. #### Index data for easy lookup Under normal conditions, you would typically access onchain data by fetching the -appropriate account. When using state compression, however, it’s not so +appropriate account. When using state compression, however, it's not so straightforward. As mentioned above, the data now exists in the ledger state rather than in an @@ -223,18 +223,18 @@ instruction. Unfortunately, while this data will in a sense exist in the ledger state forever, it will likely be inaccessible through validators after a certain period of time. -To save space and be more performant, validators don’t retain every transaction -back to the genesis block. The specific amount of time you’ll be able to access +To save space and be more performant, validators don't retain every transaction +back to the genesis block. The specific amount of time you'll be able to access the Noop instruction logs related to your data will vary based on the validator. -Eventually, you’ll lose access to it if you’re relying directly on instruction +Eventually, you'll lose access to it if you're relying directly on instruction logs. Technically, you *can* replay the transaction state back to the genesis block -but the average team isn’t going to do that, and it certainly won’t be +but the average team isn't going to do that, and it certainly won't be performant. The [Digital Asset Standard (DAS)](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api) has been adopted by many RPC providers to enable efficient queries of compressed -NFTs and other assets. However, at the time of writing, it doesn’t support +NFTs and other assets. However, at the time of writing, it doesn't support arbitrary state compression. Instead, you have two primary options: 1. Use an indexing provider that will build a custom indexing solution for your @@ -251,7 +251,7 @@ need to rely on infrastructure providers to handle their indexing. #### Create Rust types As with a typical Anchor program, one of the first things you should do is -define your program’s Rust types. However, Rust types in a traditional Anchor +define your program's Rust types. However, Rust types in a traditional Anchor program often represent accounts. In a state-compressed program, your account state will only store the Merkle tree. The more “usable” data schema will just be serialized and logged to the Noop program. @@ -280,7 +280,7 @@ impl MessageLog { To be abundantly clear, **this is not an account that you will be able to read from**. Your program will be creating an instance of this type from instruction inputs, not constructing an instance of this type from account data that it -reads. We’ll discuss how to read data in a later section. +reads. We'll discuss how to read data in a later section. #### Initialize a new tree @@ -339,14 +339,14 @@ pub fn create_messages_tree( #### Add hashes to the tree -With an initialized Merkle tree, it’s possible to start adding data hashes. This +With an initialized Merkle tree, it's possible to start adding data hashes. This involves passing the uncompressed data to an instruction on your program that will hash the data, log it to the Noop program, and use the State Compression -Program’s `append` instruction to add the hash to the tree. The following +Program's `append` instruction to add the hash to the tree. The following discuss what your instruction needs to do in depth: 1. Use the `hashv` function from the `keccak` crate to hash the data. In most - cases, you’ll want to also hash the owner or authority of the data as well to + cases, you'll want to also hash the owner or authority of the data as well to ensure that it can only be modified by the proper authority. 2. Create a log object representing the data you wish to log to the Noop Program, then call `wrap_application_data_v1` to issue a CPI to the Noop @@ -354,7 +354,7 @@ discuss what your instruction needs to do in depth: available to any client looking for it. For broad use cases like cNFTs, that would be indexers. You might also create your own observing client to simulate what indexers are doing but specific to your application. -3. Build and issue a CPI to the State Compression Program’s `append` +3. Build and issue a CPI to the State Compression Program's `append` instruction. This takes the hash computed in step 1 and adds it to the next available leaf on your Merkle tree. Just as before, this requires the Merkle tree address and the tree authority bump as signature seeds. @@ -413,11 +413,11 @@ as those used to append the initial data to the tree: 1. **Verify update authority** - The first step is new. In most cases, you want to verify update authority. This typically involves proving that the signer of the `update` transaction is the true owner or authority of the leaf at the - given index. Since the data is compressed as a hash on the leaf, we can’t + given index. Since the data is compressed as a hash on the leaf, we can't simply compare the `authority` public key to a stored value. Instead, we need to compute the previous hash using the old data and the `authority` listed in the account validation struct. We then build and issue a CPI to the State - Compression Program’s `verify_leaf` instruction using our computed hash. + Compression Program's `verify_leaf` instruction using our computed hash. 2. **Hash the new data** - This step is the same as the first step from appending initial data. Use the `hashv` function from the `keccak` crate to hash the new data and the update authority, each as their corresponding byte @@ -427,7 +427,7 @@ as those used to append the initial data to the tree: `wrap_application_data_v1` to issue a CPI to the Noop program. 4. **Replace the existing leaf hash** - This step is slightly different than the last step of appending initial data. Build and issue a CPI to the State - Compression Program’s `replace_leaf` instruction. This uses the old hash, the + Compression Program's `replace_leaf` instruction. This uses the old hash, the new hash, and the leaf index to replace the data of the leaf at the given index with the new hash. Just as before, this requires the Merkle tree address and the tree authority bump as signature seeds. @@ -504,8 +504,8 @@ pub fn update_message( #### Delete hashes -At the time of writing, the State Compression Program doesn’t provide an -explicit `delete` instruction. Instead, you’ll want to update leaf data with +At the time of writing, the State Compression Program doesn't provide an +explicit `delete` instruction. Instead, you'll want to update leaf data with data that indicates the data as “deleted.” The specific data will depend on your use case and security concerns. Some may opt to set all data to 0, whereas others might store a static string that all “deleted” items will have in common. @@ -513,13 +513,13 @@ others might store a static string that all “deleted” items will have in com #### Access data from a client The discussion so far has covered 3 of the 4 standard CRUD procedures: Create, -Update, and Delete. What’s left is one of the more difficult concepts in state +Update, and Delete. What's left is one of the more difficult concepts in state compression: reading data. -Accessing data from a client is tricky primarily because the data isn’t stored +Accessing data from a client is tricky primarily because the data isn't stored in a format that is easy to access. The data hashes stored in the Merkle tree -account can’t be used to reconstruct the initial data, and the data logged to -the Noop program isn’t available indefinitely. +account can't be used to reconstruct the initial data, and the data logged to +the Noop program isn't available indefinitely. Your best bet is one of two options: @@ -531,18 +531,18 @@ Your best bet is one of two options: If your project is truly decentralized such that many participants will interact with your program through means other than your own frontend, then option 2 might not be sufficient. However, depending on the scale of the project or -whether or not you’ll have control over most program access, it can be a viable +whether or not you'll have control over most program access, it can be a viable approach. There is no “right” way to do this. Two potential approaches are: 1. Store the raw data in a database at the same time as sending it to the program, along with the leaf that the data is hashed and stored to. -2. Create a server that observes your program’s transactions, looks up the +2. Create a server that observes your program's transactions, looks up the associated Noop logs, decodes the logs, and stores them. -We’ll do a little bit of both when writing tests in this lesson’s lab (though we -won’t persist data in a db - it will only live in memory for the duration of the +We'll do a little bit of both when writing tests in this lesson's lab (though we +won't persist data in a db - it will only live in memory for the duration of the tests). The setup for this is somewhat tedious. Given a particular transaction, you can @@ -619,7 +619,7 @@ development experience, please share with the community! ## Lab -Let’s practice generalized state compression by creating a new Anchor program. +Let's practice generalized state compression by creating a new Anchor program. This program will use custom state compression to power a simple note-taking app. @@ -631,8 +631,8 @@ Start by initializing an Anchor program: anchor init compressed-notes ``` -We’ll be using the `spl-account-compression` crate with the `cpi` feature -enabled. Let’s add it as a dependency in `programs/compressed-notes/Cargo.toml`. +We'll be using the `spl-account-compression` crate with the `cpi` feature +enabled. Let's add it as a dependency in `programs/compressed-notes/Cargo.toml`. ```toml [dependencies] @@ -641,8 +641,8 @@ spl-account-compression = { version="0.2.0", features = ["cpi"] } solana-program = "1.16.0" ``` -We’ll be testing locally but we need both the Compression program and the Noop -program from Mainnet. We’ll need to add these to the `Anchor.toml` in the root +We'll be testing locally but we need both the Compression program and the Noop +program from Mainnet. We'll need to add these to the `Anchor.toml` in the root directory so they get cloned to our local cluster. ```toml @@ -656,7 +656,7 @@ address = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV" address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" ``` -Lastly, let’s prepare the `lib.rs` file for the rest of the Demo. Remove the +Lastly, let's prepare the `lib.rs` file for the rest of the Demo. Remove the `initialize` instruction and the `Initialize` accounts struct, then add the imports shown in the code snippet below (be sure to put in **_your_** program id): @@ -689,8 +689,8 @@ pub mod compressed_notes { } ``` -For the rest of this Demo, we’ll be making updates to the program code directly -in the `lib.rs` file. This simplifies the explanations a bit. You’re welcome to +For the rest of this Demo, we'll be making updates to the program code directly +in the `lib.rs` file. This simplifies the explanations a bit. You're welcome to modify the structure as you will. Feel free to build before continuing. This ensures your environment is working @@ -698,7 +698,7 @@ properly and shortens future build times. #### 2. Define `Note` schema -Next, we’re going to define what a note looks like within our program. Notes +Next, we're going to define what a note looks like within our program. Notes should have the following properties: - `leaf_node` - this should be a 32-byte array representing the hash stored on @@ -723,15 +723,15 @@ impl NoteLog { ``` In a traditional Anchor program, this would be an account struct, but since -we’re using state compression, our accounts won’t be mirroring our native -structures. Since we don’t need all the functionality of an account, we can just +we're using state compression, our accounts won't be mirroring our native +structures. Since we don't need all the functionality of an account, we can just use the `AnchorSerialize` derive macro rather than the `account` macro. #### 3. Define input accounts and constraints As luck would have it, every one of our instructions will be using the same -accounts. We’ll create a single `NoteAccounts` struct for our account -validation. It’ll need the following accounts: +accounts. We'll create a single `NoteAccounts` struct for our account +validation. It'll need the following accounts: - `owner` - this is the creator and owner of the note; should be a signer on the transaction @@ -771,7 +771,7 @@ pub struct NoteAccounts<'info> { #### 4. Create `create_note_tree` instruction -Next, let’s create our `create_note_tree` instruction. Remember, clients will +Next, let's create our `create_note_tree` instruction. Remember, clients will have already allocated the Merkle tree account but will use this instruction to initialize it. @@ -834,25 +834,25 @@ and the tree authority bump. #### 5. Create `append_note` instruction -Now, let’s create our `append_note` instruction. This instruction needs to take -the raw note as a String and compress it into a hash that we’ll store on the -Merkle tree. We’ll also log the note to the Noop program so the entirety of the -data exists within the chain’s state. +Now, let's create our `append_note` instruction. This instruction needs to take +the raw note as a String and compress it into a hash that we'll store on the +Merkle tree. We'll also log the note to the Noop program so the entirety of the +data exists within the chain's state. The steps here are as follows: 1. Use the `hashv` function from the `keccak` crate to hash the note and owner, - each as their corresponding byte representation. It’s **_crucial_** that you - hash the owner as well as the note. This is how we’ll verify note ownership + each as their corresponding byte representation. It's **_crucial_** that you + hash the owner as well as the note. This is how we'll verify note ownership before updates in the update instruction. 2. Create an instance of the `NoteLog` struct using the hash from step 1, the - owner’s public key, and the raw note as a String. Then call + owner's public key, and the raw note as a String. Then call `wrap_application_data_v1` to issue a CPI to the Noop program, passing the instance of `NoteLog`. This ensures the entirety of the note (not just the hash) is readily available to any client looking for it. For broad use cases like cNFTs, that would be indexers. You might create your observing client to simulate what indexers are doing but for your own application. -3. Build and issue a CPI to the State Compression Program’s `append` +3. Build and issue a CPI to the State Compression Program's `append` instruction. This takes the hash computed in step 1 and adds it to the next available leaf on your Merkle tree. Just as before, this requires the Merkle tree address and the tree authority bump as signature seeds. @@ -901,14 +901,14 @@ pub mod compressed_notes { #### 6. Create `update_note` instruction -The last instruction we’ll make is the `update_note` instruction. This should +The last instruction we'll make is the `update_note` instruction. This should replace an existing leaf with a new hash representing the new updated note data. -For this to work, we’ll need the following parameters: +For this to work, we'll need the following parameters: 1. `index` - the index of the leaf we are going to update 2. `root` - the root hash of the Merkle tree -3. `old_note` - the string representation of the old note we’re updating +3. `old_note` - the string representation of the old note we're updating 4. `new_note` - the string representation of the new note we want to update to Remember, the steps here are similar to `append_note`, but with some minor @@ -916,22 +916,22 @@ additions and modifications: 1. The first step is new. We need to first prove that the `owner` calling this function is the true owner of the leaf at the given index. Since the data is - compressed as a hash on the leaf, we can’t simply compare the `owner` public + compressed as a hash on the leaf, we can't simply compare the `owner` public key to a stored value. Instead, we need to compute the previous hash using the old note data and the `owner` listed in the account validation struct. We - then build and issue a CPI to the State Compression Program’s `verify_leaf` + then build and issue a CPI to the State Compression Program's `verify_leaf` instruction using our computed hash. 2. This step is the same as the first step from creating the `append_note` instruction. Use the `hashv` function from the `keccak` crate to hash the new note and its owner, each as their corresponding byte representation. 3. This step is the same as the second step from creating the `append_note` instruction. Create an instance of the `NoteLog` struct using the hash from - step 2, the owner’s public key, and the new note as a string. Then call + step 2, the owner's public key, and the new note as a string. Then call `wrap_application_data_v1` to issue a CPI to the Noop program, passing the instance of `NoteLog` 4. This step is slightly different than the last step from creating the `append_note` instruction. Build and issue a CPI to the State Compression - Program’s `replace_leaf` instruction. This uses the old hash, the new hash, + Program's `replace_leaf` instruction. This uses the old hash, the new hash, and the leaf index to replace the data of the leaf at the given index with the new hash. Just as before, this requires the Merkle tree address and the tree authority bump as signature seeds. @@ -1009,19 +1009,19 @@ pub mod compressed_notes { #### 7. Client test setup -We’re going to write a few tests to ensure that our program works as expected. -First, let’s do some setup. +We're going to write a few tests to ensure that our program works as expected. +First, let's do some setup. -We’ll be using the `@solana/spl-account-compression` package. Go ahead and +We'll be using the `@solana/spl-account-compression` package. Go ahead and install it: ```bash yarn add @solana/spl-account-compression ``` -Next, we’re going to give you the contents of a utility file we’ve created to +Next, we're going to give you the contents of a utility file we've created to make testing easier. Create a `utils.ts` file in the `tests` directory, add in -the below, then we’ll explain it. +the below, then we'll explain it. ```typescript import { @@ -1132,21 +1132,21 @@ export async function getNoteLog(connection: Connection, txSignature: string) { There are 3 main things in the above file: -1. `NoteLog` - a class representing the note log we’ll find in the Noop program - logs. We’ve also added the borsh schema as `NoteLogBorshSchema` for +1. `NoteLog` - a class representing the note log we'll find in the Noop program + logs. We've also added the borsh schema as `NoteLogBorshSchema` for deserialization. 2. `getHash` - a function that creates a hash of the note and note owner so we can compare it to what we find on the Merkle tree -3. `getNoteLog` - a function that looks through the provided transaction’s logs, +3. `getNoteLog` - a function that looks through the provided transaction's logs, finds the Noop program logs, then deserializes and returns the corresponding Note log. #### 8. Write client tests -Now that we’ve got our packages installed and utility file ready, let’s dig into -the tests themselves. We’re going to create four of them: +Now that we've got our packages installed and utility file ready, let's dig into +the tests themselves. We're going to create four of them: -1. Create Note Tree - this will create the Merkle tree we’ll be using to store +1. Create Note Tree - this will create the Merkle tree we'll be using to store note hashes 2. Add Note - this will call our `append_note` instruction 3. Add Max Size Note - this will call our `append_note` instruction with a note @@ -1154,11 +1154,11 @@ the tests themselves. We’re going to create four of them: 4. Update First Note - this will call our `update_note` instruction to modify the first note we added -The first test is mostly just for setup. In the last three tests, we’ll be +The first test is mostly just for setup. In the last three tests, we'll be asserting each time that the note hash on the tree matches what we would expect given the note text and signer. -Let’s start with our imports. There are quite a few from Anchor, +Let's start with our imports. There are quite a few from Anchor, `@solana/web3.js`, `@solana/spl-account-compression`, and our own utils file. ```typescript @@ -1183,7 +1183,7 @@ import { getHash, getNoteLog } from "./utils"; import { assert } from "chai"; ``` -Next, we’ll want to set up the state variables we’ll be using throughout our +Next, we'll want to set up the state variables we'll be using throughout our tests. This includes the default Anchor setup as well as generating a Merkle tree keypair, the tree authority, and some notes. @@ -1217,12 +1217,12 @@ describe("compressed-notes", () => { }); ``` -Finally, let’s start with the tests themselves. First the `Create Note Tree` +Finally, let's start with the tests themselves. First the `Create Note Tree` test. This test will do two things: 1. Allocate a new account for the Merkle tree with a max depth of 3, max buffer size of 8, and canopy depth of 0 -2. Initialize this new account using our program’s `createNoteTree` instruction +2. Initialize this new account using our program's `createNoteTree` instruction ```typescript it("Create Note Tree", async () => { @@ -1258,7 +1258,7 @@ it("Create Note Tree", async () => { }); ``` -Next, we’ll create the `Add Note` test. It should call `append_note` with +Next, we'll create the `Add Note` test. It should call `append_note` with `firstNote`, then check that the onchain hash matches our computed hash and that the note log matches the text of the note we passed into the instruction. @@ -1282,7 +1282,7 @@ it("Add Note", async () => { }); ``` -Next, we’ll create the `Add Max Size Note` test. It is the same as the previous +Next, we'll create the `Add Max Size Note` test. It is the same as the previous test, but with the second note. ```typescript @@ -1306,14 +1306,14 @@ it("Add Max Size Note", async () => { }); ``` -Lastly, we’ll create the `Update First Note` test. This is slightly more complex -than adding a note. We’ll do the following: +Lastly, we'll create the `Update First Note` test. This is slightly more complex +than adding a note. We'll do the following: -1. Get the Merkle tree root as it’s required by the instruction. +1. Get the Merkle tree root as it's required by the instruction. 2. Call the `update_note` instruction of our program, passing in the index 0 (for the first note), the Merkle tree root, the first note, and the updated data. Remember, it needs the first note and the root because the program must - verify the entire proof path for the note’s leaf before it can be updated. + verify the entire proof path for the note's leaf before it can be updated. ```typescript it("Update First Note", async () => { @@ -1344,19 +1344,19 @@ it("Update First Note", async () => { }); ``` -That’s it, congrats! Go ahead and run `anchor test` and you should get four +That's it, congrats! Go ahead and run `anchor test` and you should get four passing tests. -If you’re running into issues, feel free to go back through some of the demo or +If you're running into issues, feel free to go back through some of the demo or look at the full solution code in the [Compressed Notes repository](https://github.com/unboxed-software/anchor-compressed-notes). ## Challenge -Now that you’ve practiced the basics of state compression, add a new instruction +Now that you've practiced the basics of state compression, add a new instruction to the Compressed Notes program. This new instruction should allow users to -delete an existing note. keep in mind that you can’t remove a leaf from the -tree, so you’ll need to decide what “deleted” looks like for your program. Good +delete an existing note. keep in mind that you can't remove a leaf from the +tree, so you'll need to decide what “deleted” looks like for your program. Good luck! If you'd like a very simple example of a delete function, check out the diff --git a/content/courses/token-extensions/close-mint.md b/content/courses/token-extensions/close-mint.md index f2751b852..42a0df6eb 100644 --- a/content/courses/token-extensions/close-mint.md +++ b/content/courses/token-extensions/close-mint.md @@ -290,7 +290,7 @@ the local RPC URL. const connection = new Connection("http://127.0.0.1:8899", "confirmed"); ``` -Alternatively, if you’d like to use testnet or devnet, import the +Alternatively, if you'd like to use testnet or devnet, import the `clusterApiUrl` from `@solana/web3.js` and pass it to the connection as such: ```typescript diff --git a/content/courses/token-extensions/default-account-state.md b/content/courses/token-extensions/default-account-state.md index baff06b7a..c0a19a134 100644 --- a/content/courses/token-extensions/default-account-state.md +++ b/content/courses/token-extensions/default-account-state.md @@ -296,7 +296,7 @@ the local RPC URL. const connection = new Connection("http://127.0.0.1:8899", "confirmed"); ``` -Alternatively, if you’d like to use testnet or devnet, import the +Alternatively, if you'd like to use testnet or devnet, import the `clusterApiUrl` from `@solana/web3.js` and pass it to the connection as such: ```typescript @@ -647,7 +647,7 @@ esrun src/index.ts #### 7.3 Transferring without thawing the recipient's account -Now that we’ve tested minting, we can test transferring our tokens frozen and +Now that we've tested minting, we can test transferring our tokens frozen and not. First lets test a transfer without thawing the recipient's token account. Remember, by default, the `otherTokenAccountKeypair` is frozen due to the extension. @@ -746,7 +746,7 @@ Remember the key takeaways: accounts. - Frozen account's balance cannot change. -Congratulations! We’ve just created and tested a mint using the default account +Congratulations! We've just created and tested a mint using the default account extension! ## Challenge diff --git a/content/courses/token-extensions/immutable-owner.md b/content/courses/token-extensions/immutable-owner.md index b5342a53a..d5eb17192 100644 --- a/content/courses/token-extensions/immutable-owner.md +++ b/content/courses/token-extensions/immutable-owner.md @@ -182,7 +182,7 @@ the local RPC URL. const connection = new Connection("http://127.0.0.1:8899", "confirmed"); ``` -Alternatively, if you’d like to use testnet or devnet, import the +Alternatively, if you'd like to use testnet or devnet, import the `clusterApiUrl` from `@solana/web3.js` and pass it to the connection as such: ```typescript @@ -360,7 +360,7 @@ const signature = await sendAndConfirmTransaction(connection, transaction, [ return signature; ``` -Now that we’ve added the functionality for `token-helper`, we can create our +Now that we've added the functionality for `token-helper`, we can create our test token accounts. One of the two test token accounts will be created by calling `createTokenAccountWithImmutableOwner`. The other will be created with the baked-in SPL helper function `createAssociatedTokenAccount`. This helper @@ -475,7 +475,7 @@ Now we can run `npx esrun src/index.ts`. This test should log a failure message similar to the one from the previous test. This means that both of our token accounts are in fact immutable and working as intended. -Congratulations! We’ve just created token accounts and tested the immutable +Congratulations! We've just created token accounts and tested the immutable owner extension! If you are stuck at any point, you can find the working code on the `solution` branch of [this repository](https://github.com/Unboxed-Software/solana-lab-immutable-owner/tree/solution). diff --git a/content/courses/token-extensions/interest-bearing-token.md b/content/courses/token-extensions/interest-bearing-token.md index ba44e30ad..29cfa7735 100644 --- a/content/courses/token-extensions/interest-bearing-token.md +++ b/content/courses/token-extensions/interest-bearing-token.md @@ -542,7 +542,7 @@ Now run `npx esrun src/index.ts`. This is expected to fail and log out **Mint tokens and read interest rate** -So we’ve tested updating the interest rate. How do we check that the accrued +So we've tested updating the interest rate. How do we check that the accrued interest increases when an account mints more tokens? We can use the `amountToUiAmount` and `getAccount` helpers from the SPL library to help us achieve this. @@ -690,7 +690,7 @@ try { This is expected to work and the new interest rate should be 10. -Thats it! We’ve just created an interest bearing token, updated the interest +Thats it! We've just created an interest bearing token, updated the interest rate and logged the updated state of the token! ## Challenge diff --git a/content/courses/token-extensions/non-transferable-token.md b/content/courses/token-extensions/non-transferable-token.md index f9f2d4c43..c62ceceb0 100644 --- a/content/courses/token-extensions/non-transferable-token.md +++ b/content/courses/token-extensions/non-transferable-token.md @@ -303,7 +303,7 @@ esrun src/index.ts ``` The non-transferable mint has been set up correctly and will be created when we -run `npm start`. Let’s move on to the next step and create a source account and +run `npm start`. Let's move on to the next step and create a source account and mint a token to it. #### 4. Mint token diff --git a/content/courses/token-extensions/permanent-delegate.md b/content/courses/token-extensions/permanent-delegate.md index bbc26b9a1..5576ca0af 100644 --- a/content/courses/token-extensions/permanent-delegate.md +++ b/content/courses/token-extensions/permanent-delegate.md @@ -338,7 +338,7 @@ the local RPC URL. const connection = new Connection("http://127.0.0.1:8899", "confirmed"); ``` -Alternatively, if you’d like to use testnet or devnet, import the +Alternatively, if you'd like to use testnet or devnet, import the `clusterApiUrl` from `@solana/web3.js` and pass it to the connection as such: ```typescript diff --git a/content/courses/token-extensions/required-memo.md b/content/courses/token-extensions/required-memo.md index c5f5decf3..43bab1061 100644 --- a/content/courses/token-extensions/required-memo.md +++ b/content/courses/token-extensions/required-memo.md @@ -242,7 +242,7 @@ the local RPC URL. `const connection = new Connection("http://127.0.0.1:8899", "confirmed");` -Alternatively, if you’d like to use testnet or devnet, import the +Alternatively, if you'd like to use testnet or devnet, import the `clusterApiUrl` from `@solana/web3.js` and pass it to the connection as such: ```typescript @@ -600,7 +600,7 @@ extension. npx esrun src/index.ts ``` -Congratulations! We’ve just tested the required memo extension! +Congratulations! We've just tested the required memo extension! ## Challenge diff --git a/content/courses/token-extensions/transfer-fee.md b/content/courses/token-extensions/transfer-fee.md index f981c5e98..16cd7ff93 100644 --- a/content/courses/token-extensions/transfer-fee.md +++ b/content/courses/token-extensions/transfer-fee.md @@ -535,7 +535,7 @@ the local RPC URL. const connection = new Connection("http://127.0.0.1:8899", "confirmed"); ``` -Alternatively, if you’d like to use testnet or devnet, import the +Alternatively, if you'd like to use testnet or devnet, import the `clusterApiUrl` from `@solana/web3.js` and pass it to the connection as such: ```typescript diff --git a/content/courses/tokens-and-nfts/token-program.md b/content/courses/tokens-and-nfts/token-program.md index 08794cf32..feb79adc9 100644 --- a/content/courses/tokens-and-nfts/token-program.md +++ b/content/courses/tokens-and-nfts/token-program.md @@ -402,7 +402,7 @@ async function buildMintToTransaction( SPL Token transfers require both the sender and receiver to have token accounts for the mint of the tokens being transferred. The tokens are transferred from -the sender’s token account to the receiver’s token account. +the sender's token account to the receiver's token account. You can use `getOrCreateAssociatedTokenAccount` when obtaining the receiver's associated token account to ensure their token account exists before the @@ -457,7 +457,7 @@ async function buildTransferTransaction( ### Lab -We’re going to use the Token Program to create a Token Mint, create an +We're going to use the Token Program to create a Token Mint, create an Associated Token Account, mint tokens, transfer tokens, and burn tokens. Assuming you already have a `.env` file with a `SECRET_KEY` setup per @@ -838,7 +838,7 @@ balance go up! ### Challenge -Now it’s your turn to build something independently. Create an application that +Now it's your turn to build something independently. Create an application that allows a user to create a new mint, create a token account, and mint tokens. To interact with the Token Program using the wallet adapter, you will have to diff --git a/content/guides/advanced/stake-weighted-qos.md b/content/guides/advanced/stake-weighted-qos.md index 7fe090140..3a59738a8 100644 --- a/content/guides/advanced/stake-weighted-qos.md +++ b/content/guides/advanced/stake-weighted-qos.md @@ -106,7 +106,7 @@ Stake-weighted QoS will not work unless BOTH sides are properly configured. ### Configuring the Validator node -On the validator, you’ll have to enable +On the validator, you'll have to enable `--staked-nodes-overrides /path/to/overrides.yml`. The `--staked-nodes-overrides` flag helps the validator prioritize transactions being sent from known sources to apply stake to their transactions. This can @@ -114,7 +114,7 @@ help a validator prioritize certain transactions over known hosts over others, enabling the usage of Stake-weighted QoS with RPCs. RPCs should not be staked in any way. -Today, Stake-weighted QoS gives a stake-weighted priority to 80% of a leader’s +Today, Stake-weighted QoS gives a stake-weighted priority to 80% of a leader's TPU capacity. However, there are configuration options which can be used to virtually assign different stake-weights to TPU peers, including assigning unstaked peers virtual stake. @@ -130,7 +130,7 @@ staked_map_id: `staked_map_id` contains a map of identity public key to the stake amount in lamports to apply to each RPC. When set, the validator will prioritize QUIC connections with the RPC found at that identity publicKey, assigning an amount -of stake to their transactions. The 80% of the leader’s TPU capacity will be +of stake to their transactions. The 80% of the leader's TPU capacity will be split proportionally based on the lamport amounts specified in the `staked-nodes-overrides` file and existing cluster stake. diff --git a/content/guides/games/hello-world.md b/content/guides/games/hello-world.md index 9befc65a6..23e4b4bab 100644 --- a/content/guides/games/hello-world.md +++ b/content/guides/games/hello-world.md @@ -195,7 +195,7 @@ Alternatively, you can use the signer's address as an extra seed in the ### Move Left Instruction -Now that we can initialize a `GameDataAccount` account, let’s implement the +Now that we can initialize a `GameDataAccount` account, let's implement the `move_left` instruction which allows a player update their `player_position`. In this example, moving left simply means decrementing the `player_position` @@ -233,8 +233,8 @@ pub struct MoveLeft<'info> { ### Move Right Instruction -Lastly, let’s implement the `move_right` instruction. Similarly, moving right -will simply mean incrementing the `player_position` by 1. We’ll also limit the +Lastly, let's implement the `move_right` instruction. Similarly, moving right +will simply mean incrementing the `player_position` by 1. We'll also limit the maximum position to 3. Just like before, the only account needed for this instruction is the @@ -381,7 +381,7 @@ file and add the code snippets from the following sections. ### Derive the GameDataAccount Account Address -First, let’s derive the PDA for the `GameDataAccount` using the +First, let's derive the PDA for the `GameDataAccount` using the `findProgramAddress` function. > A [Program Derived Address (PDA)](/docs/core/pda.md) is unique address in the @@ -398,7 +398,7 @@ const [globalLevel1GameDataAccount, bump] = ### Initialize the Game State -Next, let’s try to fetch the game data account using the PDA from the previous +Next, let's try to fetch the game data account using the PDA from the previous step. If the account doesn't exist, we'll create it by invoking the `initialize` instruction from our program. @@ -460,8 +460,8 @@ console.log("Player position is:", gameDateAccount.playerPosition.toString()); ### Logging the Player's Position -Lastly, let’s use a `switch` statement to log the character's position based on -the `playerPosition` value stored in the `gameDateAccount`. We’ll use this as a +Lastly, let's use a `switch` statement to log the character's position based on +the `playerPosition` value stored in the `gameDateAccount`. We'll use this as a visual representation of the character's movement in the game. ```ts filename="client.ts" diff --git a/content/guides/games/interact-with-tokens.md b/content/guides/games/interact-with-tokens.md index 3e74748b6..d36375972 100644 --- a/content/guides/games/interact-with-tokens.md +++ b/content/guides/games/interact-with-tokens.md @@ -88,13 +88,13 @@ pub mod anchor_token { ``` Here we are simply bringing into scope the crates and corresponding modules we -will be using for this program. We’ll be using the `anchor_spl` and +will be using for this program. We'll be using the `anchor_spl` and `mpl_token_metadata` crates to help us interact with the SPL Token program and Metaplex's Token Metadata program. ## Create Mint instruction -First, let’s implement an instruction to create a new token mint and its +First, let's implement an instruction to create a new token mint and its metadata account. The on-chain token metadata, including the name, symbol, and URI, will be provided as parameters to the instruction. @@ -107,7 +107,7 @@ The `create_mint` instruction requires the following accounts: - `admin` - the `ADMIN_PUBKEY` that signs the transaction and pays for the initialization of the accounts - `reward_token_mint` - the new token mint we are initializing, using a PDA as - both the mint account’s address and its mint authority + both the mint account's address and its mint authority - `metadata_account` - the metadata account we are initializing for the token mint - `token_program` - required for interacting with instructions on the Token @@ -295,7 +295,7 @@ health by 10 and mints 1 token to the player's token account as a reward. The `kill_enemy` instruction requires the following accounts: - `player` - the player receiving the token -- `player_data` - the player data account storing the player’s current health +- `player_data` - the player data account storing the player's current health - `player_token_account` - the player's associated token account where tokens will be minted - `reward_token_mint` - the token mint account, specifying the type of token @@ -389,7 +389,7 @@ pub enum ErrorCode { ``` The player's health is reduced by 10 to represent the “battle with the enemy”. -We’ll also check the player's current health and return a custom Anchor error if +We'll also check the player's current health and return a custom Anchor error if the player has 0 health. The instruction then uses a cross-program invocation (CPI) to call the `mint_to` @@ -409,7 +409,7 @@ token and restore their health to its maximum value. The `heal` instruction requires the following accounts: - `player` - the player executing the healing action -- `player_data` - the player data account storing the player’s current health +- `player_data` - the player data account storing the player's current health - `player_token_account` - the player's associated token account where the tokens will be burned - `reward_token_mint` - the token mint account, specifying the type of token diff --git a/content/guides/games/store-sol-in-pda.md b/content/guides/games/store-sol-in-pda.md index a024af0f7..d472df7f7 100644 --- a/content/guides/games/store-sol-in-pda.md +++ b/content/guides/games/store-sol-in-pda.md @@ -180,7 +180,7 @@ displays the starting message. The `initialize_level_one` instruction requires 4 accounts: - `new_game_data_account` - the `GameDataAccount` we are initializing to store - the player’s position + the player's position - `chest_vault` - the `ChestVaultAccount` we are initializing to store the SOL reward - `signer` - the player paying for the initialization of the accounts diff --git a/content/guides/getstarted/cosmwasm-to-solana.md b/content/guides/getstarted/cosmwasm-to-solana.md index 5709298fc..04666aa67 100644 --- a/content/guides/getstarted/cosmwasm-to-solana.md +++ b/content/guides/getstarted/cosmwasm-to-solana.md @@ -408,7 +408,7 @@ pub fn process_reset( ## Solana Program Advantages 1. Performance Efficiency: - - Solana’s binary instruction data and direct account manipulation provide + - Solana's binary instruction data and direct account manipulation provide high performance and low latency. - This is critical for high-throughput applications like decentralized exchanges (DEXes) and other performance-sensitive use cases. @@ -424,6 +424,6 @@ pub fn process_reset( specialized logic. In conclusion, Solana is ideal for applications that require high performance, -low latency, and fine-grained control over execution. It’s better suited for +low latency, and fine-grained control over execution. It's better suited for developers comfortable with lower-level programming and those who need to optimize for specific use cases. diff --git a/content/guides/getstarted/intro-to-anchor.md b/content/guides/getstarted/intro-to-anchor.md index 3012c23cb..7b098c196 100644 --- a/content/guides/getstarted/intro-to-anchor.md +++ b/content/guides/getstarted/intro-to-anchor.md @@ -27,7 +27,7 @@ and abstractions that make building Solana programs more intuitive and secure. The main macros found in an Anchor program include: - [`declare_id`](#declare_id-macro): Specifies the program's on-chain address -- [`#[program]`](#program-macro): Specifies the module containing the program’s +- [`#[program]`](#program-macro): Specifies the module containing the program's instruction logic - [`#[derive(Accounts)]`](#derive-accounts-macro): Applied to structs to indicate a list of accounts required for an instruction diff --git a/content/guides/getstarted/local-rust-hello-world.md b/content/guides/getstarted/local-rust-hello-world.md index 864c80399..88b4b8692 100644 --- a/content/guides/getstarted/local-rust-hello-world.md +++ b/content/guides/getstarted/local-rust-hello-world.md @@ -306,7 +306,7 @@ await connection.confirmTransaction({ }); console.log( - `Congratulations! Look at your ‘Hello World’ transaction in the Solana Explorer: + `Congratulations! Look at your ‘Hello World' transaction in the Solana Explorer: https://explorer.solana.com/tx/${txHash}?cluster=custom`, ); ``` @@ -325,7 +325,7 @@ node client.mjs You should see the following output: ```shell -Congratulations! Look at your ‘Hello World’ transaction in the Solana Explorer: +Congratulations! Look at your ‘Hello World' transaction in the Solana Explorer: https://explorer.solana.com/tx/2fTcQ74z4DVi8WRuf2oNZ36z7k9tGRThaRPXBMYgjMUNUbUSKLrP6djpRUZ8msuTXvZHFe3UXi31dfgytG2aJZbv?cluster=custom ``` diff --git a/content/guides/getstarted/rust-to-solana.md b/content/guides/getstarted/rust-to-solana.md index 883f372c8..3c001d31b 100644 --- a/content/guides/getstarted/rust-to-solana.md +++ b/content/guides/getstarted/rust-to-solana.md @@ -35,13 +35,13 @@ need to know to start their Solana journeys. ## Understanding the Core Differences First, note that this guide aims at understanding the differences in using Rust -as a language when working with Solana. It won’t cover +as a language when working with Solana. It won't cover [Blockchain or Solana basics](https://solana.com/learn/blockchain-basics). -It also won’t cover core Solana concepts that must be understood in order to +It also won't cover core Solana concepts that must be understood in order to program in Solana, such as: -- [Programs](https://solana.com/docs/core/programs) - Solana’s version of smart +- [Programs](https://solana.com/docs/core/programs) - Solana's version of smart contracts - [Accounts](https://solana.com/docs/core/accounts) - A record in the Solana ledger that either holds data (a data account) or is an executable program @@ -53,7 +53,7 @@ program in Solana, such as: For more information on those core concepts, check out the [Solana developer documentation](https://solana.com/docs). -Let’s now look at the differences in **project setup**. +Let's now look at the differences in **project setup**. ## Key Setup Details @@ -152,7 +152,7 @@ Using an additional crate that depends on `rand` will also cause compile errors. However, if the crate used simply depends on `rand` but does not actually generate random numbers, then it is possible to work around this by adding the -following to the program’s Cargo.toml: +following to the program's Cargo.toml: ```toml [dependencies] @@ -222,7 +222,7 @@ allows developers to develop and deploy Solana programs. ![Solana Playground](/assets/guides/rust-to-solana/solana-playground.png) -It’s the easiest way to begin developing with Solana, and it supports building, +It's the easiest way to begin developing with Solana, and it supports building, testing, and deploying Solana Rust programs. Additionally, a number of built-in tutorials are available to guide learning. @@ -247,7 +247,7 @@ and then use `anchor init ` to create a new Anchor project. ## Creating offchain Programs So far, this guide has covered the key details of developing **onchain Solana -programs** in Rust. However, it’s also possible to develop **offchain Solana +programs** in Rust. However, it's also possible to develop **offchain Solana clients** in Rust. This can be done by using the [solana_sdk crate](https://docs.rs/solana-sdk/latest/solana_sdk/). This contains the [solana_client crate](https://docs.rs/solana-client/latest/solana_client/) diff --git a/content/guides/getstarted/scaffold-nextjs-anchor.md b/content/guides/getstarted/scaffold-nextjs-anchor.md index 5adfef9a4..95729eb8d 100644 --- a/content/guides/getstarted/scaffold-nextjs-anchor.md +++ b/content/guides/getstarted/scaffold-nextjs-anchor.md @@ -57,7 +57,7 @@ If you haven't installed Solana CLI, Rust, or Anchor before, you can easily do so by [following our helpful installation guide](https://solana.com/docs/intro/installation) -> This scaffolds only supports TypeScript for now, but don’t worry, TypeScript +> This scaffolds only supports TypeScript for now, but don't worry, TypeScript > simply extends on the JavaScript you already know to add helpful type > definitions. diff --git a/content/guides/getstarted/solana-test-validator.md b/content/guides/getstarted/solana-test-validator.md index c69046e2c..a2dd52058 100644 --- a/content/guides/getstarted/solana-test-validator.md +++ b/content/guides/getstarted/solana-test-validator.md @@ -76,7 +76,7 @@ Once you have the `solana-test-validator` up and running, you can interact with it using various Solana CLI (Command Line Interface) commands. These commands let you [deploy programs](/docs/programs/deploying.md), manage [accounts](/docs/core/accounts.md), send -[transactions](/docs/core/transactions.md), and much more. Here’s a detailed +[transactions](/docs/core/transactions.md), and much more. Here's a detailed guide on the key commands you will use. ### Checking the Status of the Test Validator diff --git a/content/guides/javascript/get-program-accounts.md b/content/guides/javascript/get-program-accounts.md index 3b750e21d..dedc4c497 100644 --- a/content/guides/javascript/get-program-accounts.md +++ b/content/guides/javascript/get-program-accounts.md @@ -144,7 +144,7 @@ token accounts that are owned by our wallet address. When looking at a token account, we can see the first two fields stored on a token account are both pubkeys, and that each pubkey is 32 bytes in length. Given that `owner` is the second field, we should begin our `memcmp` at an `offset` of 32 bytes. From -here, we’ll be looking for accounts whose owner field matches our wallet +here, we'll be looking for accounts whose owner field matches our wallet address. ![Account Size](/public/assets/guides/get-program-accounts/memcmp.png) @@ -217,7 +217,7 @@ Much like `memcmp`, `dataSlice` accepts two arguments: - `length`: The number of bytes which should be returned `dataSlice` is particularly useful when we run queries on a large dataset but -don’t actually care about the account data itself. An example of this would be +don't actually care about the account data itself. An example of this would be if we wanted to find the number of token accounts (i.e. number of token holders) for a particular token mint. @@ -301,7 +301,7 @@ Found 3 token account(s) for mint BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf ``` By combining all three parameters (`dataSlice`, `dataSize`, and `memcmp`) we can -limit the scope of our query and efficiently return only the data we’re +limit the scope of our query and efficiently return only the data we're interested in. ## Other Resources diff --git a/content/guides/token-extensions/getting-started.md b/content/guides/token-extensions/getting-started.md index 67c2ac233..5e43e6a42 100644 --- a/content/guides/token-extensions/getting-started.md +++ b/content/guides/token-extensions/getting-started.md @@ -105,7 +105,7 @@ make sense to combine: - Confidential transfer + permanent delegate Other than these, you have the option to customize with any combination of token -extensions that suit your project’s needs. +extensions that suit your project's needs. ## How do I add custom logic to my tokens with token extensions? @@ -125,7 +125,7 @@ It is important to note that while transfer hooks give the capability to insert custom logic within a transfer, all accounts from the initial transfer are converted to read-only accounts. This means that the signer privileges of the sender do not extend to the Transfer Hook program. This is to avoid potential -unexpected logic executing on someone’s wallet who interacts with a token with +unexpected logic executing on someone's wallet who interacts with a token with transfer hooks, protecting the users. You can diff --git a/content/guides/token-extensions/transfer-hook.md b/content/guides/token-extensions/transfer-hook.md index b5b70559b..685850004 100644 --- a/content/guides/token-extensions/transfer-hook.md +++ b/content/guides/token-extensions/transfer-hook.md @@ -790,7 +790,7 @@ create_account( )?; ``` -Once we’ve created the account, we initialize the account data to store the list +Once we've created the account, we initialize the account data to store the list of ExtraAccountMetas. ```rust @@ -810,7 +810,7 @@ ExtraAccountMetas account. ### Custom Transfer Hook Instruction -Next, let’s implement the custom `transfer_hook` instruction. This is the +Next, let's implement the custom `transfer_hook` instruction. This is the instruction the Token Extension program will invoke on every token transfer. In this example, we will require a fee paid in wSOL for every token transfer. diff --git a/content/guides/wallets/add-solana-wallet-adapter-to-nextjs.md b/content/guides/wallets/add-solana-wallet-adapter-to-nextjs.md index f360fd035..e0e8f5d1c 100644 --- a/content/guides/wallets/add-solana-wallet-adapter-to-nextjs.md +++ b/content/guides/wallets/add-solana-wallet-adapter-to-nextjs.md @@ -148,7 +148,7 @@ import the provided standard CSS styles required for these react components to be displayed properly in our application. Each of these styles can be easily overridden to customize the look. -Let’s import these dependencies and use them further in the context/provider +Let's import these dependencies and use them further in the context/provider component we are building: ```tsx filename=AppWalletProvider.tsx @@ -340,7 +340,7 @@ side of your app that is a child of your `AppWalletAdapter` context. In this example guide, it will be your entire application. - the `useWallet` hook has details like `publicKey` and state of the wallet, - whether it’s `connecting` or it’s `connected`. + whether it's `connecting` or it's `connected`. - the `useConnection` hook will facilitate your application's connection to the Solana blockchain, via your RPC endpoint @@ -397,7 +397,7 @@ const getAirdropOnClick = async () => { ### Getting a wallet balance -Here’s an example of getting the SOL balance of the wallet connected using the +Here's an example of getting the SOL balance of the wallet connected using the `useConnection` and `useWallet` hooks. [`getBalance`](https://solana.com/docs/rpc/http/getbalance#parameters) is an RPC @@ -425,7 +425,7 @@ With functions like these and the ones provided within the wallet adapter packages, you can detect whether the user's wallet is connected or not, create a button to get an airdrop of devnet or SOL in the network defined, and more. -Let’s make another page now to demonstrate how we can use each of these hooks to +Let's make another page now to demonstrate how we can use each of these hooks to access actually access the `connection` object and your user's wallet state to send or sign transactions, read the wallet balance, and test functionality. diff --git a/content/workshops/solana-101.md b/content/workshops/solana-101.md index 1210c2ed8..72850d2b4 100644 --- a/content/workshops/solana-101.md +++ b/content/workshops/solana-101.md @@ -9,7 +9,7 @@ repoUrl: https://github.com/Solana-Workshops/solana-101 duration: "2 hours" objectives: - The Solana Network - - Solana’s Programming Model + - Solana's Programming Model - Tokens & NFTs tags: - Introduction @@ -36,7 +36,7 @@ authorGithubUsername: buffalojoec - Technical Advantages - Network Overview -#### Solana’s Programming Model +#### Solana's Programming Model - Accounts @@ -64,19 +64,19 @@ authorGithubUsername: buffalojoec ### Why Solana? -Let’s talk about the main technological advantages to building a decentralized +Let's talk about the main technological advantages to building a decentralized application on Solana. -Solana has extremely fast block confirmation times, so users don’t have to wait +Solana has extremely fast block confirmation times, so users don't have to wait to make sure their action worked. -Solana’s transaction fees are exceptionally low, so developers can build more +Solana's transaction fees are exceptionally low, so developers can build more robust user experiences that cost less. -Let’s take a brief look at how Solana’s network creates blocks and processes +Let's take a brief look at how Solana's network creates blocks and processes transactions. Like most proof-of-stake networks, Solana elects a leader for each block -creation cycle, who’s responsible for creating a new block. +creation cycle, who's responsible for creating a new block. Unlike Ethereum - Solana does not use a mempool. Instead, it forwards new transactions to the next leader in the block creation cycle, which means when @@ -86,12 +86,12 @@ into a new block. Next, Solana leverages a high-throughput engine called Turbine that disseminates information about a new block to the rest of the network. -When a block’s transactions are executed, Solana’s runtime actually allows the +When a block's transactions are executed, Solana's runtime actually allows the operations within each transaction to run in parallel wherever possible. The combination of these 3 innovations leads to greatly increased speed and throughput for the network. -Solana’s most popular innovation is Proof-of-History, which leverages a +Solana's most popular innovation is Proof-of-History, which leverages a Verifiable-Delay Function (VDF) to allow all nodes in the network to agree on the passage of time. @@ -100,23 +100,23 @@ Weighted QoS, makes it perfect for high-performance applications. ### Programming on Solana -Now let’s dive into the concepts you’ll need to know when programming on Solana. -The first thing we’ll want to understand is the concept of an account. +Now let's dive into the concepts you'll need to know when programming on Solana. +The first thing we'll want to understand is the concept of an account. #### Account An account on Solana is a slice of data from the blockchain. Everything on Solana is an account! You can kind of think of it like a -computer’s file system - where everything is a file! +computer's file system - where everything is a file! Every account has a unique address, holds some balance of SOL, and can store arbitrary data. Based on the size of that arbitrary data, a user is required to -pay some value of SOL for what’s called “Rent”. +pay some value of SOL for what's called “Rent”. Since this is blockchain data, anyone can read from an account. Also, anyone can -credit SOL or tokens to an account. However, only an account’s owner can modify -its data - which includes debiting it’s SOL balance. +credit SOL or tokens to an account. However, only an account's owner can modify +its data - which includes debiting it's SOL balance. ``` { @@ -134,9 +134,9 @@ its data - which includes debiting it’s SOL balance. If we take a look at what an actual account looks like in raw form, we can see some of the fields present on all accounts shown here. -The “key” field is just that account’s address. +The “key” field is just that account's address. -The “lamports” field simply tracks that account’s current balance of SOL. +The “lamports” field simply tracks that account's current balance of SOL. Lamports are the smaller denomination of SOL. “Data” is where the arbitrary data is stored inside of an account. @@ -145,7 +145,7 @@ If that arbitrary data stored in this account is actually an executable program, the “is_executable” boolean will be set to true. Lastly, the “owner” field determines which Solana program has the authority to -perform changes to this account’s data, including its balance of Lamports. +perform changes to this account's data, including its balance of Lamports. #### Programs @@ -157,9 +157,9 @@ we mentioned before. Right now, Solana programs can be written in Rust, C/C++ or Python. Soon, we may be able to write programs in other languages - such as TypeScript and GoLang. -Unlike Ethereum’s “smart contracts”, programs don’t actually have state of their +Unlike Ethereum's “smart contracts”, programs don't actually have state of their own. Instead, they perform reads and writes on accounts from the blockchain. To -perform a write, this program must be the designated owner of the account it’s +perform a write, this program must be the designated owner of the account it's attempting to modify. Programs are designed to process what are called “instructions”, and they can also send these instructions to other programs on the network. diff --git a/docs/advanced/confirmation.md b/docs/advanced/confirmation.md index 98e56cb57..02cc91ad5 100644 --- a/docs/advanced/confirmation.md +++ b/docs/advanced/confirmation.md @@ -40,7 +40,7 @@ where the magic happens and at a high level it consists of four components: - a **list of accounts** to load, and - a **“recent blockhash.”** -In this article, we’re going to be focusing a lot on a transaction’s +In this article, we're going to be focusing a lot on a transaction's [recent blockhash](/docs/terminology.md#blockhash) because it plays a big role in transaction confirmation. @@ -65,14 +65,14 @@ touch on everything except steps 1 and 4. A [“blockhash”](/docs/terminology.md#blockhash) refers to the last Proof of History (PoH) hash for a [“slot”](/docs/terminology.md#slot) (description -below). Since Solana uses PoH as a trusted clock, a transaction’s recent +below). Since Solana uses PoH as a trusted clock, a transaction's recent blockhash can be thought of as a **timestamp**. ### Proof of History refresher -Solana’s Proof of History mechanism uses a very long chain of recursive SHA-256 +Solana's Proof of History mechanism uses a very long chain of recursive SHA-256 hashes to build a trusted clock. The “history” part of the name comes from the -fact that block producers hash transaction id’s into the stream to record which +fact that block producers hash transaction id's into the stream to record which transactions were processed in their block. [PoH hash calculation](https://github.com/anza-xyz/agave/blob/aa0922d6845e119ba466f88497e8209d1c82febc/entry/src/poh.rs#L79): @@ -123,7 +123,7 @@ the runtime. ### Example of transaction expiration -Let’s walk through a quick example: +Let's walk through a quick example: 1. A validator is actively producing a new block for the current slot 2. The validator receives a transaction from a user with the recent blockhash @@ -138,26 +138,26 @@ Let’s walk through a quick example: then starts producing the block for the next slot (validators get to produce blocks for 4 consecutive slots) 6. The validator checks that same transaction again and finds it is now 152 - blockhashes old and rejects it because it’s too old :( + blockhashes old and rejects it because it's too old :( ## Why do transactions expire? -There’s a very good reason for this actually, it’s to help validators avoid +There's a very good reason for this actually, it's to help validators avoid processing the same transaction twice. A naive brute force approach to prevent double processing could be to check -every new transaction against the blockchain’s entire transaction history. But +every new transaction against the blockchain's entire transaction history. But by having transactions expire after a short amount of time, validators only need to check if a new transaction is in a relatively small set of _recently_ processed transactions. ### Other blockchains -Solana’s approach of prevent double processing is quite different from other +Solana's approach of prevent double processing is quite different from other blockchains. For example, Ethereum tracks a counter (nonce) for each transaction sender and will only process transactions that use the next valid nonce. -Ethereum’s approach is simple for validators to implement, but it can be +Ethereum's approach is simple for validators to implement, but it can be problematic for users. Many people have encountered situations when their Ethereum transactions got stuck in a _pending_ state for a long time and all the later transactions, which used higher nonce values, were blocked from @@ -165,12 +165,12 @@ processing. ### Advantages on Solana -There are a few advantages to Solana’s approach: +There are a few advantages to Solana's approach: 1. A single fee payer can submit multiple transactions at the same time that are - allowed to be processed in any order. This might happen if you’re using + allowed to be processed in any order. This might happen if you're using multiple applications at the same time. -2. If a transaction doesn’t get committed to a block and expires, users can try +2. If a transaction doesn't get committed to a block and expires, users can try again knowing that their previous transaction will NOT ever be processed. By not using counters, the Solana wallet experience may be easier for users to @@ -181,7 +181,7 @@ quickly and avoid annoying pending states. Of course there are some disadvantages too: -1. Validators have to actively track a set of all processed transaction id’s to +1. Validators have to actively track a set of all processed transaction id's to prevent double processing. 2. If the expiration time period is too short, users might not be able to submit their transaction before it expires. @@ -189,7 +189,7 @@ Of course there are some disadvantages too: These disadvantages highlight a tradeoff in how transaction expiration is configured. If the expiration time of a transaction is increased, validators need to use more memory to track more transactions. If expiration time is -decreased, users don’t have enough time to submit their transaction. +decreased, users don't have enough time to submit their transaction. Currently, Solana clusters require that transactions use blockhashes that are no more than 151 blocks old. @@ -208,27 +208,27 @@ target time of 400ms. One minute is not a lot of time considering that a client needs to fetch a recent blockhash, wait for the user to sign, and finally hope that the -broadcasted transaction reaches a leader that is willing to accept it. Let’s go +broadcasted transaction reaches a leader that is willing to accept it. Let's go through some tips to help avoid confirmation failures due to transaction expiration! ### Fetch blockhashes with the appropriate commitment level -Given the short expiration time frame, it’s imperative that clients and +Given the short expiration time frame, it's imperative that clients and applications help users create transactions with a blockhash that is as recent as possible. When fetching blockhashes, the current recommended RPC API is called [`getLatestBlockhash`](/docs/rpc/http/getLatestBlockhash.mdx). By default, this API uses the `finalized` commitment level to return the most recently finalized -block’s blockhash. However, you can override this behavior by +block's blockhash. However, you can override this behavior by [setting the `commitment` parameter](/docs/rpc/index.mdx#configuring-state-commitment) to a different commitment level. **Recommendation** The `confirmed` commitment level should almost always be used for RPC requests -because it’s usually only a few slots behind the `processed` commitment and has +because it's usually only a few slots behind the `processed` commitment and has a very low chance of belonging to a dropped [fork](https://docs.solanalabs.com/consensus/fork-generation). @@ -237,10 +237,10 @@ But feel free to consider the other options: - Choosing `processed` will let you fetch the most recent blockhash compared to other commitment levels and therefore gives you the most time to prepare and process a transaction. But due to the prevalence of forking in the Solana - blockchain, roughly 5% of blocks don’t end up being finalized by the cluster - so there’s a real chance that your transaction uses a blockhash that belongs + blockchain, roughly 5% of blocks don't end up being finalized by the cluster + so there's a real chance that your transaction uses a blockhash that belongs to a dropped fork. Transactions that use blockhashes for abandoned blocks - won’t ever be considered recent by any blocks that are in the finalized + won't ever be considered recent by any blocks that are in the finalized blockchain. - Using the [default commitment](/docs/rpc#default-commitment) level `finalized` will eliminate any risk that the blockhash you choose will belong to a dropped @@ -259,22 +259,22 @@ into issues due to one node lagging behind the other. When RPC nodes receive a `sendTransaction` request, they will attempt to determine the expiration block of your transaction using the most recent finalized block or with the block selected by the `preflightCommitment` -parameter. A **VERY** common issue is that a received transaction’s blockhash +parameter. A **VERY** common issue is that a received transaction's blockhash was produced after the block used to calculate the expiration for that -transaction. If an RPC node can’t determine when your transaction expires, it +transaction. If an RPC node can't determine when your transaction expires, it will only forward your transaction **one time** and afterwards will then **drop** the transaction. Similarly, when RPC nodes receive a `simulateTransaction` request, they will simulate your transaction using the most recent finalized block or with the block selected by the `preflightCommitment` parameter. If the block chosen for -simulation is older than the block used for your transaction’s blockhash, the +simulation is older than the block used for your transaction's blockhash, the simulation will fail with the dreaded “blockhash not found” error. **Recommendation** Even if you use `skipPreflight`, **ALWAYS** set the `preflightCommitment` -parameter to the same commitment level used to fetch your transaction’s +parameter to the same commitment level used to fetch your transaction's blockhash for both `sendTransaction` and `simulateTransaction` requests. ### Be wary of lagging RPC nodes when sending transactions @@ -290,18 +290,18 @@ lagging behind the first. For `sendTransaction` requests, clients should keep resending a transaction to a RPC node on a frequent interval so that if an RPC node is slightly lagging -behind the cluster, it will eventually catch up and detect your transaction’s +behind the cluster, it will eventually catch up and detect your transaction's expiration properly. For `simulateTransaction` requests, clients should use the [`replaceRecentBlockhash`](/docs/rpc/http/simulateTransaction.mdx) parameter to -tell the RPC node to replace the simulated transaction’s blockhash with a +tell the RPC node to replace the simulated transaction's blockhash with a blockhash that will always be valid for simulation. ### Avoid reusing stale blockhashes Even if your application has fetched a very recent blockhash, be sure that -you’re not reusing that blockhash in transactions for too long. The ideal +you're not reusing that blockhash in transactions for too long. The ideal scenario is that a recent blockhash is fetched right before a user signs their transaction. @@ -309,19 +309,19 @@ transaction. Poll for new recent blockhashes on a frequent basis to ensure that whenever a user triggers an action that creates a transaction, your application already has -a fresh blockhash that’s ready to go. +a fresh blockhash that's ready to go. **Recommendation for wallets** -Poll for new recent blockhashes on a frequent basis and replace a transaction’s +Poll for new recent blockhashes on a frequent basis and replace a transaction's recent blockhash right before they sign the transaction to ensure the blockhash is as fresh as possible. ### Use healthy RPC nodes when fetching blockhashes By fetching the latest blockhash with the `confirmed` commitment level from an -RPC node, it’s going to respond with the blockhash for the latest confirmed -block that it’s aware of. Solana’s block propagation protocol prioritizes +RPC node, it's going to respond with the blockhash for the latest confirmed +block that it's aware of. Solana's block propagation protocol prioritizes sending blocks to staked nodes so RPC nodes naturally lag about a block behind the rest of the cluster. They also have to do more work to handle application requests and can lag a lot more under heavy user traffic. @@ -338,7 +338,7 @@ still return a blockhash that is just about to expire. Monitor the health of your RPC nodes to ensure that they have an up-to-date view of the cluster state with one of the following methods: -1. Fetch your RPC node’s highest processed slot by using the +1. Fetch your RPC node's highest processed slot by using the [`getSlot`](/docs/rpc/http/getSlot.mdx) RPC API with the `processed` commitment level and then call the [`getMaxShredInsertSlot](/docs/rpc/http/getMaxShredInsertSlot.mdx) RPC API to @@ -373,25 +373,25 @@ To start using durable transactions, a user first needs to submit a transaction that [invokes instructions that create a special on-chain “nonce” account](https://docs.rs/solana-program/latest/solana_program/system_instruction/fn.create_nonce_account.html) and stores a “durable blockhash” inside of it. At any point in the future (as -long as the nonce account hasn’t been used yet), the user can create a durable +long as the nonce account hasn't been used yet), the user can create a durable transaction by following these 2 rules: 1. The instruction list must start with an [“advance nonce” system instruction](https://docs.rs/solana-program/latest/solana_program/system_instruction/fn.advance_nonce_account.html) which loads their on-chain nonce account -2. The transaction’s blockhash must be equal to the durable blockhash stored by +2. The transaction's blockhash must be equal to the durable blockhash stored by the on-chain nonce account -Here’s how these durable transactions are processed by the Solana runtime: +Here's how these durable transactions are processed by the Solana runtime: -1. If the transaction’s blockhash is no longer “recent”, the runtime checks if - the transaction’s instruction list begins with an “advance nonce” system +1. If the transaction's blockhash is no longer “recent”, the runtime checks if + the transaction's instruction list begins with an “advance nonce” system instruction 2. If so, it then loads the nonce account specified by the “advance nonce” instruction -3. Then it checks that the stored durable blockhash matches the transaction’s +3. Then it checks that the stored durable blockhash matches the transaction's blockhash -4. Lastly it makes sure to advance the nonce account’s stored blockhash to the +4. Lastly it makes sure to advance the nonce account's stored blockhash to the latest recent blockhash to ensure that the same transaction can never be processed again diff --git a/docs/advanced/retry.md b/docs/advanced/retry.md index f69bddb8e..51e38f467 100644 --- a/docs/advanced/retry.md +++ b/docs/advanced/retry.md @@ -24,7 +24,7 @@ their own custom rebroadcasting logic. - Developers should enable preflight checks to raise errors before transactions are submitted - Before re-signing any transaction, it is **very important** to ensure that the - initial transaction’s blockhash has expired + initial transaction's blockhash has expired ## The Journey of a Transaction @@ -59,13 +59,13 @@ forwarding it to the relevant leaders. UDP allows validators to quickly communicate with one another, but does not provide any guarantees regarding transaction delivery. -Because Solana’s leader schedule is known in advance of every +Because Solana's leader schedule is known in advance of every [epoch](/docs/terminology.md#epoch) (~2 days), an RPC node will broadcast its transaction directly to the current and next leaders. This is in contrast to other gossip protocols such as Ethereum that propagate transactions randomly and broadly across the entire network. By default, RPC nodes will try to forward transactions to leaders every two seconds until either the transaction is -finalized or the transaction’s blockhash expires (150 blocks or ~1 minute 19 +finalized or the transaction's blockhash expires (150 blocks or ~1 minute 19 seconds as of the time of this writing). If the outstanding rebroadcast queue size is greater than [10,000 transactions](https://github.com/solana-labs/solana/blob/bfbbc53dac93b3a5c6be9b4b65f679fdb13e41d9/send-transaction-service/src/send_transaction_service.rs#L20), @@ -75,7 +75,7 @@ that RPC operators can adjust to change the default behavior of this retry logic. When an RPC node broadcasts a transaction, it will attempt to forward the -transaction to a leader’s +transaction to a leader's [Transaction Processing Unit (TPU)](https://github.com/solana-labs/solana/blob/cd6f931223181d5a1d47cba64e857785a175a760/core/src/validator.rs#L867). The TPU processes transactions in five distinct phases: @@ -105,7 +105,7 @@ For more information on the TPU, please refer to ## How Transactions Get Dropped -Throughout a transaction’s journey, there are a few scenarios in which the +Throughout a transaction's journey, there are a few scenarios in which the transaction can be unintentionally dropped from the network. ### Before a transaction is processed @@ -113,7 +113,7 @@ transaction can be unintentionally dropped from the network. If the network drops a transaction, it will most likely do so before the transaction is processed by a leader. UDP [packet loss](https://en.wikipedia.org/wiki/Packet_loss) is the simplest reason -why this might occur. During times of intense network load, it’s also possible +why this might occur. During times of intense network load, it's also possible for validators to become overwhelmed by the sheer number of transactions required for processing. While validators are equipped to forward surplus transactions via `tpu_forwards`, there is a limit to the amount of data that can @@ -127,7 +127,7 @@ There are also two lesser known reasons why a transaction may be dropped before it is processed. The first scenario involves transactions that are submitted via an RPC pool. Occasionally, part of the RPC pool can be sufficiently ahead of the rest of the pool. This can cause issues when nodes within the pool are required -to work together. In this example, the transaction’s +to work together. In this example, the transaction's [recentBlockhash](/docs/core/transactions.md#recent-blockhash) is queried from the advanced part of the pool (Backend A). When the transaction is submitted to the lagging part of the pool (Backend B), the nodes will not recognize the @@ -139,7 +139,7 @@ transaction submission if developers enable Temporarily network forks can also result in dropped transactions. If a validator is slow to replay its blocks within the Banking Stage, it may end up -creating a minority fork. When a client builds a transaction, it’s possible for +creating a minority fork. When a client builds a transaction, it's possible for the transaction to reference a `recentBlockhash` that only exists on the minority fork. After the transaction is submitted, the cluster can then switch away from its minority fork before the transaction is processed. In this @@ -150,7 +150,7 @@ scenario, the transaction is dropped due to the blockhash not being found. ### After a transaction is processed and before it is finalized In the event a transaction references a `recentBlockhash` from a minority fork, -it’s still possible for the transaction to be processed. In this case, however, +it's still possible for the transaction to be processed. In this case, however, it would be processed by the leader on the minority fork. When this leader attempts to share its processed transactions with the rest of the network, it would fail to reach consensus with the majority of validators that do not @@ -201,8 +201,8 @@ the transaction will be processed or finalized by the cluster. ## Customizing Rebroadcast Logic In order to develop their own rebroadcasting logic, developers should take -advantage of `sendTransaction`’s `maxRetries` parameter. If provided, -`maxRetries` will override an RPC node’s default retry logic, allowing +advantage of `sendTransaction`'s `maxRetries` parameter. If provided, +`maxRetries` will override an RPC node's default retry logic, allowing developers to manually control the retry process [within reasonable bounds](https://github.com/solana-labs/solana/blob/98707baec2385a4f7114d2167ef6dfb1406f954f/validator/src/main.rs#L1258-L1274). @@ -210,9 +210,9 @@ A common pattern for manually retrying transactions involves temporarily storing the `lastValidBlockHeight` that comes from [getLatestBlockhash](/docs/rpc/http/getLatestBlockhash.mdx). Once stashed, an application can then -[poll the cluster’s blockheight](/docs/rpc/http/getBlockHeight.mdx) and manually +[poll the cluster's blockheight](/docs/rpc/http/getBlockHeight.mdx) and manually retry the transaction at an appropriate interval. In times of network -congestion, it’s advantageous to set `maxRetries` to 0 and manually rebroadcast +congestion, it's advantageous to set `maxRetries` to 0 and manually rebroadcast via a custom algorithm. While some applications may employ an [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) algorithm, others such as [Mango](https://www.mango.markets/) opt to @@ -310,7 +310,7 @@ for, it is recommended that developers keep `skipPreflight` set to `false`. Despite all attempts to rebroadcast, there may be times in which a client is required to re-sign a transaction. Before re-signing any transaction, it is -**very important** to ensure that the initial transaction’s blockhash has +**very important** to ensure that the initial transaction's blockhash has expired. If the initial blockhash is still valid, it is possible for both transactions to be accepted by the network. To an end-user, this would appear as if they unintentionally sent the same transaction twice. diff --git a/docs/economics/index.md b/docs/economics/index.md index baf26baea..55444a801 100644 --- a/docs/economics/index.md +++ b/docs/economics/index.md @@ -8,7 +8,7 @@ sidebarSortOrder: 5 **Subject to change.** -Solana’s crypto-economic system is designed to promote a healthy, long term +Solana's crypto-economic system is designed to promote a healthy, long term self-sustaining economy with participant incentives aligned to the security and decentralization of the network. The main participants in this economy are validation-clients. Their contributions to the network, state validation, and diff --git a/docs/economics/inflation/terminology.md b/docs/economics/inflation/terminology.md index 56b1c8154..e8526d500 100644 --- a/docs/economics/inflation/terminology.md +++ b/docs/economics/inflation/terminology.md @@ -14,7 +14,7 @@ genesis block or protocol inflation) minus any tokens that have been burnt (via transaction fees or other mechanism) or slashed. At network launch, 500,000,000 SOL were instantiated in the genesis block. Since then the Total Current Supply has been reduced by the burning of transaction fees and a planned token -reduction event. Solana’s _Total Current Supply_ can be found at +reduction event. Solana's _Total Current Supply_ can be found at https://explorer.solana.com/supply ### Inflation Rate [%] @@ -52,7 +52,7 @@ _Inflation Schedule_. remaining fee retained by the validator that processes the transaction. - Additional factors such as loss of private keys and slashing events should also be considered in a holistic analysis of the _Effective Inflation Rate_. - For example, it’s estimated that $10-20\%$ of all BTC have been lost and are + For example, it's estimated that $10-20\%$ of all BTC have been lost and are unrecoverable and that networks may experience similar yearly losses at the rate of $1-2\%$. diff --git a/docs/intro/quick-start/cross-program-invocation.md b/docs/intro/quick-start/cross-program-invocation.md index 94eb78b60..5bac88834 100644 --- a/docs/intro/quick-start/cross-program-invocation.md +++ b/docs/intro/quick-start/cross-program-invocation.md @@ -562,7 +562,7 @@ Running tests... You can then inspect the SolanFM links to view the transaction details, where -you’ll find the CPIs for the transfer instructions within the update and delete +you'll find the CPIs for the transfer instructions within the update and delete instructions. ![Update CPI](/assets/docs/intro/quickstart/cpi-update.png) diff --git a/docs/more/exchange.md b/docs/more/exchange.md index a85268db4..2aae6ecff 100644 --- a/docs/more/exchange.md +++ b/docs/more/exchange.md @@ -767,7 +767,7 @@ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" - ## Prioritization Fees and Compute Units -In periods of high demand, it’s possible for a transaction to expire before a +In periods of high demand, it's possible for a transaction to expire before a validator has included such transactions in their block because they chose other transactions with higher economic value. Valid Transactions on Solana may be delayed or dropped if Prioritization Fees are not implemented properly. @@ -817,7 +817,7 @@ may only return the lowest fee for each block. This will often be zero, which is not a fully useful approximation of what Prioritization Fee to use in order to avoid being rejected by validator nodes. -The `getRecentPrioritizationFees` API takes accounts’ pubkeys as parameters, and +The `getRecentPrioritizationFees` API takes accounts' pubkeys as parameters, and then returns the highest of the minimum prioritization fees for these accounts. When no account is specified, the API will return the lowest fee to land to block, which is usually zero (unless the block is full). diff --git a/docs/programs/testing.md b/docs/programs/testing.md index 029c24cfa..32d3bcdc3 100644 --- a/docs/programs/testing.md +++ b/docs/programs/testing.md @@ -18,7 +18,7 @@ There are two ways to test programs on Solana: 2. The various [BanksClient-based](https://docs.rs/solana-banks-client/latest/solana_banks_client/) test frameworks for SBF (Solana Bytecode Format) programs: Bankrun is a - framework that simulates a Solana bank’s operations, enabling developers to + framework that simulates a Solana bank's operations, enabling developers to deploy, interact with, and assess the behavior of programs under test conditions that mimic the mainnet. It helps set up the test environment and offers tools for detailed transaction insights, enhancing debugging and @@ -38,13 +38,13 @@ There are two ways to test programs on Solana: In this guide, we are using Solana Bankrun. `Bankrun` is a superfast, powerful, and lightweight framework for testing Solana programs in Node.js. -- The biggest advantage of using Solana Bankrun is that you don’t have to set +- The biggest advantage of using Solana Bankrun is that you don't have to set up - an environment to test programs like you’d have to do while using the + an environment to test programs like you'd have to do while using the `solana-test-validator`. Instead, you can do that with a piece of code, inside the tests. -- It also dynamically sets time and account data, which isn’t possible with +- It also dynamically sets time and account data, which isn't possible with `solana-test-validator` ## Installation @@ -66,7 +66,7 @@ directories: - `./tests/fixtures` (just create this directory if it doesn't exist already). - Your current working directory. - A directory you define in the `BPF_OUT_DIR` or `SBF_OUT_DIR` environment - variables. `export BPF_OUT_DIR=’/path/to/binary’` + variables. `export BPF_OUT_DIR='/path/to/binary'` - Build your program specifying the correct directory so that library can pick the file up from directory just from the name. `cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures` @@ -151,7 +151,7 @@ let transaction = await client.processTransaction(tx); ## Example -Here’s an example to write test for +Here's an example to write test for a [hello world program](https://github.com/solana-developers/program-examples/tree/main/basics/hello-solana/native) : ```typescript From eb8c710093b7ce2000ebac88936ecc56897f3242 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Mon, 30 Sep 2024 14:45:00 +1000 Subject: [PATCH 03/54] Fix ellipsis characters that should be three dots --- content/courses/onchain-development/anchor-pdas.md | 2 +- content/courses/program-optimization/program-architecture.md | 2 +- content/guides/games/nfts-in-games.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/courses/onchain-development/anchor-pdas.md b/content/courses/onchain-development/anchor-pdas.md index b2a55c9e3..c728fe7e9 100644 --- a/content/courses/onchain-development/anchor-pdas.md +++ b/content/courses/onchain-development/anchor-pdas.md @@ -236,7 +236,7 @@ anchor-lang = { version = "0.30.1", features = ["init-if-needed"] } ``` Once you've enabled the feature, you can include the constraint in the -`#[account(…)]` attribute macro. The example below demonstrates using the +`#[account(...)]` attribute macro. The example below demonstrates using the `init_if_needed` constraint to initialize a new associated token account if one does not already exist. diff --git a/content/courses/program-optimization/program-architecture.md b/content/courses/program-optimization/program-architecture.md index 52056d9a7..d1d05e1f6 100644 --- a/content/courses/program-optimization/program-architecture.md +++ b/content/courses/program-optimization/program-architecture.md @@ -175,7 +175,7 @@ pub struct SomeFunctionContext<'info> { In Anchor, **`Box`** is used to allocate the account to the Heap, not the Stack. Which is great since the Heap gives us 32KB to work with. The best part is you don't have to do anything different within the function. All you need to -do is add `Box<…>` around all of your big data accounts. +do is add `Box<...>` around all of your big data accounts. But Box is not perfect. You can still overflow the stack with sufficiently large accounts. We'll learn how to fix this in the next section. diff --git a/content/guides/games/nfts-in-games.md b/content/guides/games/nfts-in-games.md index db657913e..4e7e2924c 100644 --- a/content/guides/games/nfts-in-games.md +++ b/content/guides/games/nfts-in-games.md @@ -233,7 +233,7 @@ let uri = 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/mas let asset: Asset = await loader.load(uri); let gltf: GlTf = asset.gltf; console.log(gltf); -// -> {asset: {…}, scene: 0, scenes: Array(1), nodes: Array(2), meshes: Array(1), …} +// -> {asset: {...}, scene: 0, scenes: Array(1), nodes: Array(2), meshes: Array(1), ...} let data = await asset.accessorData(0); // fetches BoxTextured0.bin let image: Image = await asset.imageData.get(0) // fetches CesiumLogoFlat.png From 3c33a0eb9d96721be542dd417e6217f6c8fb6fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <47941531+Andross96@users.noreply.github.com> Date: Mon, 30 Sep 2024 06:53:42 +0200 Subject: [PATCH 04/54] fix: typo with getMaxShredInsertSlot link (#540) --- docs/advanced/confirmation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/confirmation.md b/docs/advanced/confirmation.md index 02cc91ad5..eb698b009 100644 --- a/docs/advanced/confirmation.md +++ b/docs/advanced/confirmation.md @@ -341,7 +341,7 @@ of the cluster state with one of the following methods: 1. Fetch your RPC node's highest processed slot by using the [`getSlot`](/docs/rpc/http/getSlot.mdx) RPC API with the `processed` commitment level and then call the - [`getMaxShredInsertSlot](/docs/rpc/http/getMaxShredInsertSlot.mdx) RPC API to + [`getMaxShredInsertSlot`](/docs/rpc/http/getMaxShredInsertSlot.mdx) RPC API to get the highest slot that your RPC node has received a “shred” of a block for. If the difference between these responses is very large, the cluster is producing blocks far ahead of what the RPC node has processed. From d2dfdeb6128f897c8ca635070fac3907d742ec4a Mon Sep 17 00:00:00 2001 From: Shawaz <65177277+shawazi@users.noreply.github.com> Date: Mon, 30 Sep 2024 00:54:22 -0400 Subject: [PATCH 05/54] Fix a single typo (#516) "build" is the command to build a project, but the correct tense in the context of that sentence would be "built" --- content/courses/onchain-development/intro-to-anchor-frontend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/courses/onchain-development/intro-to-anchor-frontend.md b/content/courses/onchain-development/intro-to-anchor-frontend.md index 7d2a419bb..ecbe269e3 100644 --- a/content/courses/onchain-development/intro-to-anchor-frontend.md +++ b/content/courses/onchain-development/intro-to-anchor-frontend.md @@ -196,7 +196,7 @@ import idl from "./idl.json"; You would _ideally_ also require types for the IDL which would make it easier to interact with the program. The types can be found at `/target/types` folder -after you have build your program. Here are the types for the above IDL which +after you have built your program. Here are the types for the above IDL which when you notice has the exact same structure as the IDL but are just as type helper. From 63db12baf58a7ba342d1d1f8604bb0e600db9645 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Mon, 30 Sep 2024 14:58:55 +1000 Subject: [PATCH 06/54] Clarify multiple files template confusion after #506 --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4aed87c2..edcee7aac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,8 +84,8 @@ In particular: have `instruction handlers` that process `instructions`. Do not refer to [instruction handlers](https://solana.com/docs/terminology#instruction-handler) as instructions! The reason is simple: an instruction cannot process an - instruction. The `multiple` template in Anchor also calls the function's - `handler`. + instruction. The `multiple` template in Anchor also calls these functions + `handler`s. ### Code From b464a8b8f9d486205b1aa5582d58120efa3be533 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Mon, 30 Sep 2024 16:20:55 +1000 Subject: [PATCH 07/54] Prettier --- docs/advanced/confirmation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced/confirmation.md b/docs/advanced/confirmation.md index eb698b009..b26144204 100644 --- a/docs/advanced/confirmation.md +++ b/docs/advanced/confirmation.md @@ -341,8 +341,8 @@ of the cluster state with one of the following methods: 1. Fetch your RPC node's highest processed slot by using the [`getSlot`](/docs/rpc/http/getSlot.mdx) RPC API with the `processed` commitment level and then call the - [`getMaxShredInsertSlot`](/docs/rpc/http/getMaxShredInsertSlot.mdx) RPC API to - get the highest slot that your RPC node has received a “shred” of a block + [`getMaxShredInsertSlot`](/docs/rpc/http/getMaxShredInsertSlot.mdx) RPC API + to get the highest slot that your RPC node has received a “shred” of a block for. If the difference between these responses is very large, the cluster is producing blocks far ahead of what the RPC node has processed. 2. Call the `getLatestBlockhash` RPC API with the `confirmed` commitment level From 73cb9e4cae1d6a69695b4f6caaf067c4cf0deddf Mon Sep 17 00:00:00 2001 From: Krishnendu Samanta <76649777+krishnendu-2003@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:06:24 +0530 Subject: [PATCH 08/54] Intro to solana content (#472) * Updated * Updated * Updated * Updated --- content/courses/intro-to-solana/getting-started.md | 2 +- content/courses/intro-to-solana/intro-to-cryptography.md | 6 ++++++ content/courses/intro-to-solana/intro-to-writing-data.md | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/content/courses/intro-to-solana/getting-started.md b/content/courses/intro-to-solana/getting-started.md index d2e5e3a73..15565d26c 100644 --- a/content/courses/intro-to-solana/getting-started.md +++ b/content/courses/intro-to-solana/getting-started.md @@ -8,7 +8,7 @@ objectives: description: "Understand what web3, blockchains, and Solana are." --- -## Welcome! +## Welcome Welcome to the best starting point for developers looking to learn web3 and blockchain! diff --git a/content/courses/intro-to-solana/intro-to-cryptography.md b/content/courses/intro-to-solana/intro-to-cryptography.md index 6f1c3d83b..396e3032a 100644 --- a/content/courses/intro-to-solana/intro-to-cryptography.md +++ b/content/courses/intro-to-solana/intro-to-cryptography.md @@ -20,6 +20,9 @@ description: "Understand asymmetric cryptography and how Solana uses it." ## Lesson +In this lesson, we will explore the basics of cryptography and how it's applied +within the Solana ecosystem. + ### Symmetric and Asymmetric Cryptography 'Cryptography' the study of hiding information. There are two main types of @@ -157,6 +160,9 @@ You know how to make and load keypairs! Let's practice what we've learned. ## Lab +In this lab we will learn about keypairs, and how to store secret keys securely +on solana + ### Installation Make a new directory, install TypeScript, Solana web3.js and esrun: diff --git a/content/courses/intro-to-solana/intro-to-writing-data.md b/content/courses/intro-to-solana/intro-to-writing-data.md index deede360e..3bff43d78 100644 --- a/content/courses/intro-to-solana/intro-to-writing-data.md +++ b/content/courses/intro-to-solana/intro-to-writing-data.md @@ -235,7 +235,7 @@ console.log( console.log(`Transaction signature is ${signature}!`); ``` -### Experiment! +### Experiment Send SOL to other students in the class. From 8325fb5b3beac71ce31dcb501c327ac22246a8c3 Mon Sep 17 00:00:00 2001 From: Nick Frostbutter <75431177+nickfrosty@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:36:29 -0400 Subject: [PATCH 09/54] [docs/rpc] fix: required field on `getTokenAccountsByOwner` (#543) * fix: required field * refactor: use a live account * fix: real data --- docs/rpc/http/getTokenAccountsByOwner.mdx | 76 +++++++++++++++-------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/docs/rpc/http/getTokenAccountsByOwner.mdx b/docs/rpc/http/getTokenAccountsByOwner.mdx index 84d5d1fd0..d60af8eb0 100644 --- a/docs/rpc/http/getTokenAccountsByOwner.mdx +++ b/docs/rpc/http/getTokenAccountsByOwner.mdx @@ -18,9 +18,9 @@ Returns all SPL Token accounts by token owner. Pubkey of account delegate to query, as base-58 encoded string - + -A JSON object with one of the following fields: +A JSON object with either one of the following fields: - `mint: ` - Pubkey of the specific token Mint to limit accounts to, as base-58 encoded string; or @@ -121,9 +121,9 @@ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" - "id": 1, "method": "getTokenAccountsByOwner", "params": [ - "4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F", + "A1TMhSGzQxMr1TboBKtgixKz1sS6REASMxPo1qsyTSJd", { - "mint": "3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E" + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { "encoding": "jsonParsed" @@ -137,52 +137,74 @@ curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" - ```json { + "id": 1, "jsonrpc": "2.0", "result": { "context": { - "slot": 1114 + "apiVersion": "2.0.8", + "slot": 329669901 }, "value": [ { "account": { "data": { - "program": "spl-token", "parsed": { - "accountType": "account", "info": { - "tokenAmount": { - "amount": "1", - "decimals": 1, - "uiAmount": 0.1, - "uiAmountString": "0.1" - }, - "delegate": "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", - "delegatedAmount": { - "amount": "1", - "decimals": 1, - "uiAmount": 0.1, - "uiAmountString": "0.1" - }, + "isNative": false, + "mint": "BejB75Gmq8btLboHx7yffWcurHVBv5xvKcnY1fBYxnvf", + "owner": "A1TMhSGzQxMr1TboBKtgixKz1sS6REASMxPo1qsyTSJd", "state": "initialized", + "tokenAmount": { + "amount": "10000000000000", + "decimals": 9, + "uiAmount": 10000, + "uiAmountString": "10000" + } + }, + "type": "account" + }, + "program": "spl-token", + "space": 165 + }, + "executable": false, + "lamports": 2039280, + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "rentEpoch": 18446744073709551615, + "space": 165 + }, + "pubkey": "5HvuXcy57o41qtGBBJM7dRN9DS6G3jd9KEhHt4eYqJmB" + }, + { + "account": { + "data": { + "parsed": { + "info": { "isNative": false, - "mint": "3wyAj7Rt1TWVPZVteFJPLa26JmLvdb1CAKEFZm3NY75E", - "owner": "4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F" + "mint": "FSX34rYUJ4zfdD7z4p3L1Fd1pGiiErusaSNTfgKqhep6", + "owner": "A1TMhSGzQxMr1TboBKtgixKz1sS6REASMxPo1qsyTSJd", + "state": "initialized", + "tokenAmount": { + "amount": "10000000000000", + "decimals": 9, + "uiAmount": 10000, + "uiAmountString": "10000" + } }, "type": "account" }, + "program": "spl-token", "space": 165 }, "executable": false, - "lamports": 1726080, + "lamports": 2039280, "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - "rentEpoch": 4, + "rentEpoch": 18446744073709551615, "space": 165 }, - "pubkey": "C2gJg6tKpQs41PRS1nC8aw3ZKNZK3HQQZGVrDFDup5nx" + "pubkey": "HvTGvCP4tg2wVdFtqZCTdMPHDXmkYwNAxaTBCHabqh2X" } ] - }, - "id": 1 + } } ``` From 6aa178dac2296b06bc53bd9cc5f2be0db54fe8d4 Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Tue, 1 Oct 2024 06:58:24 +0530 Subject: [PATCH 10/54] Program security - Bump seed canonicalization lesson updated (#446) * Fixed grammar * Updated content as per comments * Formatted code --- .../bump-seed-canonicalization.md | 618 ++++++++++-------- 1 file changed, 358 insertions(+), 260 deletions(-) diff --git a/content/courses/program-security/bump-seed-canonicalization.md b/content/courses/program-security/bump-seed-canonicalization.md index e2ea4d93f..1ee208d63 100644 --- a/content/courses/program-security/bump-seed-canonicalization.md +++ b/content/courses/program-security/bump-seed-canonicalization.md @@ -8,41 +8,53 @@ objectives: - Use Anchor's `seeds` and `bump` constraints to ensure the canonical bump is always used in future instructions when deriving a PDA description: - "Understand the need for consistent PDA calculation by storing and reusuing - the canonical bump." + "Understand the need for consistent PDA calculation by storing and reusing the + canonical bump." --- ## Summary - The [**`create_program_address`**](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.create_program_address) - function derives a PDA without searching for the **canonical bump**. This - means there are multiple valid bumps, all of which will produce different - addresses. + function derives a PDA but does so without searching for the canonical bump. + It allows multiple valid bumps to produce different addresses. While this can + still generate a valid PDA, it lacks determinism, as multiple bumps may yield + different addresses for the same set of seeds. - Using [**`find_program_address`**](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.find_program_address) - ensures that the highest valid bump, or canonical bump, is used for the - derivation, thus creating a deterministic way to find an address given - specific seeds. -- Upon initialization, you can use Anchor's `seeds` and `bump` constraint to - ensure that PDA derivations in the account validation struct always use the - canonical bump -- Anchor allows you to **specify a bump** with the `bump = ` - constraint when verifying the address of a PDA -- Because `find_program_address` can be expensive, best practice is to store the - derived bump in an account's data field to be referenced later on when - re-deriving the address for verification + ensures that the **highest valid bump**, often referred to as the **canonical + bump**, is used in the PDA derivation. This provides a deterministic way to + compute an address for a given set of seeds, ensuring consistency across the + program. +- In Anchor, you can specify the `seeds` and the `bump` to ensure that PDA + derivations in your account validation struct always align with the correct + canonical bump. +- Anchor also allows you to specify a bump directly in the validation struct + using the `bump = ` constraint. This ensures that the correct bump + is used when verifying the PDA. +- Using `find_program_address` can be computationally expensive due to the + process of searching for the highest valid bump. It's considered best practice + to store the derived bump in an account's data field upon initialization. This + allows the bump to be referenced in subsequent instruction handlers, avoiding + the need to repeatedly call `find_program_address` to re-derive the PDA. + ```rust #[derive(Accounts)] pub struct VerifyAddress<'info> { - #[account( - seeds = [DATA_PDA_SEED.as_bytes()], - bump = data.bump - )] - data: Account<'info, Data>, + #[account( + seeds = [DATA_PDA_SEED.as_bytes()], + bump = data.bump + )] + data: Account<'info, Data>, } ``` +- In summary, while `create_program_address` can generate a PDA, + `find_program_address` ensures consistency and reliability by always producing + the canonical bump, which is critical for deterministic program execution. + This helps maintain integrity in onchain apps, especially when validating PDAs + across multiple instruction handlers. + ## Lesson Bump seeds are a number between 0 and 255, inclusive, used to ensure that an @@ -52,37 +64,39 @@ is a valid PDA. The **canonical bump** is the highest bump value that produces a valid PDA. The standard in Solana is to _always use the canonical bump_ when deriving PDAs, both for security and convenience. -### Insecure PDA derivation using `create_program_address` +### Insecure PDA Derivation using create_program_address Given a set of seeds, the `create_program_address` function will produce a valid PDA about 50% of the time. The bump seed is an additional byte added as a seed -to "bump" the derived address into valid territory. Since there are 256 possible -bump seeds and the function produces valid PDAs approximately 50% of the time, -there are many valid bumps for a given set of input seeds. +to "bump" the derived address into a valid territory. Since there are 256 +possible bump seeds and the function produces valid PDAs approximately 50% of +the time, there are many valid bumps for a given set of input seeds. -You can imagine that this could cause confusion for locating accounts when using +You can imagine that this could cause confusion in locating accounts when using seeds as a way of mapping between known pieces of information to accounts. Using the canonical bump as the standard ensures that you can always find the right account. More importantly, it avoids security exploits caused by the open-ended nature of allowing multiple bumps. -In the example below, the `set_value` instruction uses a `bump` that was passed -in as instruction data to derive a PDA. The instruction then derives the PDA -using `create_program_address` function and checks that the `address` matches -the public key of the `data` account. +In the example below, the `set_value` instruction handler uses a `bump` that was +passed in as instruction data to derive a PDA. The instruction handler then +derives the PDA using `create_program_address` function and checks that the +`address` matches the public key of the `data` account. ```rust use anchor_lang::prelude::*; -declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); +declare_id!("ABQaKhtpYQUUgZ9m2sAY7ZHxWv6KyNdhUJW8Dh8NQbkf"); #[program] pub mod bump_seed_canonicalization_insecure { use super::*; + // Insecure PDA Derivation using create_program_address pub fn set_value(ctx: Context, key: u64, new_value: u64, bump: u8) -> Result<()> { let address = - Pubkey::create_program_address(&[key.to_le_bytes().as_ref(), &[bump]], ctx.program_id).unwrap(); + Pubkey::create_program_address(&[key.to_le_bytes().as_ref(), &[bump]], ctx.program_id) + .unwrap(); if address != ctx.accounts.data.key() { return Err(ProgramError::InvalidArgument.into()); } @@ -95,33 +109,34 @@ pub mod bump_seed_canonicalization_insecure { #[derive(Accounts)] pub struct BumpSeed<'info> { - data: Account<'info, Data>, + #[account(mut)] + pub data: Account<'info, Data>, } #[account] pub struct Data { - value: u64, + pub value: u64, } ``` -While the instruction derives the PDA and checks the passed-in account, which is -good, it allows the caller to pass in an arbitrary bump. Depending on the -context of your program, this could result in undesired behavior or potential -exploit. +While the instruction handler derives the PDA and checks the passed-in account, +which is good, it allows the caller to pass in an arbitrary bump. Depending on +the context of your program, this could result in undesired behavior or +potential exploit. If the seed mapping was meant to enforce a one-to-one relationship between PDA and user, for example, this program would not properly enforce that. A user could call the program multiple times with many valid bumps, each producing a different PDA. -### Recommended derivation using `find_program_address` +### Recommended Derivation using find_program_address A simple way around this problem is to have the program expect only the canonical bump and use `find_program_address` to derive the PDA. The [`find_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.find_program_address) -_always uses the canonical bump_. This function iterates through calling +_always uses the canonical bump_. This function iterates by calling `create_program_address`, starting with a bump of 255 and decrementing the bump by one with each iteration. As soon as a valid address is found, the function returns both the derived PDA and the canonical bump used to derive it. @@ -151,7 +166,7 @@ pub fn set_value_secure( } ``` -### Use Anchor's `seeds` and `bump` constraints +### Use Anchor's seeds and bump Constraints Anchor provides a convenient way to derive PDAs in the account validation struct using the `seeds` and `bump` constraints. These can even be combined with the @@ -166,6 +181,8 @@ use anchor_lang::prelude::*; declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); +pub const DISCRIMINATOR_SIZE: usize = 8; + #[program] pub mod bump_seed_canonicalization_recommended { use super::*; @@ -175,28 +192,29 @@ pub mod bump_seed_canonicalization_recommended { Ok(()) } } - -// initialize account at PDA +// Initialize account at PDA #[derive(Accounts)] #[instruction(key: u64)] pub struct BumpSeed<'info> { - #[account(mut)] - payer: Signer<'info>, - #[account( - init, - seeds = [key.to_le_bytes().as_ref()], - // derives the PDA using the canonical bump - bump, - payer = payer, - space = 8 + 8 - )] - data: Account<'info, Data>, - system_program: Program<'info, System> + #[account(mut)] + pub payer: Signer<'info>, + #[account( + init, + seeds = [key.to_le_bytes().as_ref()], + // Derives the PDA using the canonical bump + bump, + payer = payer, + space = DISCRIMINATOR_SIZE + Data::INIT_SPACE + )] + pub data: Account<'info, Data>, + + pub system_program: Program<'info, System>, } #[account] +#[derive(InitSpace)] pub struct Data { - value: u64, + pub value: u64, } ``` @@ -210,7 +228,7 @@ arbitrary bumps, but rather to let you optimize your program. The iterative nature of `find_program_address` makes it expensive, so best practice is to store the canonical bump in the PDA account's data upon initializing a PDA, allowing you to reference the bump stored when validating the PDA in subsequent -instructions. +instruction handlers. When you specify the bump to use, Anchor uses `create_program_address` with the provided bump instead of `find_program_address`. This pattern of storing the @@ -222,66 +240,76 @@ use anchor_lang::prelude::*; declare_id!("CVwV9RoebTbmzsGg1uqU1s4a3LvTKseewZKmaNLSxTqc"); +// Constant for account space calculation +pub const DISCRIMINATOR_SIZE: usize = 8; + #[program] pub mod bump_seed_canonicalization_recommended { use super::*; + // Instruction handler to set a value and store the bump pub fn set_value(ctx: Context, _key: u64, new_value: u64) -> Result<()> { ctx.accounts.data.value = new_value; - // store the bump on the account - ctx.accounts.data.bump = *ctx.bumps.get("data").unwrap(); + + // Store the canonical bump on the account + // This bump is automatically derived by Anchor + ctx.accounts.data.bump = ctx.bumps.data; + Ok(()) } + // Instruction handler to verify the PDA address pub fn verify_address(ctx: Context, _key: u64) -> Result<()> { msg!("PDA confirmed to be derived with canonical bump: {}", ctx.accounts.data.key()); Ok(()) } } -// initialize account at PDA +// Account validation struct for initializing the PDA account #[derive(Accounts)] #[instruction(key: u64)] pub struct BumpSeed<'info> { - #[account(mut)] - payer: Signer<'info>, - #[account( - init, - seeds = [key.to_le_bytes().as_ref()], - // derives the PDA using the canonical bump - bump, - payer = payer, - space = 8 + 8 + 1 - )] - data: Account<'info, Data>, - system_program: Program<'info, System> + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + init, + seeds = [key.to_le_bytes().as_ref()], + bump, // Anchor automatically uses the canonical bump + payer = payer, + space = DISCRIMINATOR_SIZE + Data::INIT_SPACE + )] + pub data: Account<'info, Data>, + + pub system_program: Program<'info, System> } +// Account validation struct for verifying the PDA address #[derive(Accounts)] #[instruction(key: u64)] pub struct VerifyAddress<'info> { - #[account( - seeds = [key.to_le_bytes().as_ref()], - // guranteed to be the canonical bump every time - bump = data.bump - )] - data: Account<'info, Data>, + #[account( + seeds = [key.to_le_bytes().as_ref()], + bump = data.bump // Use the stored bump, guaranteed to be canonical + )] + pub data: Account<'info, Data>, } +// Data structure for the PDA account #[account] +#[derive(InitSpace)] pub struct Data { - value: u64, - // bump field - bump: u8 + pub value: u64, + pub bump: u8 // Stores the canonical bump } ``` If you don't specify the bump on the `bump` constraint, Anchor will still use `find_program_address` to derive the PDA using the canonical bump. As a -consequence, your instruction will incur a variable amount of compute budget. -Programs that are already at risk of exceeding their compute budget should use -this with care since there is a chance that the program's budget may be -occasionally and unpredictably exceeded. +consequence, your instruction handler will incur a variable amount of compute +budget. Programs that are already at risk of exceeding their compute budget +should use this with care since there is a chance that the program's budget may +be occasionally and unpredictably exceeded. On the other hand, if you only need to verify the address of a PDA passed in without initializing an account, you'll be forced to either let Anchor derive @@ -294,35 +322,35 @@ To demonstrate the security exploits possible when you don't check for the canonical bump, let's work with a program that lets each program user "claim" rewards on time. -#### 1. Setup +### 1. Setup -Start by getting the code on the `starter` branch of -[this repository](https://github.com/Unboxed-Software/solana-bump-seed-canonicalization/tree/starter). +Start by getting the code on the +[`starter` branch of this repository](https://github.com/solana-developers/bump-seed-canonicalization/tree/starter). -Notice that there are two instructions on the program and a single test in the -`tests` directory. +Notice that there are two instruction handlers on the program and a single test +in the `tests` directory. -The instructions on the program are: +The instruction handlers on the program are: 1. `create_user_insecure` 2. `claim_insecure` -The `create_user_insecure` instruction simply creates a new account at a PDA -derived using the signer's public key and a passed-in bump. +The `create_user_insecure` instruction handler simply creates a new account at a +PDA derived using the signer's public key and a passed-in bump. -The `claim_insecure` instruction mints 10 tokens to the user and then marks the -account's rewards as claimed so that they can't claim again. +The `claim_insecure` instruction handler mints 10 tokens to the user and then +marks the account's rewards as claimed so that they can't claim again. However, the program doesn't explicitly check that the PDAs in question are using the canonical bump. Have a look at the program to understand what it does before proceeding. -#### 2. Test insecure instructions +### 2. Test Insecure Instruction Handlers -Since the instructions don't explicitly require the `user` PDA to use the -canonical bump, an attacker can create multiple accounts per wallet and claim -more rewards than should be allowed. +Since the instruction handlers don't explicitly require the `user` PDA to use +the canonical bump, an attacker can create multiple accounts per wallet and +claim more rewards than should be allowed. The test in the `tests` directory creates a new keypair called `attacker` to represent an attacker. It then loops through all possible bumps and calls @@ -331,156 +359,188 @@ the attacker has been able to claim rewards multiple times and has earned more than the 10 tokens allotted per user. ```typescript -it("Attacker can claim more than reward limit with insecure instructions", async () => { - const attacker = Keypair.generate(); - await safeAirdrop(attacker.publicKey, provider.connection); - const ataKey = await getAssociatedTokenAddress(mint, attacker.publicKey); - - let numClaims = 0; - - for (let i = 0; i < 256; i++) { - try { - const pda = createProgramAddressSync( - [attacker.publicKey.toBuffer(), Buffer.from([i])], - program.programId, - ); - await program.methods - .createUserInsecure(i) - .accounts({ - user: pda, - payer: attacker.publicKey, - }) - .signers([attacker]) - .rpc(); - await program.methods - .claimInsecure(i) - .accounts({ - user: pda, - mint, - payer: attacker.publicKey, - userAta: ataKey, - }) - .signers([attacker]) - .rpc(); - - numClaims += 1; - } catch (error) { - if (error.message !== "Invalid seeds, address must fall off the curve") { - console.log(error); +it("allows attacker to claim more than reward limit with insecure instruction handlers", async () => { + try { + const attacker = Keypair.generate(); + await airdropIfRequired( + connection, + attacker.publicKey, + 1 * LAMPORTS_PER_SOL, + 0.5 * LAMPORTS_PER_SOL, + ); + const ataKey = await getAssociatedTokenAddress(mint, attacker.publicKey); + + let successfulClaimCount = 0; + + for (let i = 0; i < 256; i++) { + try { + const pda = anchor.web3.PublicKey.createProgramAddressSync( + [attacker.publicKey.toBuffer(), Buffer.from([i])], + program.programId, + ); + await program.methods + .createUserInsecure(i) + .accounts({ + user: pda, + payer: attacker.publicKey, + }) + .signers([attacker]) + .rpc(); + await program.methods + .claimInsecure(i) + .accounts({ + user: pda, + mint, + payer: attacker.publicKey, + userAta: ataKey, + mintAuthority, + tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID, + associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID, + systemProgram: anchor.web3.SystemProgram.programId, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }) + .signers([attacker]) + .rpc(); + + successfulClaimCount += 1; + } catch (error) { + if ( + error instanceof Error && + !error.message.includes( + "Invalid seeds, address must fall off the curve", + ) + ) { + console.error(error); + } } } - } - const ata = await getAccount(provider.connection, ataKey); + const ata = await getAccount(connection, ataKey); - console.log( - `Attacker claimed ${numClaims} times and got ${Number(ata.amount)} tokens`, - ); + console.log( + `Attacker claimed ${successfulClaimCount} times and got ${Number( + ata.amount, + )} tokens`, + ); - expect(numClaims).to.be.greaterThan(1); - expect(Number(ata.amount)).to.be.greaterThan(10); + expect(successfulClaimCount).to.be.greaterThan(1); + expect(Number(ata.amount)).to.be.greaterThan(10); + } catch (error) { + throw new Error(`Test failed: ${error.message}`); + } }); ``` Run `anchor test` to see that this test passes, showing that the attacker is -successful. Since the test calles the instructions for every valid bump, it -takes a bit to run, so be patient. +successful. Since the test calls the instruction handlers for every valid bump, +it takes a bit to run, so be patient. ```bash - bump-seed-canonicalization -Attacker claimed 129 times and got 1290 tokens - ✔ Attacker can claim more than reward limit with insecure instructions (133840ms) + Bump seed canonicalization +Attacker claimed 121 times and got 1210 tokens + ✔ allows attacker to claim more than reward limit with insecure instructions (119994ms) ``` -#### 3. Create secure instructions +### 3. Create Secure Instruction Handler -Let's demonstrate patching the vulnerability by creating two new instructions: +Let's demonstrate patching the vulnerability by creating two new instruction +handlers: 1. `create_user_secure` 2. `claim_secure` -Before we write the account validation or instruction logic, let's create a new -user type, `UserSecure`. This new type will add the canonical bump as a field on -the struct. +Before we write the account validation or instruction handler logic, let's +create a new user type, `UserSecure`. This new type will add the canonical bump +as a field on the struct. ```rust +// Secure user account structure #[account] +#[derive(InitSpace)] pub struct UserSecure { - auth: Pubkey, - bump: u8, - rewards_claimed: bool, + pub auth: Pubkey, + pub bump: u8, + pub rewards_claimed: bool, } ``` -Next, let's create account validation structs for each of the new instructions. -They'll be very similar to the insecure versions but will let Anchor handle the -derivation and deserialization of the PDAs. +Next, let's create account validation structs for each of the new instruction +handlers. They'll be very similar to the insecure versions but will let Anchor +handle the derivation and deserialization of the PDAs. ```rust +// Account validation struct for securely creating a user account #[derive(Accounts)] pub struct CreateUserSecure<'info> { #[account(mut)] - payer: Signer<'info>, + pub payer: Signer<'info>, #[account( init, - seeds = [payer.key().as_ref()], - // derives the PDA using the canonical bump - bump, payer = payer, - space = 8 + 32 + 1 + 1 + space = DISCRIMINATOR_SIZE + UserSecure::INIT_SPACE, + seeds = [payer.key().as_ref()], + bump )] - user: Account<'info, UserSecure>, - system_program: Program<'info, System>, + pub user: Account<'info, UserSecure>, + pub system_program: Program<'info, System>, } +// Account validation struct for secure claiming of rewards #[derive(Accounts)] pub struct SecureClaim<'info> { #[account( + mut, seeds = [payer.key().as_ref()], bump = user.bump, constraint = !user.rewards_claimed @ ClaimError::AlreadyClaimed, constraint = user.auth == payer.key() )] - user: Account<'info, UserSecure>, + pub user: Account<'info, UserSecure>, #[account(mut)] - payer: Signer<'info>, + pub payer: Signer<'info>, #[account( init_if_needed, payer = payer, associated_token::mint = mint, associated_token::authority = payer )] - user_ata: Account<'info, TokenAccount>, + pub user_ata: Account<'info, TokenAccount>, #[account(mut)] - mint: Account<'info, Mint>, - /// CHECK: mint auth PDA - #[account(seeds = ["mint".as_bytes().as_ref()], bump)] + pub mint: Account<'info, Mint>, + /// CHECK: This is the mint authority PDA, checked by seeds constraint + #[account(seeds = [b"mint"], bump)] pub mint_authority: UncheckedAccount<'info>, - token_program: Program<'info, Token>, - associated_token_program: Program<'info, AssociatedToken>, - system_program: Program<'info, System>, - rent: Sysvar<'info, Rent>, + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, + pub rent: Sysvar<'info, Rent>, } ``` -Finally, let's implement the instruction logic for the two new instructions. The -`create_user_secure` instruction simply needs to set the `auth`, `bump` and -`rewards_claimed` fields on the `user` account data. +Finally, let's implement the instruction handler logic for the two new +instruction handlers. The `create_user_secure` instruction handler simply needs +to set the `auth`, `bump` and `rewards_claimed` fields on the `user` account +data. ```rust +// Secure instruction to create a user account pub fn create_user_secure(ctx: Context) -> Result<()> { - ctx.accounts.user.auth = ctx.accounts.payer.key(); - ctx.accounts.user.bump = *ctx.bumps.get("user").unwrap(); - ctx.accounts.user.rewards_claimed = false; + ctx.accounts.user.set_inner(UserSecure { + auth: ctx.accounts.payer.key(), + bump: ctx.bumps.user, + rewards_claimed: false, + }); Ok(()) } ``` -The `claim_secure` instruction needs to mint 10 tokens to the user and set the -`user` account's `rewards_claimed` field to `true`. +The `claim_secure` instruction handler needs to mint 10 tokens to the user and +set the `user` account's `rewards_claimed` field to `true`. ```rust +// Secure instruction to claim rewards pub fn claim_secure(ctx: Context) -> Result<()> { + // Mint tokens to the user's associated token account token::mint_to( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), @@ -489,104 +549,142 @@ pub fn claim_secure(ctx: Context) -> Result<()> { to: ctx.accounts.user_ata.to_account_info(), authority: ctx.accounts.mint_authority.to_account_info(), }, - &[&[ - b"mint".as_ref(), - &[*ctx.bumps.get("mint_authority").unwrap()], - ]], + &[&[b"mint", &[ctx.bumps.mint_authority]]], ), 10, )?; + // Mark rewards as claimed ctx.accounts.user.rewards_claimed = true; Ok(()) } ``` -#### 4. Test secure instructions +### 4. Test Secure Instruction Handlers Let's go ahead and write a test to show that the attacker can no longer claim -more than once using the new instructions. +more than once using the new instruction handlers. Notice that if you start to loop through using multiple PDAs like the old test, -you can't even pass the non-canonical bump to the instructions. However, you can -still loop through using the various PDAs and at the end check that only 1 claim -happened for a total of 10 tokens. Your final test will look something like -this: +you can't even pass the non-canonical bump to the instruction handlers. However, +you can still loop through using the various PDAs and at the end check that only +1 claim happened for a total of 10 tokens. Your final test will look something +like this: ```typescript -it.only("Attacker can only claim once with secure instructions", async () => { - const attacker = Keypair.generate(); - await safeAirdrop(attacker.publicKey, provider.connection); - const ataKey = await getAssociatedTokenAddress(mint, attacker.publicKey); - const [userPDA] = findProgramAddressSync( - [attacker.publicKey.toBuffer()], - program.programId, - ); - - await program.methods - .createUserSecure() - .accounts({ - payer: attacker.publicKey, - }) - .signers([attacker]) - .rpc(); - - await program.methods - .claimSecure() - .accounts({ - payer: attacker.publicKey, - userAta: ataKey, - mint, - user: userPDA, - }) - .signers([attacker]) - .rpc(); - - let numClaims = 1; - - for (let i = 0; i < 256; i++) { - try { - const pda = createProgramAddressSync( - [attacker.publicKey.toBuffer(), Buffer.from([i])], - program.programId, - ); - await program.methods - .createUserSecure() - .accounts({ - user: pda, - payer: attacker.publicKey, - }) - .signers([attacker]) - .rpc(); - - await program.methods - .claimSecure() - .accounts({ - payer: attacker.publicKey, - userAta: ataKey, - mint, - user: pda, - }) - .signers([attacker]) - .rpc(); - - numClaims += 1; - } catch {} - } +it("allows attacker to claim only once with secure instruction handlers", async () => { + try { + const attacker = Keypair.generate(); + await airdropIfRequired( + connection, + attacker.publicKey, + 1 * LAMPORTS_PER_SOL, + 0.5 * LAMPORTS_PER_SOL, + ); + const ataKey = await getAssociatedTokenAddress(mint, attacker.publicKey); + const [userPDA] = anchor.web3.PublicKey.findProgramAddressSync( + [attacker.publicKey.toBuffer()], + program.programId, + ); + + await program.methods + .createUserSecure() + .accounts({ + payer: attacker.publicKey, + user: userPDA, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .signers([attacker]) + .rpc(); + + await program.methods + .claimSecure() + .accounts({ + payer: attacker.publicKey, + user: userPDA, + userAta: ataKey, + mint, + mintAuthority, + tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID, + associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID, + systemProgram: anchor.web3.SystemProgram.programId, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }) + .signers([attacker]) + .rpc(); + + let successfulClaimCount = 1; + + for (let i = 0; i < 256; i++) { + try { + const pda = anchor.web3.PublicKey.createProgramAddressSync( + [attacker.publicKey.toBuffer(), Buffer.from([i])], + program.programId, + ); + await program.methods + .createUserSecure() + .accounts({ + user: pda, + payer: attacker.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .signers([attacker]) + .rpc(); + + await program.methods + .claimSecure() + .accounts({ + payer: attacker.publicKey, + user: pda, + userAta: ataKey, + mint, + mintAuthority, + tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID, + associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID, + systemProgram: anchor.web3.SystemProgram.programId, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }) + .signers([attacker]) + .rpc(); + + successfulClaimCount += 1; + } catch (error) { + if ( + error instanceof Error && + !error.message.includes("Error Number: 2006") && + !error.message.includes( + "Invalid seeds, address must fall off the curve", + ) + ) { + // Comment console error logs to see the test outputs properly + console.error(error); + } + } + } + + const ata = await getAccount(connection, ataKey); - const ata = await getAccount(provider.connection, ataKey); + console.log( + `Attacker claimed ${successfulClaimCount} times and got ${Number( + ata.amount, + )} tokens`, + ); - expect(Number(ata.amount)).to.equal(10); - expect(numClaims).to.equal(1); + expect(Number(ata.amount)).to.equal(10); + expect(successfulClaimCount).to.equal(1); + } catch (error) { + throw new Error(`Test failed: ${error.message}`); + } }); ``` ```bash - bump-seed-canonicalization + Bump seed canonicalization Attacker claimed 119 times and got 1190 tokens - ✔ Attacker can claim more than reward limit with insecure instructions (128493ms) - ✔ Attacker can only claim once with secure instructions (1448ms) + ✔ allows attacker to claim more than reward limit with insecure instruction handlers (117370ms) +Attacker claimed 1 times and got 10 tokens + ✔ allows attacker to claim only once with secure instruction handlers (16362ms) ``` If you use Anchor for all of the PDA derivations, this particular exploit is @@ -594,8 +692,7 @@ pretty simple to avoid. However, if you end up doing anything "non-standard," be careful to design your program to explicitly use the canonical bump! If you want to take a look at the final solution code you can find it on the -`solution` branch of -[the same repository](https://github.com/Unboxed-Software/solana-bump-seed-canonicalization/tree/solution). +[`solution` branch of the same repository](https://github.com/solana-developers/bump-seed-canonicalization/tree/solution). ## Challenge @@ -609,6 +706,7 @@ Remember, if you find a bug or exploit in somebody else's program, please alert them! If you find one in your own program, be sure to patch it right away. + Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=d3f6ca7a-11c8-421f-b7a3-d6c08ef1aa8b)! From 76f7ff4ff751a5f8496b17bb18939d1465fe090d Mon Sep 17 00:00:00 2001 From: Neeraj Kumar Date: Tue, 1 Oct 2024 10:22:18 +0530 Subject: [PATCH 11/54] Fix inconsistency in intro-to-custom-onchain-programs.md (#544) * Fix inconsistency in intro-to-custom-onchain-programs.md * Update intro-to-custom-onchain-programs.md * Update content/courses/intro-to-solana/intro-to-custom-onchain-programs.md --------- Co-authored-by: Mike MacCana --- .../intro-to-custom-onchain-programs.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md b/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md index c0b0b3400..493fef4b8 100644 --- a/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md +++ b/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md @@ -17,15 +17,9 @@ invoked in the onchain program. ### Instructions -In previous chapters, we used: - -- The `SystemProgram.transfer()` function from `@solana/web3.js` to make an - instruction for the System program to transfer SOL. -- The `mintTo()` and `transfer()` functions from `@solana/spl-token`, to make - instructions to the Token program to mint and transfer tokens -- The `createCreateMetadataAccountV3Instruction()` function from - `@metaplex-foundation/mpl-token-metadata@2` to make instructions to Metaplex - to create token Metadata. +In previous lessons, we used the `SystemProgram.transfer()` function from +`@solana/web3.js`, which creates an instruction for the System program to +transfer SOL. When working with other programs, however, you'll need to create instructions manually. With `@solana/web3.js`, you can create instructions with the From 89aef2fc1b09313d8e942b37a3fa8922ba14d77b Mon Sep 17 00:00:00 2001 From: husna khan <131730258+husna3249@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:24:47 +0530 Subject: [PATCH 12/54] Updated fromPubkey to use PublicKey in the transfer instruction (#542) * Updated fromPubkey to use PublicKey in the transfer instruction * Update solana-pay.md * Fix duplicate fromPubkey in transfer instruction and correct indentation * fix indentation * fix indentation --------- Co-authored-by: Mike MacCana --- content/courses/solana-pay/solana-pay.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/courses/solana-pay/solana-pay.md b/content/courses/solana-pay/solana-pay.md index b8b8a0cf9..48d7991ab 100644 --- a/content/courses/solana-pay/solana-pay.md +++ b/content/courses/solana-pay/solana-pay.md @@ -236,10 +236,10 @@ async function post(req: PublicKey, res: PublicKey) { }); const instruction = SystemProgram.transfer({ - fromPubkey: account, + fromPubkey: new PublicKey(account), toPubkey: Keypair.generate().publicKey, lamports: 0.001 * LAMPORTS_PER_SOL, - }); + }); transaction.add(instruction); From f0504e43362980faf36555ce4c7568f182cf1502 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 1 Oct 2024 19:10:26 -0600 Subject: [PATCH 13/54] fix typo (#548) * fix typo ez fix * Update content/courses/program-optimization/program-architecture.md --------- Co-authored-by: Mike MacCana --- content/courses/program-optimization/program-architecture.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/courses/program-optimization/program-architecture.md b/content/courses/program-optimization/program-architecture.md index d1d05e1f6..805888b71 100644 --- a/content/courses/program-optimization/program-architecture.md +++ b/content/courses/program-optimization/program-architecture.md @@ -271,8 +271,8 @@ const txHash = await program.methods .rpc(); ``` -The second caveat is that your'll have to call one of the following methods from -inside your rust instruction function to load the account: +The second caveat is that you'll have to call one of the following methods from +inside your rust instruction handler to load the account: - `load_init` when first initializing an account (this will ignore the missing account discriminator that gets added only after the user's instruction code) From 6506b8d30c4467799523f5e3750479e6a47e771d Mon Sep 17 00:00:00 2001 From: Sasha Shpota <5640984+Shpota@users.noreply.github.com> Date: Wed, 2 Oct 2024 03:39:11 +0200 Subject: [PATCH 14/54] Add sol4k to the client-side development section (#547) --- docs/intro/dev.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/intro/dev.md b/docs/intro/dev.md index 1d9d5caab..751e5a7b9 100644 --- a/docs/intro/dev.md +++ b/docs/intro/dev.md @@ -68,16 +68,16 @@ If you're developing on the client-side, you can work with any programming language you're comfortable with. Solana has community-contributed SDKs to help developers interact with the Solana network in most popular languages : -| Language | SDK | -| ---------- | ------------------------------------------------------------------------------------------- | -| RUST | [solana_sdk](https://docs.rs/solana-sdk/latest/solana_sdk/) | -| Typescript | [@solana/web3.js](https://github.com/solana-labs/solana-web3.js) | -| Python | [solders](https://github.com/kevinheavey/solders) | -| Java | [solanaj](https://github.com/skynetcap/solanaj) | -| C++ | [solcpp](https://github.com/mschneider/solcpp) | -| Go | [solana-go](https://github.com/gagliardetto/solana-go) | -| Kotlin | [solanaKT](https://github.com/metaplex-foundation/SolanaKT) | -| Dart | [solana](https://github.com/espresso-cash/espresso-cash-public/tree/master/packages/solana) | +| Language | SDK | +| ---------- | ------------------------------------------------------------------------------------------------------ | +| RUST | [solana_sdk](https://docs.rs/solana-sdk/latest/solana_sdk/) | +| Typescript | [@solana/web3.js](https://github.com/solana-labs/solana-web3.js) | +| Python | [solders](https://github.com/kevinheavey/solders) | +| Java | [solanaj](https://github.com/skynetcap/solanaj) | +| C++ | [solcpp](https://github.com/mschneider/solcpp) | +| Go | [solana-go](https://github.com/gagliardetto/solana-go) | +| Kotlin | [solanaKT](https://github.com/metaplex-foundation/SolanaKT) or [sol4k](https://github.com/sol4k/sol4k) | +| Dart | [solana](https://github.com/espresso-cash/espresso-cash-public/tree/master/packages/solana) | You'll also need a connection with an RPC to interact with the network. You can either work with a [RPC infrastructure provider](https://solana.com/rpc) or From e7460a59f7351f5e6cb87fc4b8195fea04509f39 Mon Sep 17 00:00:00 2001 From: davik stone <50995003+davik20@users.noreply.github.com> Date: Wed, 2 Oct 2024 02:47:39 +0100 Subject: [PATCH 15/54] Fix typo: Change "species" to "specifies" in documentation (#519) * update species to specifies * update species to specifies * Update content/courses/onchain-development/intro-to-anchor-frontend.md * Update content/courses/onchain-development/intro-to-anchor-frontend.md --------- Co-authored-by: Mike MacCana --- .../onchain-development/intro-to-anchor-frontend.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/courses/onchain-development/intro-to-anchor-frontend.md b/content/courses/onchain-development/intro-to-anchor-frontend.md index ecbe269e3..c4ea5331e 100644 --- a/content/courses/onchain-development/intro-to-anchor-frontend.md +++ b/content/courses/onchain-development/intro-to-anchor-frontend.md @@ -154,11 +154,11 @@ counter program you built previously: Inspecting the IDL, you can see the `programId` and the `metadata` object which have been added in anchor 0.30.0 -This program contains two instructions (`initialize` and `increment`). +This program contains two instruction handlers, `initialize` and `increment`. -Notice that in addition to specifying the instructions, it species the accounts -and inputs for each instruction. The `initialize` instruction requires three -accounts: +Notice that in addition to specifying the instruction handlers, it specifies the +accounts and inputs for each instruction. The `initialize` instruction requires +three accounts: 1. `counter` - the new account being initialized in the instruction 2. `user` - the payer for the transaction and initialization From 0dfaf2847436034d0d1f9a1ada9d391623941a83 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Wed, 2 Oct 2024 11:53:24 +1000 Subject: [PATCH 16/54] Prettier --- .../intro-to-solana/intro-to-custom-onchain-programs.md | 4 ++-- content/courses/solana-pay/solana-pay.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md b/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md index 493fef4b8..8ad09db6e 100644 --- a/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md +++ b/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md @@ -17,8 +17,8 @@ invoked in the onchain program. ### Instructions -In previous lessons, we used the `SystemProgram.transfer()` function from -`@solana/web3.js`, which creates an instruction for the System program to +In previous lessons, we used the `SystemProgram.transfer()` function from +`@solana/web3.js`, which creates an instruction for the System program to transfer SOL. When working with other programs, however, you'll need to create instructions diff --git a/content/courses/solana-pay/solana-pay.md b/content/courses/solana-pay/solana-pay.md index 48d7991ab..bfacb6677 100644 --- a/content/courses/solana-pay/solana-pay.md +++ b/content/courses/solana-pay/solana-pay.md @@ -239,7 +239,7 @@ async function post(req: PublicKey, res: PublicKey) { fromPubkey: new PublicKey(account), toPubkey: Keypair.generate().publicKey, lamports: 0.001 * LAMPORTS_PER_SOL, - }); + }); transaction.add(instruction); From 33c09667975c9680d78d88a5071b35a687dfa711 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Wed, 2 Oct 2024 11:53:51 +1000 Subject: [PATCH 17/54] Remove deprecation warnings that are now fixed --- content/courses/mobile/metadata.yml | 4 ---- content/courses/state-compression/metadata.yml | 4 ---- content/courses/tokens-and-nfts/metadata.yml | 4 ---- 3 files changed, 12 deletions(-) diff --git a/content/courses/mobile/metadata.yml b/content/courses/mobile/metadata.yml index f0dff73dd..0d22afc5b 100644 --- a/content/courses/mobile/metadata.yml +++ b/content/courses/mobile/metadata.yml @@ -7,7 +7,3 @@ lessons: - mwa-deep-dive - solana-mobile-dapps-with-expo priority: 99 -# Uses deprecated @metaplex-foundation/js library for NFTs -# which also uses old RPC methods and no longer functions. -# TODO: Superteam to update -isHidden: true diff --git a/content/courses/state-compression/metadata.yml b/content/courses/state-compression/metadata.yml index a95b504a7..3a86b52a2 100644 --- a/content/courses/state-compression/metadata.yml +++ b/content/courses/state-compression/metadata.yml @@ -6,7 +6,3 @@ lessons: - compressed-nfts - generalized-state-compression priority: 65 -# Uses deprecated @metaplex-foundation/js library for NFTs -# which also uses old RPC methods and no longer functions. -# TODO: Superteam to update -isHidden: true diff --git a/content/courses/tokens-and-nfts/metadata.yml b/content/courses/tokens-and-nfts/metadata.yml index 07e368033..ec3629722 100644 --- a/content/courses/tokens-and-nfts/metadata.yml +++ b/content/courses/tokens-and-nfts/metadata.yml @@ -6,7 +6,3 @@ lessons: - token-program-advanced - nfts-with-metaplex priority: 10 -# Uses deprecated @metaplex-foundation/js library for NFTs -# which also uses old RPC methods and no longer functions. -# TODO: Superteam to update -isHidden: true From fc6d1c18ab5ac53c4ae57c7962f3ceb7a0bfbcc6 Mon Sep 17 00:00:00 2001 From: XuananLe <116771860+XuananLe@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:39:40 +0700 Subject: [PATCH 18/54] Mobile Development: Update the docs + code of Solana Mobile Dapps with Expo (#461) * Update solana-mobile-dapps-with-expo.md * Run prettier the README file * Update the type Nft We don't use the @metaplex-foundation/js any more so we need to update the type NFT * Update solana-mobile-dapps-with-expo.md Fix README format * Migrate to Pinata Cloud instead of NFT Storage because they stopped their classic service since June 2024 * Update to Pinata Upload Image and Upload Data * Formatting + Remove unnecessary deps * Fix NftProvider + Update ContentBox * Update Code + Address all the issues * Small Fix + remove rn-fetch-blob because we don't need anymore. * Remove redundant console.log() * Small Fix Env variable * Update Docs + Small fix * Update content/courses/mobile/solana-mobile-dapps-with-expo.md * Update content/courses/mobile/solana-mobile-dapps-with-expo.md --------- Co-authored-by: Mike MacCana --- .../mobile/solana-mobile-dapps-with-expo.md | 1138 +++++++++-------- 1 file changed, 636 insertions(+), 502 deletions(-) diff --git a/content/courses/mobile/solana-mobile-dapps-with-expo.md b/content/courses/mobile/solana-mobile-dapps-with-expo.md index 16ee816f3..90d1bece8 100644 --- a/content/courses/mobile/solana-mobile-dapps-with-expo.md +++ b/content/courses/mobile/solana-mobile-dapps-with-expo.md @@ -11,10 +11,10 @@ description: "How to use Solana in your Expo apps." - Expo is an open-source collection of tools and libraries that wrap around React Native, much like Next.js is a framework built on top of React. -- In addition to simplifying the build/deploy process, Expo provides packages - that give you access to mobile devices' peripherals and capabilities. -- A lot of Solana ecosystem libraries don't support React native out of the box, - but you can typically use them with the right +- Along with simplifying the build and deploy process, Expo offers packages that + allow access to mobile device peripherals and capabilities. +- Many Solana ecosystem libraries don't natively support React Native, but you + can often use them with the appropriate [polyfills](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill). ## Lesson @@ -37,8 +37,9 @@ lesson will be spent in the lab. ### React Native Expo -Expo is an open-source collection of tools and libraries that wrap around React -Native, much like Next.js is a framework built on top of React. +Expo is an open-source platform for making universal native apps for Android, +iOS, and the web that wraps around React Native, much like Next.js is a +framework built on top of React. Expo consists of three main parts: @@ -46,15 +47,15 @@ Expo consists of three main parts: 2. The Expo Go App 3. A suite of libraries that grant access to various mobile device capabilities. -The Expo CLI is a build and debugging tool that helps make all of the magic -happen. Chances are, you'll only have to interact with it when you're building -or starting a development server. It just works. +The Expo CLI is a powerful tool for building and debugging that simplifies the +development process. Chances are, you'll only have to interact with it when +you're building or starting a development server. It just works. The [Expo Go App](https://expo.dev/client) is a really cool piece of tech that allows _most_ apps to be developed without using an emulator or physical device. You download the app, you scan the QR from the build output and then you have a -working dev environment right on your phone. Unfortunately, this will not work -with the Solana mobile SDK. Coming from the +working dev environment right on your phone. However, this doesn't work with the +Solana Mobile SDK. Coming from the [Solana Expo setup article](https://docs.solanamobile.com/react-native/expo): > The traditional Expo Go development flow is only limited to certain @@ -64,7 +65,7 @@ with the Solana mobile SDK. Coming from the > fully compatible with Expo. Lastly, and most importantly, Expo does an amazing job providing -[easy-to-use libraries](https://docs.expo.dev/versions/latest/) that give you +[comprehensive libraries](https://docs.expo.dev/versions/latest/) that give you access to the device's onboard peripherals, such as camera, battery, and speakers. The libraries are intuitive and the documentation is phenomenal. @@ -73,12 +74,12 @@ speakers. The libraries are intuitive and the documentation is phenomenal. To get started with Expo, you first need the prerequisite setup described in the [Introduction to Solana Mobile lesson](/content/courses/mobile/intro-to-solana-mobile). After that, you'll want to sign up for an -[Expo Application Services (EAS) account](https://expo.dev/). +[Expo Application Services (EAS) account](https://expo.dev/eas). Once you have an EAS account, you can install the EAS CLI and log in: ```bash -npm install --global eas-cli +npm install -g eas-cli eas login ``` @@ -102,7 +103,7 @@ the following inside this file: ```json { "cli": { - "version": ">= 5.2.0" + "version": ">= 5.12.0" }, "build": { "development": { @@ -120,13 +121,14 @@ the following inside this file: } ``` -With the EAS config file created, you can build using the -`npx eas build --local` command plus relevant flags for any additional -requirements. For example, the following will build the project locally with a -development profile specifically for Android: +With the EAS configuration file in place, you can build your project using +`eas build`. This submits a job to the EAS Build service, where your APK is +built using Expo's cloud infrastructure. If you want to build locally, you can +add the `--local` flag. For example, the following command builds the project +locally with a development profile specifically for Android: ```bash -npx eas build --profile development --platform android --local +eas build --profile development --platform android --message "Developing on Android!" --local ``` You then need to install the output APK to your device or emulator. If you're @@ -168,8 +170,12 @@ JS/TS. import { Pedometer } from "expo-sensors"; ``` -Depending on the package, there may be additional setup required. Be sure to -read the [docs](https://docs.expo.dev/versions/latest/) when working with a new +Depending on the package, there may be additional setup required. For example, +if you're using the `expo-camera` package, you not only need to install the +package but also configure the appropriate permissions in your `app.json` or +`AndroidManifest.xml` file for Android and request runtime permissions for +accessing the camera. Be sure to read the +[Expo docs](https://docs.expo.dev/versions/latest/) when working with a new package. ### Integrate ecosystem libraries into your Expo app @@ -204,8 +210,10 @@ For a Solana + Expo app, you'll need the following: as `Transaction` and `Uint8Array`. - `@solana/web3.js`: Solana Web Library for interacting with the Solana network through the [JSON RPC API](/docs/rpc/http/index.mdx). -- `react-native-get-random-values`: Secure random number generator polyfill - for `web3.js` underlying Crypto library on React Native. +- `expo-crypto` is a secure random number generator polyfill used in React + Native for web3.js's underlying Crypto library. This feature is supported only + in Expo SDK version 49+ and requires Expo Router. Make sure your setup is + updated to meet these requirements. - `buffer`: Buffer polyfill needed for `web3.js` on React Native. #### Metaplex Polyfills @@ -213,37 +221,32 @@ For a Solana + Expo app, you'll need the following: If you want to use the Metaplex SDK, you'll need to add the Metaplex library plus a few additional polyfills: -- `@metaplex-foundation/js@0.19.4` - Metaplex Library +- `@metaplex-foundation/umi` `@metaplex-foundation/umi-bundle-defaults` + `@metaplex-foundation/mpl-core` - Metaplex Library - Several more polyfills - `assert` - - `util` - `crypto-browserify` - - `stream-browserify` - `readable-stream` - - `browserify-zlib` - - `path-browserify` - - `react-native-url-polyfill` - -All of the libraries that the above polyfills are meant to replace are utilized -by the Metaplex library in the background. It's unlikely you'll be importing any -of them into your code directly. Because of this, you'll need to register the -polyfills using a `metro.config.js` file. This will ensure that Metaplex uses -the polyfills instead of the usual Node.js libraries that aren't supported in -React Native. Below is an example `metro.config.js` file: + - `zlib` + - `react-native-url-polyfill` All of the libraries that the above polyfills + are meant to replace are utilized by the Metaplex libraries in the + background. It's unlikely you'll be importing any of them into your code + directly. Because of this, you'll need to register the polyfills using a + `metro.config.js` file. This will ensure that Metaplex uses the polyfills + instead of the usual Node.js libraries that aren't supported in React + Native. Below is an example `metro.config.js` file: ```js -const { getDefaultConfig } = require("@expo/metro-config"); -const defaultConfig = getDefaultConfig(__dirname); - -defaultConfig.resolver.extraNodeModules = { - crypto: require.resolve("crypto-browserify"), - stream: require.resolve("readable-stream"), - url: require.resolve("react-native-url-polyfill"), - zlib: require.resolve("browserify-zlib"), - path: require.resolve("path-browserify"), -}; +// Learn more https://docs.expo.io/guides/customizing-metro +const { getDefaultConfig } = require("expo/metro-config"); + +/** @type {import('expo/metro-config').MetroConfig} */ +const config = getDefaultConfig(__dirname); -module.exports = defaultConfig; +// Add polyfill resolvers +config.resolver.extraNodeModules.crypto = require.resolve("expo-crypto"); + +module.exports = config; ``` ### Putting it all together @@ -260,9 +263,11 @@ Let's practice this together by building the Mint-A-Day app, where users will able to mint a single NFT snapshot of their lives daily, creating a permanent diary of sorts. -To mint the NFTs we'll be using Metaplex's Javascript SDK along with -[nft.storage](https://nft.storage/) to store images and metadata. All of our -onchain work will be on Devnet. +To mint the NFTs we'll be using Metaplex's Umi libraries along with +[Pinata Cloud](https://pinata.cloud/) to store images and metadata. We are using +Pinata in this tutorial, but +[there are many good solutions for store images for long-term storage](https://solana.com/developers/guides/getstarted/how-to-create-a-token#create-and-upload-image-and-offchain-metadata). +All of our onchain work will be on Devnet. The first half of this lab is cobbling together the needed components to make Expo, Solana, and Metaplex all work together. We'll do this modularly so you'll @@ -293,12 +298,12 @@ it to run. We use 5GB of ram on our side. To simplify the Expo process, you'll want an Expo Application Services (EAS) account. This will help you build and run the application. -First sign up for an [EAS account](https://expo.dev/). +First sign up for an [EAS account](https://expo.dev/eas). Then, install the EAS CLI and log in: ```bash -npm install --global eas-cli +npm install -g eas-cli eas login ``` @@ -307,13 +312,13 @@ eas login Let's create our app with the following: ```bash -npx create-expo-app -t expo-template-blank-typescript solana-expo +npx create-expo-app --template blank-typescript solana-expo cd solana-expo +npx expo install expo-dev-client # This installs a library that enables the creation of custom development builds, providing useful tools for debugging and testing. While optional, it is recommended for a smoother development experience. ``` This uses `create-expo-app` to generate a new scaffold for us based on the -`expo-template-blank-typescript` template. This is just an empty Typescript -React Native app. +`blank-typescript` template. A Blank template with TypeScript enabled. #### 3. Local build config @@ -331,7 +336,7 @@ Copy and paste the following into the newly created `eas.json`: ```json { "cli": { - "version": ">= 5.2.0" + "version": ">= 3.12.0" }, "build": { "development": { @@ -351,8 +356,8 @@ Copy and paste the following into the newly created `eas.json`: #### 4. Build and emulate -Now let's build the project. You will choose `y` for every answer. This will -take a while to complete. +Now let's build the project locally. You will choose `y` for every answer. This +will take a while to complete. ```bash npx eas build --profile development --platform android --local @@ -387,15 +392,14 @@ already have a Devnet-enabled wallet installed you can skip step 0. You'll need a wallet that supports Devnet to test with. In [our Mobile Wallet Adapter lesson](/content/courses/mobile/mwa-deep-dive) we -created one of these. Let's install it from the solution branch in a different -directory from our app: +created one of these. Let's install it from the repo in a different directory +from our app: ```bash cd .. -git clone https://github.com/Unboxed-Software/react-native-fake-solana-wallet +git clone https://github.com/solana-developers/react-native-fake-solana-wallet cd react-native-fake-solana-wallet -git checkout solution -npm run install +yarn ``` The wallet should be installed on your emulator or device. Make sure to open the @@ -416,11 +420,11 @@ all Solana mobile apps. This will include some polyfills that allow otherwise incompatible packages to work with React native: ```bash -npm install \ +yarn add \ @solana/web3.js \ @solana-mobile/mobile-wallet-adapter-protocol-web3js \ @solana-mobile/mobile-wallet-adapter-protocol \ - react-native-get-random-values \ + expo-crypto \ buffer ``` @@ -433,16 +437,17 @@ Create two new folders: `components` and `screens`. We are going to use some boilerplate code from the [first Mobile lesson](/content/courses/mobile/basic-solana-mobile). We will be -copying over `components/AuthProvider.tsx` and +copying over `components/AuthorizationProvider.tsx` and `components/ConnectionProvider.tsx`. These files provide us with a `Connection` object as well as some helper functions that authorize our dapp. -Create file `components/AuthProvider.tsx` and copy the contents -[of our existing Auth Provider from Github](https://raw.githubusercontent.com/Unboxed-Software/solana-advance-mobile/main/components/AuthProvider.tsx) +Create file `components/AuthorizationProvider.tsx` and copy the contents of +[our existing Auth Provider from Github](https://raw.githubusercontent.com/solana-developers/mobile-apps-with-expo/main/components/AuthorizationProvider.tsx) into the new file. Secondly, create file `components/ConnectionProvider.tsx` and copy the contents -[of our existing Connection Provider from Github](https://raw.githubusercontent.com/Unboxed-Software/solana-advance-mobile/main/components/ConnectionProvider.tsx) +of +[our existing Connection Provider from Github](https://raw.githubusercontent.com/solana-developers/mobile-apps-with-expo/main/components/ConnectionProvider.tsx) into the new file. Now let's create a boilerplate for our main screen in `screens/MainScreen.tsx`: @@ -460,18 +465,46 @@ export function MainScreen() { } ``` +Next, create file called `polyfills.ts` for react-native to work with all the +solana dependencies + +```typescript filename="polyfills.ts" +import { getRandomValues as expoCryptoGetRandomValues } from "expo-crypto"; +import { Buffer } from "buffer"; + +// Set global Buffer +global.Buffer = Buffer; + +// Define Crypto class with getRandomValues method +class Crypto { + getRandomValues = expoCryptoGetRandomValues; +} + +// Check if crypto is already defined in the global scope +const hasInbuiltWebCrypto = typeof window.crypto !== "undefined"; + +// Use existing crypto if available, otherwise create a new Crypto instance +const webCrypto = hasInbuiltWebCrypto ? window.crypto : new Crypto(); + +// Polyfill crypto object if it's not already defined +if (!hasInbuiltWebCrypto) { + Object.defineProperty(window, "crypto", { + configurable: true, + enumerable: true, + get: () => webCrypto, + }); +} +``` + Finally, let's change `App.tsx` to wrap our application in the two providers we just created: ```tsx -import "react-native-get-random-values"; -import { StatusBar } from "expo-status-bar"; -import { StyleSheet, Text, View } from "react-native"; import { ConnectionProvider } from "./components/ConnectionProvider"; -import { AuthorizationProvider } from "./components/AuthProvider"; +import { AuthorizationProvider } from "./components/AuthorizationProvider"; import { clusterApiUrl } from "@solana/web3.js"; import { MainScreen } from "./screens/MainScreen"; -global.Buffer = require("buffer").Buffer; +import "./polyfills"; export default function App() { const cluster = "devnet"; @@ -491,22 +524,36 @@ export default function App() { } ``` -Notice we've added two polyfills above: `buffer` and -`react-native-get-random-values`. These are necessary for the Solana -dependencies to run correctly. +Notice we've added the polyfills file `polyfills.ts`. These are necessary for +the Solana dependencies to run correctly. #### 4. Build and run Solana boilerplate +Add the following convenient run scripts to your `package.json` file. + +```json + "scripts": { + "start": "expo start --dev-client", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web", + "build": "npx eas build --profile development --platform android", + "build:local": "npx eas build --profile development --platform android --local", + "test": "echo \"No tests specified\" && exit 0", + "clean": "rm -rf node_modules && yarn" + } +``` + Let's make sure everything is working and compiling correctly. In Expo, anytime you change the dependencies, you'll need to rebuild and re-install the app. **_Optional:_** To avoid possible build version conflicts, you may want to _uninstall_ the previous version before you drag and drop the new one in. -Build: +Build locally: ```bash -npx eas build --profile development --platform android --local +yarn run build:local ``` Install: **_Drag_** the resulting build file into your emulator. @@ -514,7 +561,7 @@ Install: **_Drag_** the resulting build file into your emulator. Run: ```bash -npx expo start --dev-client --android +yarn run android ``` Everything should compile and you should have a boilerplate Solana Expo app. @@ -528,12 +575,12 @@ you can reference. #### 1. Install Metaplex dependencies -The Metaplex SDK abstracts away a lot of the minutia of working with NFTs, -however it was written largely for Node.js, so we'll need several more polyfills -to make it work: +[Metaplex programs and tools](https://developers.metaplex.com/programs-and-tools) +abstracts away a lot of the minutia of working with NFTs, however it was written +largely for Node.js, so we'll need several more polyfills to make it work: ```bash -npm install assert \ +yarn add assert \ util \ crypto-browserify \ stream-browserify \ @@ -541,7 +588,12 @@ npm install assert \ browserify-zlib \ path-browserify \ react-native-url-polyfill \ - @metaplex-foundation/js@0.19.4 + @metaplex-foundation/umi \ + @metaplex-foundation/umi-bundle-defaults \ + @metaplex-foundation/umi-signer-wallet-adapters \ + @metaplex-foundation/umi-web3js-adapters \ + @metaplex-foundation/mpl-token-metadata \ + @metaplex-foundation/mpl-candy-machine ``` #### 2. Polyfill config @@ -555,122 +607,95 @@ touch metro.config.js Copy and paste the following into `metro.config.js`: -```js -// Import the default Expo Metro config -const { getDefaultConfig } = require("@expo/metro-config"); - -// Get the default Expo Metro configuration -const defaultConfig = getDefaultConfig(__dirname); - -// Customize the configuration to include your extra node modules -defaultConfig.resolver.extraNodeModules = { - crypto: require.resolve("crypto-browserify"), - stream: require.resolve("readable-stream"), - url: require.resolve("react-native-url-polyfill"), - zlib: require.resolve("browserify-zlib"), - path: require.resolve("path-browserify"), -}; +```javascript +// Learn more https://docs.expo.io/guides/customizing-metro +const { getDefaultConfig } = require("expo/metro-config"); -// Export the modified configuration -module.exports = defaultConfig; +/** @type {import('expo/metro-config').MetroConfig} */ +const config = getDefaultConfig(__dirname); + +// Add polyfill resolvers +config.resolver.extraNodeModules.crypto = require.resolve("expo-crypto"); + +module.exports = config; ``` #### 3. Metaplex provider -We're going to create a Metaplex provider file that will help us access a -`Metaplex` object. This `Metaplex` object is what gives us access to all of the -functions we'll need like `fetch` and `create`. To do this we create a new file -`/components/MetaplexProvider.tsx`. Here we pipe our mobile wallet adapter into -an `IdentitySigner` for the `Metaplex` object to use. This allows it to call -several privileged functions on our behalf: +We'll be creating NFTs using +[Metaplex's MPL Token Metadata library](https://developers.metaplex.com/token-metadata), +leveraging the `Umi` object, a tool commonly used in many Metaplex applications. +This combination will give us access to key functions like `fetch` and `create` +that are essential for NFT creation. To set this up, we will create a new file, +`/components/UmiProvider.tsx`, where we'll connect our mobile wallet adapter to +the `Umi` object. This allows us to execute privileged actions, such as +interacting with token metadata, on our behalf. ```tsx +import { createContext, ReactNode, useContext } from "react"; +import type { Umi } from "@metaplex-foundation/umi"; import { - IdentitySigner, - Metaplex, - MetaplexPlugin, -} from "@metaplex-foundation/js"; -import { - transact, - Web3MobileWallet, -} from "@solana-mobile/mobile-wallet-adapter-protocol-web3js"; -import { Connection, Transaction } from "@solana/web3.js"; -import { useMemo } from "react"; -import { Account } from "./AuthProvider"; - -export const mobileWalletAdapterIdentity = ( - mwaIdentitySigner: IdentitySigner, -): MetaplexPlugin => ({ - install(metaplex: Metaplex) { - metaplex.identity().setDriver(mwaIdentitySigner); - }, -}); - -export const useMetaplex = ( - connection: Connection, - selectedAccount: Account | null, - authorizeSession: (wallet: Web3MobileWallet) => Promise, -) => { - return useMemo(() => { - if (!selectedAccount || !authorizeSession) { - return { mwaIdentitySigner: null, metaplex: null }; - } - - const mwaIdentitySigner: IdentitySigner = { - publicKey: selectedAccount.publicKey, - signMessage: async (message: Uint8Array): Promise => { - return await transact(async (wallet: Web3MobileWallet) => { - await authorizeSession(wallet); - - const signedMessages = await wallet.signMessages({ - addresses: [selectedAccount.publicKey.toBase58()], - payloads: [message], - }); - - return signedMessages[0]; - }); - }, - signTransaction: async ( - transaction: Transaction, - ): Promise => { - return await transact(async (wallet: Web3MobileWallet) => { - await authorizeSession(wallet); + createNoopSigner, + publicKey, + signerIdentity, +} from "@metaplex-foundation/umi"; +import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; +import { walletAdapterIdentity } from "@metaplex-foundation/umi-signer-wallet-adapters"; +import { mplTokenMetadata } from "@metaplex-foundation/mpl-token-metadata"; +import { mplCandyMachine } from "@metaplex-foundation/mpl-candy-machine"; +import { useAuthorization } from "./AuthorizationProvider"; + +type UmiContext = { + umi: Umi | null; +}; - const signedTransactions = await wallet.signTransactions({ - transactions: [transaction], - }); +const DEFAULT_CONTEXT: UmiContext = { + umi: null, +}; - return signedTransactions[0]; - }); - }, - signAllTransactions: async ( - transactions: Transaction[], - ): Promise => { - return transact(async (wallet: Web3MobileWallet) => { - await authorizeSession(wallet); - const signedTransactions = await wallet.signTransactions({ - transactions: transactions, - }); - return signedTransactions; - }); - }, - }; +export const UmiContext = createContext(DEFAULT_CONTEXT); - const metaplex = Metaplex.make(connection).use( - mobileWalletAdapterIdentity(mwaIdentitySigner), +export const UmiProvider = ({ + endpoint, + children, +}: { + endpoint: string; + children: ReactNode; +}) => { + const { selectedAccount } = useAuthorization(); + const umi = createUmi(endpoint) + .use(mplTokenMetadata()) + .use(mplCandyMachine()); + if (selectedAccount === null) { + const noopSigner = createNoopSigner( + publicKey("11111111111111111111111111111111"), ); + umi.use(signerIdentity(noopSigner)); + } else { + umi.use(walletAdapterIdentity(selectedAccount)); + } - return { metaplex }; - }, [authorizeSession, selectedAccount, connection]); + return {children}; }; + +export function useUmi(): Umi { + const umi = useContext(UmiContext).umi; + if (!umi) { + throw new Error( + "Umi context was not initialized. " + + "Did you forget to wrap your app with ?", + ); + } + return umi; +} ``` #### 4. NFT Provider We're also making a higher-level NFT provider that helps with NFT state management. It combines all three of our previous providers: -`ConnectionProvider`, `AuthProvider`, and `MetaplexProvider` to allow us to -create our `Metaplex` object. We will fill this out at a later step; for now, it +`ConnectionProvider`, `AuthorizationProvider`, and `UmiProvider` to allow us to +create our `Umi` object. We will fill this out at a later step; for now, it makes for a good boilerplate. Let's create the new file `components/NFTProvider.tsx`: @@ -678,17 +703,22 @@ Let's create the new file `components/NFTProvider.tsx`: ```tsx import "react-native-url-polyfill/auto"; import { useConnection } from "./ConnectionProvider"; -import { Account, useAuthorization } from "./AuthProvider"; +import { Account, useAuthorization } from "./AuthorizationProvider"; import React, { ReactNode, createContext, useContext, useState } from "react"; -import { useMetaplex } from "./MetaplexProvider"; +import { useUmi } from "./UmiProvider"; +import { Umi } from "@metaplex-foundation/umi"; export interface NFTProviderProps { children: ReactNode; } -export interface NFTContextState {} +export interface NFTContextState { + umi: Umi | null; +} -const DEFAULT_NFT_CONTEXT_STATE: NFTContextState = {}; +const DEFAULT_NFT_CONTEXT_STATE: NFTContextState = { + umi: null, +}; const NFTContext = createContext(DEFAULT_NFT_CONTEXT_STATE); @@ -698,9 +728,11 @@ export function NFTProvider(props: NFTProviderProps) { const { connection } = useConnection(); const { authorizeSession } = useAuthorization(); const [account, setAccount] = useState(null); - const { metaplex } = useMetaplex(connection, account, authorizeSession); + const { umi } = useUmi(connection, account, authorizeSession); - const state = {}; + const state: NFTContextState = { + umi, + }; return {children}; } @@ -716,13 +748,12 @@ Notice we've added yet another polyfill to the top Now, let's wrap our new `NFTProvider` around `MainScreen` in `App.tsx`: ```tsx -import "react-native-get-random-values"; +import "./polyfills"; import { ConnectionProvider } from "./components/ConnectionProvider"; -import { AuthorizationProvider } from "./components/AuthProvider"; +import { AuthorizationProvider } from "./components/AuthorizationProvider"; import { clusterApiUrl } from "@solana/web3.js"; import { MainScreen } from "./screens/MainScreen"; import { NFTProvider } from "./components/NFTProvider"; -global.Buffer = require("buffer").Buffer; export default function App() { const cluster = "devnet"; @@ -769,12 +800,12 @@ npx expo start --dev-client --android Everything we've done to this point is effectively boilerplate. We need to add the functionality we intend for our Mint-A-Day app to have. Mint-A-day is a -daily snapshot app. It lets users take a snapshot of their life daily in the +daily snapshot app. It allows users take a snapshot of their life daily in the form of minting an NFT. The app will need access to the device's camera and a place to remotely store the captured images. Fortunately, Expo SDK can provide access to the camera and -[NFT.Storage](https://nft.storage) can store your NFT files for free. +[Pinata Cloud](https://pinata.cloud/) can store your NFT files safely. #### 1. Camera setup @@ -806,31 +837,40 @@ as a plugin in `app.json`: } ``` -This particular dependency makes it super simple to use the camera. To allow the -user to take a picture and return the image all you have to do is call the -following: +This dependency makes it incredibly easy to use the camera. To allow the user to +take a picture and return the image, simply call the following: ```tsx +// Launch the camera to take a picture using ImagePicker const result = await ImagePicker.launchCameraAsync({ + // Restrict media types to images only (no videos) mediaTypes: ImagePicker.MediaTypeOptions.Images, + + // Allow the user to edit/crop the image after taking it allowsEditing: true, + + // Specify the aspect ratio of the cropping frame (1:1 for a square) aspect: [1, 1], + + // Set the image quality to maximum (1.0 = highest quality, 0.0 = lowest) quality: 1, }); + +// 'result' will contain information about the captured image +// If the user cancels, result.cancelled will be true, otherwise it will contain the image URI ``` No need to add this anywhere yet - we'll get to it in a few steps. -#### 2. NFT.Storage setup +#### 2. Pinata Cloud setup The last thing we need to do is set up our access to -[nft.storage](https://nft.storage). We'll need to get an API key and add it as -an environment variable, then we need to add one last dependency to convert our -images into a file type we can upload. +[Pinata Cloud](https://pinata.cloud/). We'll need to get an API key and add it +as an environment variable, then we need to add one last dependency to convert +our images into a file type we can upload. -We'll be using NFT.storage to host our NFTs with IPFS since they do this for -free. [Sign up, and create an API key](https://nft.storage/manage/). Keep this -API key private. +We'll be using Pinata Cloud to host our NFTs with IPFS since they do this for a +very cheap price. Remember to keep this API key private. Best practices suggest keeping API keys in a `.env` file with `.env` added to your `.gitignore`. It's also a good idea to create a `.env.example` file that @@ -840,19 +880,13 @@ for the project. Create both files, in the root of your directory and add `.env` to your `.gitignore` file. -Then, add your API key to the `.env` file with the name -`EXPO_PUBLIC_NFT_STORAGE_API`. Now you'll be able to access your API key safely -in the application. - -Lastly, install `rn-fetch-blob`. This package will help us grab images from the -device's URI scheme and turn them into Blobs we can the upload to -[NFT.storage](https://nft.storage). - -Install it with the following: - -```bash -npm i rn-fetch-blob -``` +Next, add your API key to the `.env` file with the variable name +`EXPO_PUBLIC_NFT_PINATA_JWT`. This allows you to securely access your API key in +the application using `process.env.EXPO_PUBLIC_NFT_PINATA_JWT`, unlike +traditional `import "dotenv/config"` which may require additional polyfills when +working with Expo. For more information on securely storing secrets, refer to +the +[Expo documentation on environment variables](https://docs.expo.dev/build-reference/variables/#importing-secrets-from-a-dotenv-file) #### 3. Final build @@ -887,8 +921,8 @@ The app itself is relatively straightforward. The general flow is: 1. The user connects (authorizes) using the `transact` function and by calling `authorizeSession` inside the callback -2. Our code then uses the `Metaplex` object to fetch all of the NFTs created by - the user +2. Our code then uses the `Umi` object to fetch all of the NFTs created by the + user 3. If an NFT has not been created for the current day, allow the user to take a picture, upload it, and mint it as an NFT @@ -897,26 +931,28 @@ The app itself is relatively straightforward. The general flow is: `NFTProvider.tsx` will control the state with our custom `NFTProviderContext`. This should have the following fields: -- `metaplex: Metaplex | null` - Holds the metaplex object that we use to call - `fetch` and `create` +- `umi: Umi | null` - Holds the metaplex object that we use to call `fetch` and + `create` - `publicKey: PublicKey | null` - The NFT creator's public key - `isLoading: boolean` - Manages loading state -- `loadedNFTs: (Nft | Sft | SftWithToken | NftWithToken)[] | null` - An array of - the user's snapshot NFTs -- `nftOfTheDay: (Nft | Sft | SftWithToken | NftWithToken) | null` - A reference - to the NFT created today +- `loadedNFTs: (DigitalAsset)[] | null` - An array of the user's snapshot NFTs +- `nftOfTheDay: (DigitalAsset) | null` - A reference to the NFT created today - `connect: () => void` - A function for connecting to the Devnet-enabled wallet - `fetchNFTs: () => void` - A function that fetches the user's snapshot NFTs - `createNFT: (name: string, description: string, fileUri: string) => void` - A function that creates a new snapshot NFT +The `DigitalAsset` type comes from `@metaplex-foundation/mpl-token-metadata` +that have metadata, off-chain metadata, collection data, plugins (including +Attributes), and more. + ```tsx export interface NFTContextState { metaplex: Metaplex | null; // Holds the metaplex object that we use to call `fetch` and `create` on. publicKey: PublicKey | null; // The public key of the authorized wallet isLoading: boolean; // Loading state - loadedNFTs: (Nft | Sft | SftWithToken | NftWithToken)[] | null; // Array of loaded NFTs that contain metadata - nftOfTheDay: (Nft | Sft | SftWithToken | NftWithToken) | null; // The NFT snapshot created on the current day + loadedNFTs: DigitalAsset[] | null; // Array of loaded NFTs that contain metadata + nftOfTheDay: DigitalAsset | null; // The NFT snapshot created on the current day connect: () => void; // Connects (and authorizes) us to the Devnet-enabled wallet fetchNFTs: () => void; // Fetches the NFTs using the `metaplex` object createNFT: (name: string, description: string, fileUri: string) => void; // Creates the NFT @@ -943,201 +979,220 @@ through the code for each of them and then show you the entire file at the end: }; ``` -2. `fetchNFTs` - This function will fetch the NFTs using Metaplex: - - ```tsx - const fetchNFTs = async () => { - if (!metaplex || !account || isLoading) return; +2. `fetchNFTs` - This function will fetch the NFTs using + `fetchAllDigitalAssetByCreator`: - setIsLoading(true); - - try { - const nfts = await metaplex.nfts().findAllByCreator({ - creator: account.publicKey, - }); - - const loadedNFTs = await Promise.all( - nfts.map(nft => { - return metaplex.nfts().load({ metadata: nft as Metadata }); - }), - ); - setLoadedNFTs(loadedNFTs); - - // Check if we already took a snapshot today - const nftOfTheDayIndex = loadedNFTs.findIndex(nft => { - return formatDate(new Date(Date.now())) === nft.name; - }); - - if (nftOfTheDayIndex !== -1) { - setNftOfTheDay(loadedNFTs[nftOfTheDayIndex]); - } - } catch (error) { - console.log(error); - } finally { - setIsLoading(false); - } - }; - ``` - -3. `createNFT` - This function will upload a file to NFT.Storage, and then use - Metaplex to create and mint an NFT to your wallet. This comes in three parts, - uploading the image, uploading the metadata and then minting the NFT. +```tsx +const fetchNFTs = useCallback(async () => { + if (!umi || !account || isLoading) return; + setIsLoading(true); + try { + const creatorPublicKey = fromWeb3JsPublicKey(account.publicKey); + const nfts = await fetchAllDigitalAssetByCreator(umi, creatorPublicKey); + setLoadedNFTs(nfts); + } catch (error) { + console.error("Failed to fetch NFTs:", error); + } finally { + setIsLoading(false); + } +}, [umi, account, isLoading]); +``` - To upload to NFT.Storage you just make a POST with your API key and the - image/metadata as the body. +3. `createNFT` - This function will upload a file to Pinata Cloud, and then use + `createNft` function from to create and mint an NFT to your wallet. This + comes in three parts, uploading the image, uploading the metadata and then + minting the NFT. To upload to Pinata Cloud, you can use their + [HTTP API endpoint](https://docs.pinata.cloud/api-reference/endpoint/upload-a-file), + allowing interaction with their API for file uploads. We'll create two helper functions for uploading the image and metadata separately, then tie them together into a single `createNFT` function: - ```tsx - // https://nft.storage/api-docs/ - const uploadImage = async (fileUri: string): Promise => { - const imageBytesInBase64: string = await RNFetchBlob.fs.readFile( - fileUri, - "base64", - ); - const bytes = Buffer.from(imageBytesInBase64, "base64"); - - const response = await fetch("https://api.nft.storage/upload", { - method: "POST", - headers: { - Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_STORAGE_API}`, - "Content-Type": "image/jpg", - }, - body: bytes, - }); +```tsx +const ipfsPrefix = `https://${process.env.EXPO_PUBLIC_NFT_PINATA_GATEWAY_URL}/ipfs/`; +async function uploadImageFromURI(fileUri: string) { + try { + const form = new FormData(); + const randomFileName = `image_${Date.now()}_${Math.floor(Math.random() * 10000)}.jpg`; + + form.append("file", { + uri: Platform.OS === "android" ? fileUri : fileUri.replace("file://", ""), + type: "image/jpeg", // Adjust the type as necessary + name: randomFileName, // Adjust the name as necessary + }); - const data = await response.json(); - const cid = data.value.cid; + const options = { + method: "POST", + headers: { + Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_PINATA_JWT}`, + "Content-Type": "multipart/form-data", + }, + body: form, + }; - return cid as string; - }; + const response = await fetch( + "https://api.pinata.cloud/pinning/pinFileToIPFS", + options, + ); + const responseJson = await response.json(); + return responseJson; + } catch (error) { + console.error("Upload failed:", error); + } finally { + console.log("Upload process completed."); + } +} - const uploadMetadata = async ( - name: string, - description: string, - imageCID: string, - ): Promise => { - const response = await fetch("https://api.nft.storage/upload", { - method: "POST", - headers: { - Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_STORAGE_API}`, - }, - body: JSON.stringify({ - name, - description, - image: `https://ipfs.io/ipfs/${imageCID}`, - }), - }); +async function uploadMetadataJson( + name: string, + description: string, + imageCID: string, +) { + const randomFileName = `metadata_${Date.now()}_${Math.floor(Math.random() * 10000)}.json`; + const data = JSON.stringify({ + pinataContent: { + name, + description, + imageCID, + }, + pinataMetadata: { + name: randomFileName, + }, + }); + const response = await fetch( + "https://api.pinata.cloud/pinning/pinJSONToIPFS", + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_PINATA_JWT}`, + }, + body: data, + }, + ); + const responseBody = await response.json(); - const data = await response.json(); - const cid = data.value.cid; + return responseBody; +} - return cid; - }; - ``` +const uploadImage = useCallback(async (fileUri: string): Promise => { + const upload = await uploadImageFromURI(fileUri); + return upload.IpfsHash; +}, []); - Minting the NFT after the image and metadata have been uploaded is as simple - as calling `metaplex.nfts().create(...)`. Below shows the `createNFT` - function tying everything together: +const uploadMetadata = useCallback( + async ( + name: string, + description: string, + imageCID: string, + ): Promise => { + const uploadResponse = await uploadMetadataJson( + name, + description, + imageCID, + ); + return uploadResponse.IpfsHash; + }, + [], +); +``` - ```tsx - const createNFT = async ( - name: string, - description: string, - fileUri: string, - ) => { - if (!metaplex || !account || isLoading) return; +Minting the NFT after the image and metadata have been uploaded is as simple as +calling `createNft` from `@metaplex-foundation/mpl-token-metadata`. Below shows +the `createNFT` function tying everything together: - setIsLoading(true); - try { - const imageCID = await uploadImage(fileUri); - const metadataCID = await uploadMetadata(name, description, imageCID); - - const nft = await metaplex.nfts().create({ - uri: `https://ipfs.io/ipfs/${metadataCID}`, - name: name, - sellerFeeBasisPoints: 0, - }); - - setNftOfTheDay(nft.nft); - } catch (error) { - console.log(error); - } finally { - setIsLoading(false); - } - }; - ``` +```tsx +const createNFT = useCallback( + async (name: string, description: string, fileUri: string) => { + if (!umi || !account || isLoading) return; + setIsLoading(true); + try { + console.log(`Creating NFT...`); + const imageCID = await uploadImage(fileUri); + const metadataCID = await uploadMetadata(name, description, imageCID); + const mint = generateSigner(umi); + const transaction = createNft(umi, { + mint, + name, + uri: ipfsPrefix + metadataCID, + sellerFeeBasisPoints: percentAmount(0), + }); + await transaction.sendAndConfirm(umi); + const createdNft = await fetchDigitalAsset(umi, mint.publicKey); + setNftOfTheDay(createdNft); + } catch (error) { + console.error("Failed to create NFT:", error); + } finally { + setIsLoading(false); + } + }, + [umi, account, isLoading, uploadImage, uploadMetadata], +); +``` We'll put all of the above into the `NFTProvider.tsx` file. All together, this looks as follows: ```tsx import "react-native-url-polyfill/auto"; -import React, { ReactNode, createContext, useContext, useState } from "react"; import { - Metaplex, + DigitalAsset, + createNft, + fetchAllDigitalAssetByCreator, + fetchDigitalAsset, +} from "@metaplex-foundation/mpl-token-metadata"; +import { PublicKey, - Metadata, - Nft, - Sft, - SftWithToken, - NftWithToken, -} from "@metaplex-foundation/js"; -import { useConnection } from "./ConnectionProvider"; -import { Connection, clusterApiUrl } from "@solana/web3.js"; -import { transact } from "@solana-mobile/mobile-wallet-adapter-protocol"; -import { Account, useAuthorization } from "./AuthProvider"; -import RNFetchBlob from "rn-fetch-blob"; -import { useMetaplex } from "./MetaplexProvider"; + Umi, + generateSigner, + percentAmount, +} from "@metaplex-foundation/umi"; +import { fromWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters"; +import { clusterApiUrl, PublicKey as solanaPublicKey } from "@solana/web3.js"; +import React, { + ReactNode, + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { useUmi } from "./UmiProvider"; +import { useMobileWallet } from "../utils/useMobileWallet"; +import { Account, useAuthorization } from "./AuthorizationProvider"; +import { Platform } from "react-native"; export interface NFTProviderProps { children: ReactNode; } export interface NFTContextState { - metaplex: Metaplex | null; - publicKey: PublicKey | null; - isLoading: boolean; - loadedNFTs: (Nft | Sft | SftWithToken | NftWithToken)[] | null; - nftOfTheDay: (Nft | Sft | SftWithToken | NftWithToken) | null; - connect: () => void; - fetchNFTs: () => void; - createNFT: (name: string, description: string, fileUri: string) => void; + umi: Umi | null; // Holds the Umi object that we use to call `fetch` and `create` on. + publicKey: PublicKey | null; // The public key of the authorized wallet + isLoading: boolean; // Loading state + loadedNFTs: DigitalAsset[] | null; // Array of loaded NFTs that contain metadata + nftOfTheDay: DigitalAsset | null; // The NFT snapshot created on the current day + connect: () => void; // Connects (and authorizes) us to the Devnet-enabled wallet + fetchNFTs: () => void; // Fetches the NFTs using the `metaplex` object + createNFT: (name: string, description: string, fileUri: string) => void; // Creates the NFT } -const DEFAULT_NFT_CONTEXT_STATE: NFTContextState = { - metaplex: new Metaplex(new Connection(clusterApiUrl("devnet"))), - publicKey: null, - isLoading: false, - loadedNFTs: null, - nftOfTheDay: null, - connect: () => PublicKey.default, - fetchNFTs: () => {}, - createNFT: (name: string, description: string, fileUri: string) => {}, -}; - -const NFTContext = createContext(DEFAULT_NFT_CONTEXT_STATE); - export function formatDate(date: Date) { return `${date.getDate()}.${date.getMonth()}.${date.getFullYear()}`; } +const NFTContext = createContext(null); + export function NFTProvider(props: NFTProviderProps) { - const { children } = props; - const { connection } = useConnection(); - const { authorizeSession } = useAuthorization(); + const ipfsPrefix = `https://${process.env.EXPO_PUBLIC_NFT_PINATA_GATEWAY_URL}/ipfs/`; const [account, setAccount] = useState(null); + const [nftOfTheDay, setNftOfTheDay] = useState(null); + const [loadedNFTs, setLoadedNFTs] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [nftOfTheDay, setNftOfTheDay] = useState< - (Nft | Sft | SftWithToken | NftWithToken) | null - >(null); - const [loadedNFTs, setLoadedNFTs] = useState< - (Nft | Sft | SftWithToken | NftWithToken)[] | null - >(null); - - const { metaplex } = useMetaplex(connection, account, authorizeSession); - + const umi = useUmi(); + const { children } = props; const connect = () => { if (isLoading) return; @@ -1149,118 +1204,151 @@ export function NFTProvider(props: NFTProviderProps) { setIsLoading(false); }); }; - - const fetchNFTs = async () => { - if (!metaplex || !account || isLoading) return; - - setIsLoading(true); - + async function uploadImageFromURI(fileUri: string) { try { - const nfts = await metaplex.nfts().findAllByCreator({ - creator: account.publicKey, + const form = new FormData(); + const randomFileName = `image_${Date.now()}_${Math.floor(Math.random() * 10000)}.jpg`; + + // In React Native, especially when working with form data and files, you may need to send files using an object that contains a URI (file path), especially on Android and iOS platforms. However, this structure may not be recognized by TypeScript's strict type checking + // @ts-ignore + form.append("file", { + uri: + Platform.OS === "android" ? fileUri : fileUri.replace("file://", ""), + type: "image/jpeg", // Adjust the type as necessary + name: randomFileName, // Adjust the name as necessary }); - const loadedNFTs = await Promise.all( - nfts.map(nft => { - return metaplex.nfts().load({ metadata: nft as Metadata }); - }), + const options = { + method: "POST", + headers: { + Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_PINATA_JWT}`, + "Content-Type": "multipart/form-data", + }, + body: form, + }; + + const response = await fetch( + "https://api.pinata.cloud/pinning/pinFileToIPFS", + options, ); - setLoadedNFTs(loadedNFTs); - - // Check if we already took a snapshot today - const nftOfTheDayIndex = loadedNFTs.findIndex(nft => { - return formatDate(new Date(Date.now())) === nft.name; - }); + const responseJson = await response.json(); + console.log(responseJson.IpfsHash); - if (nftOfTheDayIndex !== -1) { - setNftOfTheDay(loadedNFTs[nftOfTheDayIndex]); - } + return responseJson; } catch (error) { - console.log(error); + console.error("Upload failed:", error); } finally { - setIsLoading(false); + console.log("Upload process completed."); } - }; - - // https://nft.storage/api-docs/ - const uploadImage = async (fileUri: string): Promise => { - const imageBytesInBase64: string = await RNFetchBlob.fs.readFile( - fileUri, - "base64", - ); - const bytes = Buffer.from(imageBytesInBase64, "base64"); - - const response = await fetch("https://api.nft.storage/upload", { - method: "POST", - headers: { - Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_STORAGE_API}`, - "Content-Type": "image/jpg", - }, - body: bytes, - }); - - const data = await response.json(); - const cid = data.value.cid; - - return cid as string; - }; + } - const uploadMetadata = async ( - name: string, - description: string, - imageCID: string, - ): Promise => { - const response = await fetch("https://api.nft.storage/upload", { - method: "POST", - headers: { - Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_STORAGE_API}`, - }, - body: JSON.stringify({ + async function uploadMetadataJson( + name = "Solanify", + description = "A truly sweet NFT of your day.", + imageCID = "bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4", + ) { + const randomFileName = `metadata_${Date.now()}_${Math.floor(Math.random() * 10000)}.json`; + const data = JSON.stringify({ + pinataContent: { name, description, - image: `https://ipfs.io/ipfs/${imageCID}`, - }), + imageCID, + }, + pinataMetadata: { + name: randomFileName, + }, }); + const response = await fetch( + "https://api.pinata.cloud/pinning/pinJSONToIPFS", + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + Accept: "application/json", + Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_PINATA_JWT}`, + }, + body: data, + }, + ); + const responseBody = await response.json(); - const data = await response.json(); - const cid = data.value.cid; - - return cid; - }; - - const createNFT = async ( - name: string, - description: string, - fileUri: string, - ) => { - if (!metaplex || !account || isLoading) return; + return responseBody; + } + const fetchNFTs = useCallback(async () => { + if (!umi || !account || isLoading) return; setIsLoading(true); try { - const imageCID = await uploadImage(fileUri); - const metadataCID = await uploadMetadata(name, description, imageCID); - - const nft = await metaplex.nfts().create({ - uri: `https://ipfs.io/ipfs/${metadataCID}`, - name: name, - sellerFeeBasisPoints: 0, - }); - - setNftOfTheDay(nft.nft); + const creatorPublicKey = fromWeb3JsPublicKey(account.publicKey); + const nfts = await fetchAllDigitalAssetByCreator(umi, creatorPublicKey); + setLoadedNFTs(nfts); } catch (error) { - console.log(error); + console.error("Failed to fetch NFTs:", error); } finally { setIsLoading(false); } - }; + }, [umi, account, isLoading]); + + const uploadImage = useCallback(async (fileUri: string): Promise => { + const upload = await uploadImageFromURI(fileUri); + return upload.IpfsHash; + }, []); + + const uploadMetadata = useCallback( + async ( + name: string, + description: string, + imageCID: string, + ): Promise => { + const uploadResponse = await uploadMetadataJson( + name, + description, + imageCID, + ); + return uploadResponse.IpfsHash; + }, + [], + ); - const publicKey = account?.publicKey ?? null; + const createNFT = useCallback( + async (name: string, description: string, fileUri: string) => { + if (!umi || !account || isLoading) return; + setIsLoading(true); + try { + console.log(`Creating NFT...`); + const imageCID = await uploadImage(fileUri); + const metadataCID = await uploadMetadata(name, description, imageCID); + const mint = generateSigner(umi); + const transaction = createNft(umi, { + mint, + name, + uri: ipfsPrefix + metadataCID, + sellerFeeBasisPoints: percentAmount(0), + }); + await transaction.sendAndConfirm(umi); + const createdNft = await fetchDigitalAsset(umi, mint.publicKey); + setNftOfTheDay(createdNft); + } catch (error) { + console.error("Failed to create NFT:", error); + } finally { + setIsLoading(false); + } + }, + [umi, account, isLoading, uploadImage, uploadMetadata], + ); - const state = { + const publicKey = useMemo( + () => + account?.publicKey + ? fromWeb3JsPublicKey(account.publicKey as solanaPublicKey) + : null, + [account], + ); + + const state: NFTContextState = { isLoading, - account, publicKey, - metaplex, + umi, nftOfTheDay, loadedNFTs, connect, @@ -1271,7 +1359,13 @@ export function NFTProvider(props: NFTProviderProps) { return {children}; } -export const useNFT = (): NFTContextState => useContext(NFTContext); +export const useNFT = (): NFTContextState => { + const context = useContext(NFTContext); + if (!context) { + throw new Error("useNFT must be used within an NFTProvider"); + } + return context; +}; ``` #### 2. Main Screen @@ -1401,50 +1495,90 @@ export function MainScreen() { const [previousImages, setPreviousImages] = React.useState(DEFAULT_IMAGES); const todaysDate = new Date(Date.now()); + const ipfsPrefix = `https://${process.env.EXPO_PUBLIC_NFT_PINATA_GATEWAY_URL}/ipfs/`; + type NftMetaResponse = { + name: string; + description: string; + imageCID: string; + }; + const fetchMetadata = async (uri: string) => { + try { + const response = await fetch(uri); + const metadata = await response.json(); + return metadata as NftMetaResponse; + } catch (error) { + console.error("Error fetching metadata:", error); + return null; + } + }; useEffect(() => { if (!loadedNFTs) return; - const loadedSnapshots = loadedNFTs.map(loadedNft => { - if (!loadedNft.json) return null; - if (!loadedNft.json.name) return null; - if (!loadedNft.json.description) return null; - if (!loadedNft.json.image) return null; + const loadSnapshots = async () => { + const loadedSnapshots = await Promise.all( + loadedNFTs.map(async loadedNft => { + if (!loadedNft.metadata.name) return null; + if (!loadedNft.metadata.uri) return null; - const uri = loadedNft.json.image; - const unixTime = Number(loadedNft.json.description); + const metadata = await fetchMetadata(loadedNft.metadata.uri); + if (!metadata) return null; - if (!uri) return null; - if (isNaN(unixTime)) return null; + const { imageCID, description } = metadata; + if (!imageCID || !description) return null; - return { - uri: loadedNft.json.image, - date: new Date(unixTime), - } as NFTSnapshot; - }); + const unixTime = Number(description); + if (isNaN(unixTime)) return null; - // Filter out null values - const cleanedSnapshots = loadedSnapshots.filter(loadedSnapshot => { - return loadedSnapshot !== null; - }) as NFTSnapshot[]; + return { + uri: ipfsPrefix + imageCID, + date: new Date(unixTime), + } as NFTSnapshot; + }), + ); - // Sort by date - cleanedSnapshots.sort((a, b) => { - return b.date.getTime() - a.date.getTime(); - }); + // Filter out null values + const cleanedSnapshots = loadedSnapshots.filter( + (snapshot): snapshot is NFTSnapshot => snapshot !== null, + ); - setPreviousImages(cleanedSnapshots as NFTSnapshot[]); + // Sort by date + cleanedSnapshots.sort((a, b) => b.date.getTime() - a.date.getTime()); + + setPreviousImages(cleanedSnapshots); + }; + + loadSnapshots(); }, [loadedNFTs]); useEffect(() => { if (!nftOfTheDay) return; - setCurrentImage({ - uri: nftOfTheDay.json?.image ?? "", - date: todaysDate, - }); - }, [nftOfTheDay]); + const fetchNftOfTheDayMetadata = async () => { + try { + if (!nftOfTheDay.metadata.uri) { + console.error("No metadata URI found for nftOfTheDay"); + return; + } + + const response = await fetchMetadata(nftOfTheDay.metadata.uri); + + if (!response?.imageCID) { + console.error("No image found in nftOfTheDay metadata"); + return; + } + + setCurrentImage({ + uri: ipfsPrefix + response.imageCID, + date: todaysDate, + }); + } catch (error) { + console.error("Error fetching nftOfTheDay metadata:", error); + } + }; + fetchNftOfTheDayMetadata(); + }, [nftOfTheDay, todaysDate]); const mintNFT = async () => { const result = await ImagePicker.launchCameraAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, @@ -1533,7 +1667,7 @@ approve the app. Fetch all of the NFTs by tapping `Fetch NFTs`. Lastly, tap Congratulations! That was not an easy or quick lab. You're doing great if you've made it this far. If you run into any issues, please go back through the lab and/or reference the final solution code on the -[`main` branch in Github](https://github.com/Unboxed-Software/solana-advance-mobile). +[`main` branch in Github](https://github.com/solana-developers/mobile-apps-with-expo). ## Challenge From 074a7e086551c4339ebcaa6f592b6aa327c44ee3 Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Wed, 2 Oct 2024 13:48:40 +0530 Subject: [PATCH 19/54] Tokens NFTs - Updated Token Program Advanced lesson (#512) * Updated content * Updated content * Updated content * Update content/courses/tokens-and-nfts/token-program-advanced.md * Update content/courses/tokens-and-nfts/token-program-advanced.md * Update content/courses/tokens-and-nfts/token-program-advanced.md * Update content/courses/tokens-and-nfts/token-program-advanced.md --------- Co-authored-by: Mike MacCana --- .../tokens-and-nfts/token-program-advanced.md | 406 +++++++++++------- public/assets/courses/unboxed/burn-tokens.png | Bin 0 -> 162922 bytes .../assets/courses/unboxed/delegate-token.png | Bin 0 -> 178777 bytes .../courses/unboxed/revoke-approve-tokens.png | Bin 0 -> 178332 bytes 4 files changed, 258 insertions(+), 148 deletions(-) create mode 100644 public/assets/courses/unboxed/burn-tokens.png create mode 100644 public/assets/courses/unboxed/delegate-token.png create mode 100644 public/assets/courses/unboxed/revoke-approve-tokens.png diff --git a/content/courses/tokens-and-nfts/token-program-advanced.md b/content/courses/tokens-and-nfts/token-program-advanced.md index 16c827c38..1fcff33e3 100644 --- a/content/courses/tokens-and-nfts/token-program-advanced.md +++ b/content/courses/tokens-and-nfts/token-program-advanced.md @@ -1,5 +1,5 @@ --- -title: Token burning and Delegation +title: Token Burning and Delegation objectives: - Understand why and how to burn tokens - Allow a token holder to allocate a limited amount of tokens to another @@ -8,20 +8,31 @@ description: "How to burn tokens, and approve/revoke token delegations on Solana." --- +### Summary + +- **Burning tokens** reduces the total supply of a token by removing them + from circulation. +- **Approving a delegate**, allows another account to transfer or burn a specified amount of tokens from a token account while retaining original account ownership. +- **Revoking a delegate**, removes their authority to act on behalf of the token account owner. +- Each of these operations is facilitated through the `spl-token` library, + utilizing specific functions for each action. + ### Lesson -Finally, we'll cover burning tokens, and delegation. You may not use these in -your own application, so if you're really excited about NFTs, feel free to skip -to +In this lesson, we'll cover burning tokens and delegation. You may not have a need for these +in your own application, so if you're more interested in NFTs, feel free to skip +ahead to [creating NFTs with Metaplex](/content/courses/tokens-and-nfts/nfts-with-metaplex.md)! -### Burn Tokens +#### Burn Tokens Burning tokens is the process of decreasing the token supply of a given token mint. Burning tokens removes the tokens from the given token account and from broader circulation. -To burn tokens using the `spl-token` library, use the `burn` function. +To burn tokens using the `spl-token` library, use the +[`burn()`](https://solana-labs.github.io/solana-program-library/token/js/functions/burn.html#burn) +function. ```typescript import { burn } from "@solana/spl-token"; @@ -38,20 +49,22 @@ const transactionSignature = await burn( ); ``` -The `burn` function requires the following arguments: +The `burn()` function requires the following arguments: -- `connection` - the JSON-RPC connection to the cluster -- `payer` - the account of the payer for the transaction -- `account` - the token account to burn tokens from -- `mint` - the token mint associated with the token account -- `owner` - the account of the owner of the token account -- `amount` - the amount of tokens to burn +- `connection`: JSON-RPC connection to the cluster. +- `payer`: The account responsible for paying transaction fees. +- `account`: The token account from which tokens will be burned. +- `mint`: The token mint associated with the token account. +- `owner`: The owner of the token account. +- `amount`: The number of tokens to burn. -Under the hood, the `burn` function creates a transaction with instructions -obtained from the `createBurnInstruction` function: +Under the hood, the `burn()` function creates a transaction using the +instruction obtained from +[`createBurnInstruction()`](https://solana-labs.github.io/solana-program-library/token/js/functions/createBurnInstruction.html#createBurnInstruction) +function. ```typescript -import { PublicKey, Transaction } from "@solana/web3"; +import { PublicKey, Transaction } from "@solana/web3.js"; import { createBurnInstruction } from "@solana/spl-token"; async function buildBurnTransaction( @@ -68,16 +81,16 @@ async function buildBurnTransaction( } ``` -### Approve Delegate +#### Approve Delegate Approving a delegate is the process of authorizing another account to transfer -or burn tokens from a token account. When using a delegate, the authority over -the token account remains with the original owner. The maximum amount of tokens -a delegate may transfer or burn is specified at the time the owner of the token -account approves the delegate. Note that there can only be one delegate account -associated with a token account at any given time. +or burn tokens from a token account. The authority over the token account +remains with the original owner. The maximum number of tokens a delegate can +transfer or burn is defined when the owner approves the delegate. Only one +delegate can be associated with a token account at a time. -To approve a delegate using the `spl-token` library, you use the `approve` +To approve a delegate using the `spl-token` library, use the +[`approve()`](https://solana-labs.github.io/solana-program-library/token/js/functions/approve.html#approve) function. ```typescript @@ -91,21 +104,23 @@ const transactionSignature = await approve( ); ``` -The `approve` function returns a `TransactionSignature` that can be viewed on -Solana Explorer. The `approve` function requires the following arguments: +The `approve()` function returns a `TransactionSignature` that can be viewed on +Solana Explorer. It requires the following arguments: -- `connection` - the JSON-RPC connection to the cluster -- `payer` - the account of the payer for the transaction -- `account` - the token account to delegate tokens from -- `delegate` - the account the owner is authorizing to transfer or burn tokens -- `owner` - the account of the owner of the token account -- `amount` - the maximum number of tokens the delegate may transfer or burn +- `connection`: The JSON-RPC connection to the cluster. +- `payer`: The account of the payer for the transaction. +- `account`: The token account to delegate tokens from. +- `delegate`: The account authorized to transfer or burn tokens. +- `owner`: The account of the owner of the token account. +- `amount`: The maximum number of tokens the delegate can transfer or burn. -Under the hood, the `approve` function creates a transaction with instructions -obtained from the `createApproveInstruction` function: +Under the hood, the `approve()` function creates a transaction with instructions +obtained from the +[`createApproveInstruction()`](https://solana-labs.github.io/solana-program-library/token/js/functions/createApproveInstruction.html#createApproveInstruction) +function. ```typescript -import { PublicKey, Transaction } from "@solana/web3"; +import { PublicKey, Transaction } from "@solana/web3.js"; import { createApproveInstruction } from "@solana/spl-token"; async function buildApproveTransaction( @@ -122,14 +137,15 @@ async function buildApproveTransaction( } ``` -### Revoke Delegate +#### Revoke Delegate -A previously approved delegate for a token account can be later revoked. Once a -delegate is revoked, the delegate can no longer transfer tokens from the owner's -token account. Any remaining amount left untransferred from the previously -approved amount can no longer be transferred by the delegate. +A previously approved delegate for a token account can be revoked. Once revoked, +the delegate can no longer transfer tokens from the owner's token account. Any +untransferred amount from the previously approved tokens will no longer be +accessible by the delegate. -To revoke a delegate using the `spl-token` library, you use the `revoke` +To revoke a delegate using the `spl-token` library, use the +[`revoke()`](https://solana-labs.github.io/solana-program-library/token/js/functions/revoke.html#revoke) function. ```typescript @@ -138,20 +154,22 @@ import { revoke } from "@solana/spl-token"; const transactionSignature = await revoke(connection, payer, account, owner); ``` -The `revoke` function returns a `TransactionSignature` that can be viewed on -Solana Explorer. The `revoke` function requires the following arguments: +The `revoke()` function returns a `TransactionSignature` that can be viewed on +Solana Explorer. This function requires the following arguments: -- `connection` - the JSON-RPC connection to the cluster -- `payer` - the account of the payer for the transaction -- `account` - the token account to revoke the delegate authority from -- `owner` - the account of the owner of the token account +- `connection`: The JSON-RPC connection to the cluster. +- `payer`: The account responsible for paying the transaction fees. +- `account`: The token account from which to revoke the delegate authority. +- `owner`: The account of the owner of the token account. -Under the hood, the `revoke` function creates a transaction with instructions -obtained from the `createRevokeInstruction` function: +Under the hood, the `revoke()` function generates a transaction using the +instructions from the +[`createRevokeInstruction()`](https://solana-labs.github.io/solana-program-library/token/js/functions/createRevokeInstruction.html#createRevokeInstruction) +function: ```typescript -import { PublicKey, Transaction } from "@solana/web3"; -import { revoke } from "@solana/spl-token"; +import { PublicKey, Transaction } from "@solana/web3.js"; +import { createRevokeInstruction } from "@solana/spl-token"; async function buildRevokeTransaction( account: PublicKey, @@ -167,114 +185,187 @@ async function buildRevokeTransaction( ### Lab -This lab extends the lab from the -[previous chapter](/content/courses/tokens-and-nfts/token-program.md). +This lab extends the concepts covered in the previous lesson on the +[Token Program](/content/courses/tokens-and-nfts/token-program.md). -#### 1. Delegating tokens +#### 1. Delegating Tokens -Let's use `approve` from `spl-token` to authorize a delegate to transfer or burn -up to 50 tokens from our token account. +We will use the `approve()` function from the `spl-token` library to authorize a +delegate to transfer or burn up to 50 tokens from our token account. -Just like -[Transferring Tokens](/content/courses/tokens-and-nfts/token-program.md) in the -previous lab, you can -[add a second account on devnet](/content/courses/intro-to-solana/intro-to-cryptography.md) -if you like, or find a friend who has a devnet account! +Similar to the process of +[Transferring Tokens](/content/courses/tokens-and-nfts/token-program.md#transferring-tokens) +in the previous lab, you can +[add a second account on Devnet](/content/courses/intro-to-solana/intro-to-cryptography.md) +if desired or collaborate with a friend who has a Devnet account. -Create a new file `delegate-tokens.ts` +Create a new file named `delegate-tokens.ts`. For this example, we are using the +System Program ID as a delegate for demonstration, but you can use an actual +address that you want to delegate. -```typescript +```typescript filename="delegate-tokens.ts" import "dotenv/config"; import { getExplorerLink, getKeypairFromEnvironment, } from "@solana-developers/helpers"; -import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js"; import { - approve, - getOrCreateAssociatedTokenAccount, - revoke, -} from "@solana/spl-token"; - -const connection = new Connection(clusterApiUrl("devnet")); - + Connection, + PublicKey, + clusterApiUrl, + SystemProgram, +} from "@solana/web3.js"; +import { approve, getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; + +const DEVNET_URL = clusterApiUrl("devnet"); +const TOKEN_DECIMALS = 2; +const DELEGATE_AMOUNT = 50; +const MINOR_UNITS_PER_MAJOR_UNITS = 10 ** TOKEN_DECIMALS; + +// Initialize connection and load user keypair +const connection = new Connection(DEVNET_URL); const user = getKeypairFromEnvironment("SECRET_KEY"); -console.log( - `🔑 Loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}`, -); +console.log(`🔑 Loaded keypair. Public key: ${user.publicKey.toBase58()}`); -// Add the delegate public key here. -const delegate = new PublicKey("YOUR_DELEGATE_HERE"); +// Replace this with your actual address +// For this example, we will be using System Program's ID as a delegate +const delegatePublicKey = new PublicKey(SystemProgram.programId); -// Substitute in your token mint account -const tokenMintAccount = new PublicKey("YOUR_TOKEN_MINT_ADDRESS_HERE"); +// Substitute your token mint address +const tokenMintAddress = new PublicKey("YOUR_TOKEN_MINT_ADDRESS_HERE"); -// Get or create the source and destination token accounts to store this token -const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( - connection, - user, - tokenMintAccount, - user.publicKey, -); - -// Our token has two decimal places -const MINOR_UNITS_PER_MAJOR_UNITS = Math.pow(10, 2); +try { + // Get or create the user's token account + const userTokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + user, + tokenMintAddress, + user.publicKey, + ); -const approveTransactionSignature = await approve( - connection, - user, - sourceTokenAccount.address, - delegate, - user.publicKey, - 50 * MINOR_UNITS_PER_MAJOR_UNITS, -); + // Approve the delegate + const approveTransactionSignature = await approve( + connection, + user, + userTokenAccount.address, + delegatePublicKey, + user.publicKey, + DELEGATE_AMOUNT * MINOR_UNITS_PER_MAJOR_UNITS, + ); -console.log( - `Approve Delegate Transaction: ${getExplorerLink( + const explorerLink = getExplorerLink( "transaction", approveTransactionSignature, "devnet", - )}`, -); + ); + + console.log(`✅ Delegate approved. Transaction: ${explorerLink}`); +} catch (error) { + console.error( + `Error: ${error instanceof Error ? error.message : String(error)}`, + ); +} +``` + +Replace `YOUR_TOKEN_MINT_ADDRESS_HERE` with your token mint address obtained +from the previous lesson +[Token Program](/content/courses/tokens-and-nfts/token-program.md#create-the-token-mint). + +Run the script using `npx esrun delegate-tokens.ts`. You should see: + +```bash +🔑 Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM +✅ Delegate approved. Transaction: https://explorer.solana.com/tx/21tX6L7zk5tkHeoD7V1JYYW25VAWRfQrJPnxDcMXw94yuFbHxX4UZEgS6k6co9dBWe7PqFoMoWEVfbVA92Dk4xsQ?cluster=devnet ``` +Open the Explorer link, you will see the ‌approval information. + +![Delegate Tokens](/public/assets/courses/unboxed/delegate-token.png) + #### 2. Revoke Delegate -Lets revoke the `delegate` using the `spl-token` library's `revoke` function. +Let's revoke the `delegate` using the `spl-token` library's `revoke()` function. -Revoke will set delegate for the token account to null and reset the delegated -amount to 0. +Revoke will set the delegate for the token account to null and reset the +delegated amount to 0. -All we will need for this function is the token account and user. After the +Create a new file `revoke-approve-tokens.ts`. -```typescript -const revokeTransactionSignature = await revoke( - connection, - user, - sourceTokenAccount.address, - user.publicKey, -); +```typescript filename="revoke-approve-tokens.ts" +import "dotenv/config"; +import { + getExplorerLink, + getKeypairFromEnvironment, +} from "@solana-developers/helpers"; +import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js"; +import { revoke, getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; -console.log( - `Revoke Delegate Transaction: ${getExplorerLink( +const DEVNET_URL = clusterApiUrl("devnet"); +// Substitute your token mint address +const TOKEN_MINT_ADDRESS = "YOUR_TOKEN_MINT_ADDRESS_HERE"; + +const connection = new Connection(DEVNET_URL); +const user = getKeypairFromEnvironment("SECRET_KEY"); + +console.log(`🔑 Loaded keypair. Public key: ${user.publicKey.toBase58()}`); + +try { + const tokenMintAddress = new PublicKey(TOKEN_MINT_ADDRESS); + + const userTokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + user, + tokenMintAddress, + user.publicKey, + ); + + const revokeTransactionSignature = await revoke( + connection, + user, + userTokenAccount.address, + user.publicKey, + ); + + const explorerLink = getExplorerLink( "transaction", revokeTransactionSignature, "devnet", - )}`, -); + ); + + console.log(`✅ Revoke Delegate Transaction: ${explorerLink}`); +} catch (error) { + console.error( + `Error: ${error instanceof Error ? error.message : String(error)}`, + ); +} +``` + +Replace `YOUR_TOKEN_MINT_ADDRESS_HERE` with your mint token address obtained +from the previous lesson +[Token Program](/content/courses/tokens-and-nfts/token-program.md#create-the-token-mint). + +Run the script using `npx esrun revoke-approve-tokens.ts`. You should see: + +```bash +🔑 Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM +✅ Revoke Delegate Transaction: https://explorer.solana.com/tx/YTc2Vd41SiGiHf3iEPkBH3y164fMbV2TSH2hbe7WypT6K6Q2b3f31ryFWhypmBK2tXmvGYjXeYbuwxHeJvnZZX8?cluster=devnet ``` +Open the Explorer link, you will see the revoke information. + +![Revoke Approve Tokens](/public/assets/courses/unboxed/revoke-approve-tokens.png) + #### 3. Burn Tokens Finally, let's remove some tokens from circulation by burning them. -Use the `spl-token` library's `burn` function to remove half of your tokens from -circulation. +Use the `spl-token` library's `burn()` function to remove half of your tokens +from circulation. Now, call this function to burn 5 of the user's tokens. -Now call this new function in `main` to burn 25 of the user's tokens. +Create a new file `burn-tokens.ts`. -```typescript +```typescript filename="burn-tokens.ts" import "dotenv/config"; import { getExplorerLink, @@ -283,51 +374,70 @@ import { import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js"; import { getOrCreateAssociatedTokenAccount, burn } from "@solana/spl-token"; -const connection = new Connection(clusterApiUrl("devnet")); +const DEVNET_URL = clusterApiUrl("devnet"); +const TOKEN_DECIMALS = 2; +const BURN_AMOUNT = 5; +// Substitute your token mint address +const TOKEN_MINT_ADDRESS = "YOUR_TOKEN_MINT_ADDRESS_HERE"; +const connection = new Connection(DEVNET_URL); const user = getKeypairFromEnvironment("SECRET_KEY"); -console.log( - `🔑 Loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}`, -); +console.log(`🔑 Loaded keypair. Public key: ${user.publicKey.toBase58()}`); -// Substitute in your token mint account -const tokenMintAccount = new PublicKey("YOUR_TOKEN_MINT_ADDRESS_HERE"); +try { + const tokenMintAccount = new PublicKey(TOKEN_MINT_ADDRESS); -// Get the account where the user stores these tokens -const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( - connection, - user, - tokenMintAccount, - user.publicKey, -); + const userTokenAccount = await getOrCreateAssociatedTokenAccount( + connection, + user, + tokenMintAccount, + user.publicKey, + ); -// Our token has two decimal places -const MINOR_UNITS_PER_MAJOR_UNITS = Math.pow(10, 2); + const burnAmount = BURN_AMOUNT * 10 ** TOKEN_DECIMALS; -const transactionSignature = await burn( - connection, - user, - sourceTokenAccount.address, - tokenMintAccount, - user, - 25 * MINOR_UNITS_PER_MAJOR_UNITS, -); + const transactionSignature = await burn( + connection, + user, + userTokenAccount.address, + tokenMintAccount, + user, + burnAmount, + ); -console.log( - `Burn Transaction: ${getExplorerLink( + const explorerLink = getExplorerLink( "transaction", transactionSignature, "devnet", - )}`, -); + ); + + console.log(`✅ Burn Transaction: ${explorerLink}`); +} catch (error) { + console.error( + `Error: ${error instanceof Error ? error.message : String(error)}`, + ); +} ``` -Well done! You've now +Replace `YOUR_TOKEN_MINT_ADDRESS_HERE` with your mint token address obtained +from the previous chapter +[Token Program](/content/courses/tokens-and-nfts/token-program.md#create-the-token-mint). + +Run the script using `npx esrun burn-tokens.ts`. You should see: + +```bash +🔑 Loaded keypair. Public key: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM +✅ Burn Transaction: https://explorer.solana.com/tx/5Ufipgvsi5aLzzcr8QQ7mLXHyCwBDqsPxGTPinvFpjSiARnEDgFiPbD2ZiaDkkmwKDMoQ94bf5uqF2M7wjFWcKuv?cluster=devnet +``` + +Open the Explorer link, you will see the burn information. + +![Burn Tokens](/public/assets/courses/unboxed/burn-tokens.png) - +Well done! You've now completed the lab. -### Completed the lab? + Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=72cab3b8-984b-4b09-a341-86800167cfc7)! diff --git a/public/assets/courses/unboxed/burn-tokens.png b/public/assets/courses/unboxed/burn-tokens.png new file mode 100644 index 0000000000000000000000000000000000000000..1547f718a330909c665ee5d5381689ee11c67cc8 GIT binary patch literal 162922 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 literal 0 HcmV?d00001 diff --git a/public/assets/courses/unboxed/delegate-token.png b/public/assets/courses/unboxed/delegate-token.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e954f699023acd7bcd19a096f3d3487edba0c2 GIT binary patch literal 178777 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 J;I(gu{y(fWk`Vv^ literal 0 HcmV?d00001 diff --git a/public/assets/courses/unboxed/revoke-approve-tokens.png b/public/assets/courses/unboxed/revoke-approve-tokens.png new file mode 100644 index 0000000000000000000000000000000000000000..0cf4f8cf1024446bdcfa77d0385e9f009e260779 GIT binary patch literal 178332 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

|!lpM(9`*h~L;_byHcs=6~gYdPZg@Ed)wBk4Jov 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Ǚ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

w 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 z%0D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 z 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 Date: Wed, 2 Oct 2024 13:53:22 +0530 Subject: [PATCH 20/54] Native onchain development - Updated program security lesson (#452) * Updated contents and code snippets as per guidelines * Updated content * Resolved comments as per suggestions * Updated word highlight fix --- .../program-security.md | 476 +++++++++--------- 1 file changed, 246 insertions(+), 230 deletions(-) diff --git a/content/courses/native-onchain-development/program-security.md b/content/courses/native-onchain-development/program-security.md index 1bbba4de8..61872f316 100644 --- a/content/courses/native-onchain-development/program-security.md +++ b/content/courses/native-onchain-development/program-security.md @@ -1,119 +1,125 @@ --- title: Create a Basic Program, Part 3 - Basic Security and Validation objectives: - - Explain the importance of "thinking like an attacker" - - Understand basic security practices - - Perform owner checks - - Perform signer checks - - Validate accounts passed into the program - - Perform basic data validation -description: "How to implement account checks and validate instruction data." + - Understand why "thinking like an attacker" is essential in securing Solana + programs. + - Learn and implement core security practices to protect your program. + - Perform owner and signer checks to verify account ownership and transaction + authenticity. + - Validate the accounts passed into your program to ensure they are what you + expect. + - Conduct basic data validation to prevent invalid or malicious input from + compromising your program. +description: + "Learn how to secure your Solana program with ownership, signer, and account + validation checks." --- ## Summary -- **Thinking like an attacker** means asking "How do I break this?" -- Perform **owner checks** to ensure that the provided account is owned by the - public key you expect, e.g. ensuring that an account you expect to be a PDA is - owned by `program_id` -- Perform **signer checks** to ensure that any account modification has been - signed by the right party or parties -- **Account validation** entails ensuring that provided accounts are the - accounts you expect them to be, e.g. deriving PDAs with the expected seeds to - make sure the address matches the provided account -- **Data validation** entails ensuring that any provided data meets the criteria - required by the program +- **Thinking like an attacker** is about shifting your mindset to proactively + identify potential security gaps by asking, "How do I break this?" +- **Owner checks** ensure that an account is controlled by the expected public + key, such as verifying that a PDA (Program Derived Address) is owned by the + program. +- **Signer checks** confirm that the right parties have signed the transaction, + allowing for safe modifications to accounts. +- **Account validation** is used to ensure that the accounts passed into your + program match your expectations, like checking the correctness of a PDA's + derivation. +- **Data validation** verifies that the instruction data provided to your + program adheres to specific rules or constraints, ensuring it doesn't lead to + unintended behavior. ## Lesson -In the last two lessons we worked through building a Movie Review program -together. The end result is pretty cool! It's exciting to get something working -in a new development environment. - -Proper program development, however, doesn't end at "get it working." It's -important to think through the possible failure points in your code to mitigate -them. Failure points are where undesirable behavior in your code could -potentially occur. Whether the undesirable behavior happens due to users -interacting with your program in unexpected ways or bad actors intentionally -trying to exploit your program, anticipating failure points is essential to -secure program development. +In the previous lessons +[deserialize instruction data](/content/courses/native-onchain-development/deserialize-instruction-data.md) +and +[program state management](/content/courses/native-onchain-development/program-state-management.md), +we built a Movie Review program, and while getting it to function was exciting, +secure development doesn't stop at "just working." It's critical to understand +potential failure points and take proactive steps to secure your program against +both accidental misuse and intentional exploitation. Remember, **you have no control over the transactions that will be sent to your program once it's deployed**. You can only control how your program handles them. While this lesson is far from a comprehensive overview of program security, we'll cover some of the basic pitfalls to look out for. -### Think like an attacker - -[Neodyme](https://workshop.neodyme.io/) gave a presentation at Breakpoint 2021 -entitled "Think Like An Attacker: Bringing Smart Contracts to Their Break(ing) -Point." If there's one thing you take away from this lesson, it's that you -should think like an attacker. - -In this lesson, of course, we cannot cover everything that could possibly go -wrong with your programs. Ultimately, every program will have different security -risks associated with it. While understanding common pitfalls is _essential_ to -engineering good programs, it is _insufficient_ for deploying secure ones. To -have the broadest security coverage possible, you have to approach your code -with the right mindset. +### Think Like an Attacker -As Neodyme mentioned in their presentation, the right mindset requires moving -from the question "Is this broken?" to "How do I break this?" This is the first -and most essential step in understanding what your code _actually does_ as -opposed to what you wrote it to do. +A fundamental principle in secure programming is adopting an "attacker's +mindset." This means considering every possible angle someone might use to break +or exploit your program. -#### All programs can be broken +In their presentation at Breakpoint 2021, +[Neodyme](https://workshop.neodyme.io/) emphasized that secure program +development isn't just about identifying when something is broken; it's about +exploring how it can be broken. By asking, "How do I break this?" you shift from +simply testing expected functionality to uncovering potential weaknesses in the +implementation itself. -It's not a question of "if." +All programs, regardless of complexity, can be exploited. The goal isn't to +achieve absolute security (which is impossible) but to make it as difficult as +possible for malicious actors to exploit weaknesses. By adopting this mindset, +you're better prepared to identify and close gaps in your program's security. -Rather, it's a question of "how much effort and dedication would it take." +#### All Programs Can Be Broken -Our job as developers is to close as many holes as possible and increase the -effort and dedication required to break our code. For example, in the Movie -Review program we built together over the last two lessons, we wrote code to -create new accounts to store movie reviews. If we take a closer look at the -code, however, we'll notice how the program also facilitates a lot of -unintentional behavior we could easily catch by asking "How do I break this?" -We'll dig into some of these problems and how to fix them in this lesson, but -remember that memorizing a few pitfalls isn't sufficient. It's up to you to -change your mindset toward security. +Every program has vulnerabilities. The question isn't whether it can be broken, +but how much effort it takes. As developers, our goal is to close as many +security gaps as possible and increase the effort required to break our code. +For example, while our Movie Review program creates accounts to store reviews, +there may be unintentional behaviors that could be caught by thinking like an +attacker. In this lesson, we'll explore these issues and how to address them. ### Error handling Before we dive into some of the common security pitfalls and how to avoid them, -it's important to know how to use errors in your program. While your code can -handle some issues gracefully, other issues will require that your program stop -execution and return a program error. +it's important to know how to use errors in your program. Security issues in a +Solana program often requires terminating the execution with a meaningful error. +Not all errors are catastrophic, but some should result in stopping the program +and returning an appropriate error code to prevent further processing. -#### How to create errors +#### Creating Custom Errors -While the `solana_program` crate provides a `ProgramError` enum with a list of -generic errors we can use, it will often be useful to create your own. Your -custom errors will be able to provide more context and detail while you're -debugging your code. +Solana's +[`solana_program`](https://docs.rs/solana-program/latest/solana_program/) crate +provides a generic +[`ProgramError`](https://docs.rs/solana-program/latest/solana_program/program_error/enum.ProgramError.html) +enum for error handling. However, custom errors allow you to provide more +detailed, context-specific information that helps during debugging and testing. We can define our own errors by creating an enum type listing the errors we want to use. For example, the `NoteError` contains variants `Forbidden` and `InvalidLength`. The enum is made into a Rust `Error` type by using the `derive` -attribute macro to implement the `Error` trait from the `thiserror` library. -Each error type also has its own `#[error("...")]` notation. This lets you -provide an error message for each particular error type. +attribute macro to implement the `Error` trait from the +[`thiserror`](https://docs.rs/thiserror/latest/thiserror/) library. Each error +type also has its own `#[error("...")]` notation. This lets you provide an error +message for each particular error type. + +Here's an example of how you can define custom errors in your program: ```rust -use solana_program::{program_error::ProgramError}; +use solana_program::program_error::ProgramError; use thiserror::Error; -#[derive(Error)] +#[derive(Error, Debug)] pub enum NoteError { - #[error("Wrong note owner")] + #[error("Unauthorized access - You don't own this note.")] Forbidden, - #[error("Text is too long")] + #[error("Invalid note length - The text exceeds the allowed limit.")] InvalidLength, } ``` -#### How to return errors +In this example, we create custom errors for unauthorized access and invalid +data input (such as note length). Defining custom errors gives us greater +flexibility when debugging or explaining what went wrong during execution. + +#### Returning Errors The compiler expects errors returned by the program to be of type `ProgramError` from the `solana_program` crate. That means we won't be able to return our @@ -138,54 +144,66 @@ if pda != *note_pda.key { } ``` -### Basic security checks +This ensures the program gracefully handles errors and provides meaningful +feedback when things go wrong. -While these won't comprehensively secure your program, there are a few security -checks you can keep in mind to fill in some of the larger gaps in your code: +### Basic Security Checks -- Ownership checks - used to verify that an account is owned by the program -- Signer checks - used to verify that an account has signed a transaction -- General Account Validation - used to verify that an account is the expected - account -- Data Validation - used to verify the inputs provided by a user +To ensure your Solana program is resilient against common vulnerabilities, you +should incorporate key security checks. These are critical for detecting invalid +accounts or unauthorized transactions and preventing undesired behavior. #### Ownership checks -An ownership check verifies that an account is owned by the expected public key. -Let's use the note-taking app example that we've referenced in previous lessons. -In this app, users can create, update, and delete notes that are stored by the -program in PDA accounts. - -When a user invokes the `update` instruction, they also provide a `pda_account`. -We presume the provided `pda_account` is for the particular note they want to -update, but the user can input any instruction data they want. They could even -potentially send data which matches the data format of a note account but was -not also created by the note-taking program. This security vulnerability is one -potential way to introduce malicious code. +An ownership check verifies that an account is owned by the expected program. +For instance, if your program relies on PDAs (Program Derived Addresses), you +want to ensure that those PDAs are controlled by your program and not by an +external party. + +Let's use the note-taking app example that we've referenced in the +[deserialize instruction data](/content/courses/native-onchain-development/deserialize-instruction-data.md) +and +[program state management](/content/courses/native-onchain-development/program-state-management.md) +lessons. In this app, users can create, update, and delete notes that are stored +by the program in PDA accounts. + +When a user invokes the `update` instruction handler, they also provide a +`pda_account`. We presume the provided `pda_account` is for the particular note +they want to update, but the user can input any instruction data they want. They +could even potentially send data that matches the data format of a note account +but was not also created by the note-taking program. This security vulnerability +is one potential way to introduce malicious code. The simplest way to avoid this problem is to always check that the owner of an account is the public key you expect it to be. In this case, we expect the note account to be a PDA account owned by the program itself. When this is not the case, we can report it as an error accordingly. +Here's how you can perform an ownership check to verify that an account is owned +by the program: + ```rust if note_pda.owner != program_id { return Err(ProgramError::InvalidNoteAccount); } ``` -As a side note, using PDAs whenever possible is more secure than trusting -externally-owned accounts, even if they are owned by the transaction signer. The -only accounts that the program has complete control over are PDA accounts, -making them the most secure. +In this example, we check if the `note_pda` is owned by the program itself +(denoted by `program_id`). Ownership checks like these prevent unauthorized +entities from tampering with critical accounts. + + + +PDAs are often considered to be trusted stores of a program's state. Ensuring +the correct program owns the PDAs is a fundamental way to prevent malicious +behavior. -#### Signer checks +#### Signer Checks -A signer check simply verifies that the right parties have signed a transaction. -In the note-taking app, for example, we would want to verify that the note -creator signed the transaction before we process the `update` instruction. -Otherwise, anyone can update another user's notes by simply passing in the -user's public key as the initializer. +Signer checks confirm that a transaction has been signed by the correct parties. +In the note-taking app, for example, we want to verify that only the note +creator can update the note. Without this check, anyone could attempt to modify +another user's note by passing in their public key. ```rust if !initializer.is_signer { @@ -194,39 +212,48 @@ if !initializer.is_signer { } ``` -#### General account validation +By verifying that the initializer has signed the transaction, we ensure that +only the legitimate owner of the account can perform actions on it. -In addition to checking the signers and owners of accounts, it's important to -ensure that the provided accounts are what your code expects them to be. For -example, you would want to validate that a provided PDA account's address can be -derived with the expected seeds. This ensures that it is the account you expect -it to be. +#### Account Validation + +Account validation checks that the accounts passed into the program are correct +and valid. This is often done by deriving the expected account using known seeds +(for PDAs) and comparing it to the passed account. -In the note-taking app example, that would mean ensuring that you can derive a -matching PDA using the note creator's public key and the ID as seeds (that's -what we're assuming was used when creating the note). That way a user couldn't -accidentally pass in a PDA account for the wrong note or, more importantly, that -the user isn't passing in a PDA account that represents somebody else's note -entirely. +For instance, in the note-taking app, you can derive the expected PDA using the +creator's public key and note ID, and then validate that it matches the provided +account: ```rust -let (pda, bump_seed) = Pubkey::find_program_address(&[note_creator.key.as_ref(), id.as_bytes().as_ref(),], program_id); +let (expected_pda, bump_seed) = Pubkey::find_program_address( + &[ + note_creator.key.as_ref(), + id.as_bytes().as_ref(), + ], + program_id +); -if pda != *note_pda.key { +if expected_pda != *note_pda.key { msg!("Invalid seeds for PDA"); return Err(ProgramError::InvalidArgument) } ``` -### Data validation +This check prevents a user from accidentally (or maliciously) passing the wrong +PDA or one that belongs to someone else. By validating the PDA's derivation, you +ensure the program is acting on the correct account. -Similar to validating accounts, you should also validate any data provided by -the client. +### Data Validation -For example, you may have a game program where a user can allocate character -attribute points to various categories. You may have a maximum limit in each -category of 100, in which case you would want to verify that the existing -allocation of points plus the new allocation doesn't exceed the maximum. +Data validation ensures that the input provided to your program meets the +expected criteria. This is crucial for avoiding incorrect or malicious data that +could cause the program to behave unpredictably. + +For example, let's say your program allows users to allocate points to a +character's attributes, but each attribute has a maximum allowed value. Before +making any updates, you should check that the new allocation does not exceed the +defined limit: ```rust if character.agility + new_agility > 100 { @@ -235,8 +262,8 @@ if character.agility + new_agility > 100 { } ``` -Or, the character may have an allowance of attribute points they can allocate -and you want to make sure they don't exceed that allowance. +Similarly, you should check that the user is not exceeding their allowed number +of points: ```rust if attribute_allowance < new_agility { @@ -245,10 +272,9 @@ if attribute_allowance < new_agility { } ``` -Without these checks, program behavior would differ from what you expect. In -some cases, however, it's more than just an issue of undefined behavior. -Sometimes failure to validate data can result in security loopholes that are -financially devastating. +Without these validations, the program could end up in an undefined state or be +exploited by malicious actors, potentially causing financial loss or +inconsistent behavior. For example, imagine that the character referenced in these examples is an NFT. Further, imagine that the program allows the NFT to be staked to earn token @@ -260,45 +286,50 @@ stakers. #### Integer overflow and underflow -Rust integers have fixed sizes. This means they can only support a specific -range of numbers. An arithmetic operation that results in a higher or lower -value than what is supported by the range will cause the resulting value to wrap -around. For example, a `u8` only supports numbers 0-255, so the result of -addition that would be 256 would actually be 0, 257 would be 1, etc. +One of the common pitfalls when working with integers in Rust (and in Solana +programs) is handling integer overflow and underflow. Rust integers have fixed +sizes and can only hold values within a certain range. When a value exceeds that +range, it wraps around, leading to unexpected results. -This is always important to keep in mind, but especially so when dealing with -any code that represents true value, such as depositing and withdrawing tokens. +For example, with a `u8` (which holds values between 0 and 255), adding 1 to 255 +results in a value of 0 (overflow). To avoid this, you should use checked math +functions like +[`checked_add()`](https://doc.rust-lang.org/std/primitive.u8.html#method.checked_add) +and +[`checked_sub()`](https://doc.rust-lang.org/std/primitive.u8.html#method.checked_sub): To avoid integer overflow and underflow, either: 1. Have logic in place that ensures overflow or underflow _cannot_ happen or -2. Use checked math like `checked_add` instead of `+` +2. Use checked math like `checked_add()` instead of `+` + ```rust let first_int: u8 = 5; let second_int: u8 = 255; - let sum = first_int.checked_add(second_int); + let sum = first_int.checked_add(second_int) + .ok_or(ProgramError::ArithmeticOverflow)?; ``` ## Lab -Let's practice together with the Movie Review program we've worked on in -previous lessons. No worries if you're just jumping into this lesson without -having done the previous lesson - it should be possible to follow along either -way. +In this lab, we will build upon the Movie Review program that allows users to +store movie reviews in PDA accounts. If you haven't completed the previous +lessons +[deserialize instruction data](/content/courses/native-onchain-development/deserialize-instruction-data.md) +and +[program state management](/content/courses/native-onchain-development/program-state-management.md), +don't worry—this guide is self-contained. -As a refresher, the Movie Review program lets users store movie reviews in PDA -accounts. Last lesson, we finished implementing the basic functionality of -adding a movie review. Now, we'll add some security checks to the functionality -we've already created and add the ability to update a movie review in a secure -manner. - -Just as before, we'll be using [Solana Playground](https://beta.solpg.io/) to -write, build, and deploy our code. +The Movie Review program lets users add and update reviews in PDA accounts. In +previous lessons, we implemented basic functionality for adding reviews. Now, +we'll add security checks and implement an update feature in a secure manner. +We'll use [Solana Playground](https://beta.solpg.io/) to write, build, and +deploy our program. ### 1. Get the starter code To begin, you can find -[the movie review starter code](https://beta.solpg.io/62b552f3f6273245aca4f5c9). +[the movie review starter code](https://beta.solpg.io/62b552f3f6273245aca4f5c9). If you've been following along with the Movie Review labs, you'll notice that we've refactored our program. @@ -317,12 +348,12 @@ defining custom errors. The complete file structure is as follows: - **state.rs -** serialize and deserialize state - **error.rs -** custom program errors -In addition to some changes to file structure, we've updated a small amount of -code that will let this lab be more focused on security without having you write -unnecessary boiler plate. +In addition to some changes to the file structure, we've updated a small amount +of code that will let this lab be more focused on security without having you +write unnecessary boilerplate. Since we'll be allowing updates to movie reviews, we also changed `account_len` -in the `add_movie_review` function (now in `processor.rs`). Instead of +in the `add_movie_review()` function (now in `processor.rs`). Instead of calculating the size of the review and setting the account length to only as large as it needs to be, we're simply going to allocate 1000 bytes to each review account. This way, we don't have to worry about reallocating size or @@ -356,8 +387,7 @@ that checks the `is_initialized` field on the `MovieAccountState` struct. `MovieAccountState` has a known size and provides for some compiler optimizations. -```rust -// inside state.rs +```rust filename="state.rs" impl Sealed for MovieAccountState {} impl IsInitialized for MovieAccountState { @@ -367,27 +397,21 @@ impl IsInitialized for MovieAccountState { } ``` -Before moving on, make sure you have a solid grasp on the current state of the +Before moving on, make sure you have a solid grasp of the current state of the program. Look through the code and spend some time thinking through any spots that are confusing to you. It may be helpful to compare the starter code to the [solution code from the previous lesson](https://beta.solpg.io/62b23597f6273245aca4f5b4). ### 2. Custom Errors -Let's begin by writing our custom program errors. We'll need errors that we can -use in the following situations: - -- The update instruction has been invoked on an account that hasn't been - initialized yet -- The provided PDA doesn't match the expected or derived PDA -- The input data is larger than the program allows -- The rating provided does not fall in the 1-5 range +We'll define custom errors to handle cases like uninitialized accounts, invalid +PDA matches, exceeding data limits, and invalid ratings (ratings must be between +1 and 5). These errors will be added to the `error.rs` file: The starter code includes an empty `error.rs` file. Open that file and add errors for each of the above cases. -```rust -// inside error.rs +```rust filename="error.rs" use solana_program::{program_error::ProgramError}; use thiserror::Error; @@ -414,19 +438,16 @@ impl From for ProgramError { } ``` -Note that in addition to adding the error cases, we also added the -implementation that lets us convert our error into a `ProgramError` type as -needed. +Note that in addition to adding the error cases, we also added an implementation +that lets us convert our error into a `ProgramError` type as needed. -Before moving on, let's bring `ReviewError` into scope in the `processor.rs`. We -will be using these errors shortly when we add our security checks. +After adding the errors, import `ReviewError` in `processor.rs` to use them. -```rust -// inside processor.rs +```rust filename="processor.rs" use crate::error::ReviewError; ``` -### 3. Add security checks to `add_movie_review` +### 3. Add Security Checks to add_movie_review Now that we have errors to use, let's implement some security checks to our `add_movie_review` function. @@ -438,7 +459,7 @@ also a signer on the transaction. This ensures that you can't submit movie reviews impersonating somebody else. We'll put this check right after iterating through the accounts. -```rust +```rust filename="processor.rs" let account_info_iter = &mut accounts.iter(); let initializer = next_account_info(account_info_iter)?; @@ -455,11 +476,11 @@ if !initializer.is_signer { Next, let's make sure the `pda_account` passed in by the user is the `pda` we expect. Recall we derived the `pda` for a movie review using the `initializer` -and `title` as seeds. Within our instruction we'll derive the `pda` again and +and `title` as seeds. Within our instruction, we'll derive the `pda` again and then check if it matches the `pda_account`. If the addresses do not match, we'll return our custom `InvalidPDA` error. -```rust +```rust filename="processor.rs" // Derive PDA and check that it matches client let (pda, _bump_seed) = Pubkey::find_program_address(&[initializer.key.as_ref(), account_data.title.as_bytes().as_ref(),], program_id); @@ -477,7 +498,7 @@ We'll start by making sure `rating` falls within the 1 to 5 scale. If the rating provided by the user outside of this range, we'll return our custom `InvalidRating` error. -```rust +```rust filename="processor.rs" if rating > 5 || rating < 1 { msg!("Rating cannot be higher than 5"); return Err(ReviewError::InvalidRating.into()) @@ -488,7 +509,7 @@ Next, let's check that the content of the review does not exceed the 1000 bytes we've allocated for the account. If the size exceeds 1000 bytes, we'll return our custom `InvalidDataLength` error. -```rust +```rust filename="processor.rs" let total_len: usize = 1 + 1 + (4 + title.len()) + (4 + description.len()); if total_len > 1000 { msg!("Data length is larger than 1000 bytes"); @@ -496,20 +517,20 @@ if total_len > 1000 { } ``` -Lastly, let's checking if the account has already been initialized by calling -the `is_initialized` function we implemented for our `MovieAccountState`. If the +Lastly, let's check if the account has already been initialized by calling the +`is_initialized` function we implemented for our `MovieAccountState`. If the account already exists, then we will return an error. -```rust +```rust filename="processor.rs" if account_data.is_initialized() { msg!("Account already initialized"); return Err(ProgramError::AccountAlreadyInitialized); } ``` -All together, the `add_movie_review` function should look something like this: +Altogether, the `add_movie_review()` function should look something like this: -```rust +```rust filename="processor.rs" pub fn add_movie_review( program_id: &Pubkey, accounts: &[AccountInfo], @@ -592,17 +613,12 @@ pub fn add_movie_review( } ``` -### 4. Support movie review updates in `MovieInstruction` - -Now that `add_movie_review` is more secure, let's turn our attention to -supporting the ability to update a movie review. +### 4. Support Movie Review Updates in MovieInstruction -Let's begin by updating `instruction.rs`. We'll start by adding an -`UpdateMovieReview` variant to `MovieInstruction` that includes embedded data -for the new title, rating, and description. +Next, we'll modify `instruction.rs` to add support for updating movie reviews. +We'll introduce a new `UpdateMovieReview()` variant in `MovieInstruction`: -```rust -// inside instruction.rs +```rust filename="instruction.rs" pub enum MovieInstruction { AddMovieReview { title: String, @@ -618,13 +634,12 @@ pub enum MovieInstruction { ``` The payload struct can stay the same since aside from the variant type, the -instruction data is the same as what we used for `AddMovieReview`. +instruction data is the same as what we used for `AddMovieReview()`. -Lastly, in the `unpack` function we need to add `UpdateMovieReview` to the match -statement. +We'll also update the `unpack()` function to handle `UpdateMovieReview()`. -```rust -// inside instruction.rs +```rust filename="instruction.rs" +// Inside instruction.rs impl MovieInstruction { pub fn unpack(input: &[u8]) -> Result { let (&variant, rest) = input.split_first().ok_or(ProgramError::InvalidInstructionData)?; @@ -644,38 +659,38 @@ impl MovieInstruction { } ``` -### 5. Define `update_movie_review` function +### 5. Define update_movie_review Function Now that we can unpack our `instruction_data` and determine which instruction of -the program to run, we can add `UpdateMovieReview` to the match statement in -the `process_instruction` function in the `processor.rs` file. +the program to run, we can add `UpdateMovieReview()` to the match statement in +the `process_instruction()` function in the `processor.rs` file. -```rust -// inside processor.rs +```rust filename="processor.rs" +// Inside processor.rs pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8] ) -> ProgramResult { - // unpack instruction data + // Unpack instruction data let instruction = MovieInstruction::unpack(instruction_data)?; match instruction { MovieInstruction::AddMovieReview { title, rating, description } => { add_movie_review(program_id, accounts, title, rating, description) }, - // add UpdateMovieReview to match against our new data structure + // Add UpdateMovieReview to match against our new data structure MovieInstruction::UpdateMovieReview { title, rating, description } => { - // make call to update function that we'll define next + // Make call to update function that we'll define next update_movie_review(program_id, accounts, title, rating, description) } } } ``` -Next, we can define the new `update_movie_review` function. The definition +Next, we can define the new `update_movie_review()` function. The definition should have the same parameters as the definition of `add_movie_review`. -```rust +```rust filename="processor.rs" pub fn update_movie_review( program_id: &Pubkey, accounts: &[AccountInfo], @@ -687,16 +702,16 @@ pub fn update_movie_review( } ``` -### 6. Implement `update_movie_review` function +### 6. Implement update_movie_review Function All that's left now is to fill in the logic for updating a movie review. Only let's make it secure from the start. -Just like the `add_movie_review` function, let's start by iterating through the -accounts. The only accounts we'll need are the first two: `initializer` and +Just like the `add_movie_review()` function, let's start by iterating through +the accounts. The only accounts we'll need are the first two: `initializer` and `pda_account`. -```rust +```rust filename="processor.rs" pub fn update_movie_review( program_id: &Pubkey, accounts: &[AccountInfo], @@ -722,7 +737,7 @@ Before we continue, let's implement some basic security checks. We'll start with an ownership check on for `pda_account` to verify that it is owned by our program. If it isn't, we'll return an `InvalidOwner` error. -```rust +```rust filename="processor.rs" if pda_account.owner != program_id { return Err(ProgramError::InvalidOwner) } @@ -736,7 +751,7 @@ data for a movie review, we want to ensure that the original `initializer` of the review has approved the changes by signing the transaction. If the `initializer` did not sign the transaction, we'll return an error. -```rust +```rust filename="processor.rs" if !initializer.is_signer { msg!("Missing required signature"); return Err(ProgramError::MissingRequiredSignature) @@ -748,9 +763,9 @@ if !initializer.is_signer { Next, let's check that the `pda_account` passed in by the user is the PDA we expect by deriving the PDA using `initializer` and `title` as seeds. If the addresses do not match, we'll return our custom `InvalidPDA` error. We'll -implement this the same way we did in the `add_movie_review` function. +implement this the same way we did in the `add_movie_review()` function. -```rust +```rust filename="processor.rs" // Derive PDA and check that it matches client let (pda, _bump_seed) = Pubkey::find_program_address(&[initializer.key.as_ref(), account_data.title.as_bytes().as_ref(),], program_id); @@ -760,13 +775,13 @@ if pda != *pda_account.key { } ``` -#### Unpack `pda_account` and perform data validation +#### Unpack pda_account and Perform Data Validation Now that our code ensures we can trust the passed in accounts, let's unpack the `pda_account` and perform some data validation. We'll start by unpacking `pda_account` and assigning it to a mutable variable `account_data`. -```rust +```rust filename="processor.rs" msg!("unpacking state account"); let mut account_data = try_from_slice_unchecked::(&pda_account.data.borrow()).unwrap(); msg!("borrowed account data"); @@ -785,13 +800,13 @@ if !account_data.is_initialized() { ``` Next, we need to validate the `rating`, `title`, and `description` data just -like in the `add_movie_review` function. We want to limit the `rating` to a +like in the `add_movie_review()` function. We want to limit the `rating` to a scale of 1 to 5 and limit the overall size of the review to be fewer than 1000 -bytes. If the rating provided by the user outside of this range, then we'll +bytes. If the rating provided by the user is outside of this range, then we'll return our custom `InvalidRating` error. If the review is too long, then we'll return our custom `InvalidDataLength` error. -```rust +```rust filename="processor.rs" if rating > 5 || rating < 1 { msg!("Rating cannot be higher than 5"); return Err(ReviewError::InvalidRating.into()) @@ -810,7 +825,7 @@ Now that we've implemented all of the security checks, we can finally update the movie review account by updating `account_data` and re-serializing it. At that point, we can return `Ok` from our program. -```rust +```rust filename="processor.rs" account_data.rating = rating; account_data.description = description; @@ -819,11 +834,11 @@ account_data.serialize(&mut &mut pda_account.data.borrow_mut()[..])?; Ok(()) ``` -All together, the `update_movie_review` function should look something like the -code snippet below. We've included some additional logging for clarity in +All together, the `update_movie_review()` function should look something like +the code snippet below. We've included some additional logging for clarity in debugging. -```rust +```rust filename="processor.rs" pub fn update_movie_review( program_id: &Pubkey, accounts: &[AccountInfo], @@ -900,7 +915,7 @@ pub fn update_movie_review( We're ready to build and upgrade our program! You can test your program by submitting a transaction with the right instruction data. For that, feel free to use this -[frontend](https://github.com/Unboxed-Software/solana-movie-frontend/tree/solution-update-reviews). +[frontend](https://github.com/solana-developers/movie-frontend/tree/solution-update-reviews). Remember, to make sure you're testing the right program you'll need to replace `MOVIE_REVIEW_PROGRAM_ID` with your program ID in `Form.tsx` and `MovieCoordinator.ts`. @@ -914,7 +929,7 @@ continuing. Now it's your turn to build something independently by building on top of the Student Intro program that you've used in previous lessons. If you haven't been -following along or haven't saved your code from before, feel free to use +following along or haven't saved your code before, feel free to use [this starter code](https://beta.solpg.io/62b11ce4f6273245aca4f5b2). The Student Intro program is a Solana Program that lets students introduce @@ -933,6 +948,7 @@ Note that your code may look slightly different than the solution code depending on the checks you implement and the errors you write. + Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=3dfb98cc-7ba9-463d-8065-7bdb1c841d43)! From a73581f0dcdaf87745c1766e9369e14f56f03d55 Mon Sep 17 00:00:00 2001 From: Chandra Pratama <117942738+darkvallen@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:28:55 +0700 Subject: [PATCH 21/54] Update Lookup Tables (LUTs) course (#476) * Update the Address Lookup Tables (LUTs) course to comply with the Solana contributing guide. - Simplified explanations of versioned transactions and Address Lookup Tables (LUTs). - Updated code examples to align with best practices. - Ensured the documentation followed the guidelines from the Solana contributing guide. - Enhance readability * Update lookup-tables.md - Update Course Codes * Update code snippets - Integrated the `@solana/web3.js `library's modern practices and utilized `@solana-developer/solana-helpers` where appropriate, including transaction confirmation and generating Solana Explorer links. - Enhanced explanation to increase readability * Update lookup-tables.md Fix some review * Update lookup-tables.md Updates Based on the Latest Review: - Revised certain sections for clarity - Added a callout - Fixed typos - Removed the usage of starter code and custom helpers in the lab section, and updated to create files from scratch - Import only the necessary function from `solana/web3.js` * Update lookup-tables.md - Add filenames --- .../program-optimization/lookup-tables.md | 935 ++++++++++-------- 1 file changed, 497 insertions(+), 438 deletions(-) diff --git a/content/courses/program-optimization/lookup-tables.md b/content/courses/program-optimization/lookup-tables.md index fac7b6682..6576eb3d5 100644 --- a/content/courses/program-optimization/lookup-tables.md +++ b/content/courses/program-optimization/lookup-tables.md @@ -10,36 +10,33 @@ description: "Use large amounts of accounts by using lookup tables." ## Summary -- **Versioned Transactions** refers to a way to support both legacy versions and - newer versions of transaction formats. The original transaction format is - "legacy" and new transaction versions start at version 0. Versioned - transactions were implemented to support the use of Address Lookup Tables - (also called lookup tables or LUTs). -- **Address Lookup Tables** are accounts used to store addresses of other - accounts, which can then be referenced in versioned transactions using a 1 - byte index instead of the full 32 bytes per address. This enables the creation - of more complex transactions than what was possible prior to the introduction - of LUTs. +- **Versioned Transactions** in Solana allows support for both legacy and newer + transaction formats. The original format is referred to as "legacy," while new + formats begin at version 0. Versioned transactions were introduced to + accommodate the use of Address Lookup Tables (LUTs). +- **Address Lookup Tables** are special accounts that store the addresses of + other accounts. In versioned transactions, these addresses can be referenced + by a 1-byte index instead of the full 32-byte address. This optimization + enables more complex transactions than previously possible. ## Lesson By design, Solana transactions are limited to 1232 bytes. Transactions exceeding -this size will fail. While this enables a number of network optimizations, it -can also limit the types of atomic operations that can be performed on the -network. +this limit will fail, which restricts the size of atomic operations that can be +performed. While this limit allows for optimizations at the network level, it +imposes restrictions on transaction complexity. -To help get around the transaction size limitation, Solana released a new -transaction format that allows support for multiple versions of transaction -formats. At the time of writing, Solana supports two transaction versions: +To address transaction size limitations, Solana introduced a new transaction +format supporting multiple versions. Currently, two transaction versions are +supported: -1. `legacy` - the original transaction format -2. `0` - the newest transaction format that includes support for Address Lookup - Tables +1. `legacy` - The original transaction format +2. `0` - The latest format, which supports Address Lookup Tables. -Versioned transactions don't require any modifications to existing Solana -programs, but any client-side code created prior to the release of versioned -transactions should be updated. In this lesson, we'll cover the basics of -versioned transactions and how to use them, including: +Existing Solana programs do not require changes to support versioned +transactions. However, client-side code created prior to their introduction +should be updated. In this lesson, we'll cover the basics of versioned +transactions and how to use them, including: - Creating versioned transactions - Creating and managing lookup tables @@ -47,125 +44,114 @@ versioned transactions and how to use them, including: ### Versioned Transactions -One of the items taking up the most space in Solana transactions is the -inclusion of full account addresses. At 32 bytes each, 39 accounts will render a -transaction too large. That's not even accounting for instruction data. In -practice, most transactions will be too large with around 20 accounts. +In Solana transactions, one of the largest space consumers is account addresses, +which are 32 bytes each. For transactions with 39 accounts, the size limit is +exceeded even before accounting for instruction data. Typically, transactions +become too large with around 20 accounts. -Solana released versioned transactions to support multiple transaction formats. -Alongside the release of versioned transactions, Solana released version 0 of -transactions to support Address Lookup Tables. Lookup tables are separate -accounts that store account addresses and then allow them to be referenced in a -transaction using a 1 byte index. This significantly decreases the size of a -transaction since each included account now only needs to use 1 byte instead of -32 bytes. +Versioned transactions address this issue by introducing Address Lookup Tables, +which allow addresses to be stored separately and referenced via a 1-byte index. +This greatly reduces transaction size by minimizing the space needed for account +addresses. -Even if you don't need to use lookup tables, you'll need to know how to support -versioned transactions in your client-side code. Fortunately, everything you -need to work with versioned transactions and lookup tables is included in the -`@solana/web3.js` library. +Even if Address Lookup Tables are not required for your use case, understanding +versioned transactions is crucial for maintaining compatibility with the latest +Solana features. The `@solana/web3.js` library provides all necessary tools to +work with versioned transactions and lookup tables. #### Create versioned transactions -To create a versioned transaction, you simply create a `TransactionMessage` with +To create a versioned transaction, you first create a `TransactionMessage` with the following parameters: - `payerKey` - the public key of the account that will pay for the transaction - `recentBlockhash` - a recent blockhash from the network -- `instructions` - the instructions to include in the transaction +- `instructions` - the instructions to be executed in the transaction. -You then transform this message object into a version `0` transaction using the -`compileToV0Message()` method. +Once the message object is created, you can convert it into a version `0` +transaction using the `compileToV0Message()` method. ```typescript import * as web3 from "@solana/web3.js"; // Example transfer instruction -const transferInstruction = [ - web3.SystemProgram.transfer({ - fromPubkey: payer.publicKey, // Public key of account that will send the funds - toPubkey: toAccount.publicKey, // Public key of the account that will receive the funds - lamports: 1 * LAMPORTS_PER_SOL, // Amount of lamports to be transferred - }), -]; +const transferInstruction = SystemProgram.transfer({ + fromPubkey: payer.publicKey, // Public key of the sender account + toPubkey: toAccount.publicKey, // Public key of the receiver account + lamports: 1 * LAMPORTS_PER_SOL, // Amount to transfer in lamports +}); // Get the latest blockhash -let { blockhash } = await connection.getLatestBlockhash(); +const { blockhash } = await connection.getLatestBlockhash(); // Create the transaction message -const message = new web3.TransactionMessage({ - payerKey: payer.publicKey, // Public key of the account that will pay for the transaction - recentBlockhash: blockhash, // Latest blockhash - instructions: transferInstruction, // Instructions included in transaction +const message = new TransactionMessage({ + payerKey: payer.publicKey, // Public key of the payer account + recentBlockhash: blockhash, // Most recent blockhash + instructions: [transferInstruction], // Transaction instructions }).compileToV0Message(); ``` -Finally, you pass the compiled message into the `VersionedTransaction` -constructor to create a new versioned transaction. Your code can then sign and -send the transaction to the network, similar to a legacy transaction. +Next, pass the compiled message into the `VersionedTransaction` constructor to +create a versioned transaction. The transaction is then signed and sent to the +network, similar to how legacy transactions are handled. ```typescript -// Create the versioned transaction using the message -const transaction = new web3.VersionedTransaction(message); +// Create the versioned transaction from the compiled message +const transaction = new VersionedTransaction(message); -// Sign the transaction +// Sign the transaction with the payer's keypair transaction.sign([payer]); // Send the signed transaction to the network -const transactionSignature = await connection.sendTransaction(transaction); +const signature = await connection.sendTransaction(transaction); ``` ### Address Lookup Table -Address Lookup Tables (also called lookup tables or LUTs) are accounts that -store a lookup table of other account addresses. These LUT accounts are owned by -the Address Lookup Table Program and are used to increase the number of accounts -that can be included in a single transaction. +Address Lookup Tables (LUTs) are accounts that store references to other account +addresses. These LUT accounts, owned by the Address Lookup Table Program, +increase the number of accounts that can be included in a transaction. -Versioned transactions can include the address of an LUT account and then -reference additional accounts with a 1-byte index instead of including the full -address of those accounts. This significantly reduces the amount of space used -for referencing accounts in a transaction. +In versioned transactions, LUT addresses are included, and additional accounts +are referenced with a 1-byte index instead of the full 32-byte address, reducing +space used by the transaction. -To simplify the process of working with LUTs, the `@solana/web3.js` library -includes an `AddressLookupTableProgram` class which provides a set of methods to -create instructions for managing LUTs. These methods include: +The `@solana/web3.js` library offers an `AddressLookupTableProgram` class, +providing methods to manage LUTs: -- `createLookupTable` - creates a new LUT account -- `freezeLookupTable` - makes an existing LUT immutable -- `extendLookupTable` - adds addresses to an existing LUT -- `deactivateLookupTable` - puts an LUT in a “deactivation” period before it can - be closed -- `closeLookupTable` - permanently closes an LUT account +- `createLookupTable` - creates a new LUT account. +- `freezeLookupTable` - makes a LUT immutable. +- `extendLookupTable` - adds addresses to an existing LUT. +- `deactivateLookupTable` - begins the deactivation period for an LUT. +- `closeLookupTable` - permanently closes an LUT account. #### Create a lookup table -You use the `createLookupTable` method to construct the instruction that creates -a lookup table. The function requires the following parameters: +You can use the `createLookupTable` method to construct the instruction for +creating a lookup table. This requires the following parameters: -- `authority` - the account that will have permission to modify the lookup table -- `payer` - the account that will pay for the account creation -- `recentSlot` - a recent slot to derive the lookup table's address +- `authority` - the account authorized to modify the lookup table. +- `payer` - the account responsible for paying the account creation fees. +- `recentSlot` - a recent slot used to derive the lookup table's address. -The function returns both the instruction to create the lookup table and the -address of the lookup table. +The function returns both the instruction for creating the LUT and its address. ```typescript // Get the current slot const slot = await connection.getSlot(); -// Create an instruction for creating a lookup table -// and retrieve the address of the new lookup table +// Create the lookup table creation instruction and retrieve its address const [lookupTableInst, lookupTableAddress] = - web3.AddressLookupTableProgram.createLookupTable({ - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - recentSlot: slot - 1, // The recent slot to derive lookup table's address + AddressLookupTableProgram.createLookupTable({ + authority: user.publicKey, // Account authorized to modify the LUT + payer: user.publicKey, // Account paying for transaction fees + recentSlot: slot - 1, // Use a recent slot to derive the LUT address }); ``` -Under the hood, the lookup table address is simply a PDA derived using the -`authority` and `recentSlot` as seeds. +Under the hood, the lookup table address is a Program Derived Address (PDA) +generated using the `authority` and `recentSlot` as seeds. ```typescript const [lookupTableAddress, bumpSeed] = PublicKey.findProgramAddressSync( @@ -174,10 +160,12 @@ const [lookupTableAddress, bumpSeed] = PublicKey.findProgramAddressSync( ); ``` -Note that using the most recent slot sometimes results in an error after sending -the transaction. To avoid this, you can use a slot that is one slot prior the -most recent one (e.g. `recentSlot: slot - 1`). However, if you still encounter -an error when sending the transaction, you can try resending the transaction. + +Using the most recent slot sometimes results in errors when submitting the +transaction. To avoid this, it’s recommended to use a slot that is one slot +before the most recent one (`recentSlot: currentSlot - 1`). If you still +encounter errors when sending the transaction, try resubmitting it. + ``` "Program AddressLookupTab1e1111111111111111111111111 invoke [1]", @@ -187,57 +175,57 @@ an error when sending the transaction, you can try resending the transaction. #### Extend a lookup table -You use the `extendLookupTable` method to create an instruction that adds -addresses to an existing lookup table. It takes the following parameters: +The `extendLookupTable` method creates an instruction to add addresses to an +existing lookup table. It requires the following parameters: -- `payer` - the account that will pay for the transaction fees and any increased - rent -- `authority` - the account that has permission to change the lookup table -- `lookupTable` - the address of the lookup table to extend -- `addresses` - the addresses to add to the lookup table +- `payer` - the account responsible for paying transaction fees and any + additional rent. +- `authority` - the account authorized to modify the lookup table. +- `lookupTable` - the address of the lookup table to be extended. +- `addresses` - the list of addresses to add to the lookup table. The function returns an instruction to extend the lookup table. ```typescript const addresses = [ - new web3.PublicKey("31Jy3nFeb5hKVdB4GS4Y7MhU7zhNMFxwF7RGVhPc1TzR"), - new web3.PublicKey("HKSeapcvwJ7ri6mf3HwBtspLFTDKqaJrMsozdfXfg5y2"), - // add more addresses + new PublicKey("31Jy3nFeb5hKVdB4GS4Y7MhU7zhNMFxwF7RGVhPc1TzR"), + new PublicKey("HKSeapcvwJ7ri6mf3HwBtspLFTDKqaJrMsozdfXfg5y2"), + // Add more addresses here ]; -// Create an instruction to extend a lookup table with the provided addresses -const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({ - payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - lookupTable: lookupTableAddress, // The address of the lookup table to extend - addresses: addresses, // The addresses to add to the lookup table +// Create the instruction to extend the lookup table with the provided addresses +const extendInstruction = AddressLookupTableProgram.extendLookupTable({ + payer: user.publicKey, // Account paying for transaction fees + authority: user.publicKey, // Account authorized to modify the lookup table + lookupTable: lookupTableAddress, // Address of the lookup table to extend + addresses: addresses, // Addresses to add to the lookup table }); ``` Note that when extending a lookup table, the number of addresses that can be -added in one instruction is limited by the transaction size limit, which is 1232 -bytes. This means you can add 30 addresses to a lookup table at a time. If you -need to add more than that, you'll need to send multiple transactions. Each -lookup table can store a maximum of 256 addresses. +added in a single instruction is limited by the transaction size limit of 1232 +bytes. You can add approximately 30 addresses in one transaction. If you need to +add more than that, multiple transactions are required. Each lookup table can +store up to 256 addresses. #### Send Transaction -After creating the instructions, you can add them to a transaction and sent it -to the network. +After creating the instructions, you can add them to a transaction and send it +to the network: ```typescript // Get the latest blockhash -let { blockhash } = await connection.getLatestBlockhash(); +const { blockhash } = await connection.getLatestBlockhash(); // Create the transaction message -const message = new web3.TransactionMessage({ - payerKey: payer.publicKey, // Public key of the account that will pay for the transaction +const message = new TransactionMessage({ + payerKey: payer.publicKey, // Account paying for the transaction recentBlockhash: blockhash, // Latest blockhash - instructions: [lookupTableInst, extendInstruction], // Instructions included in transaction + instructions: [lookupTableInst, extendInstruction], // Instructions to be included in the transaction }).compileToV0Message(); -// Create the versioned transaction using the message -const transaction = new web3.VersionedTransaction(message); +// Create the versioned transaction from the message +const transaction = new VersionedTransaction(message); // Sign the transaction transaction.sign([payer]); @@ -246,65 +234,62 @@ transaction.sign([payer]); const transactionSignature = await connection.sendTransaction(transaction); ``` -Note that when you first create or extend a lookup table, it needs to "warm up" -for one slot before the LUT or new addresses can be used in transactions. In -other words, you can only use lookup tables and access addresses that were added -prior to the current slot. +Note that after you create or extend a lookup table, it must "warm up" for one +slot before the lookup table or newly added addresses can be used in +transactions. You can only access lookup tables and addresses added in slots +prior to the current one. + +If you encounter the following error, it may indicate that you're trying to +access a lookup table or an address before the warm-up period has completed: ```typescript SendTransactionError: failed to send transaction: invalid transaction: Transaction address table lookup uses an invalid index ``` -If you encounter the error above or are unable to access addresses in a lookup -table immediately after extending it, it's likely because you're attempting to -access the lookup table or a specific address prior to the end of the warm up -period. To avoid this issue, add a delay after extending the lookup table before -sending a transaction that references the table. +To avoid this issue, ensure you add a delay after extending the lookup table +before attempting to reference the table in a transaction. #### Deactivate a lookup table -When a lookup table is no longer needed, you can deactivate and close it to -reclaim its rent balance. Address lookup tables can be deactivated at any time, -but they can continue to be used by transactions until a specified -"deactivation" slot is no longer "recent". This "cool-down" period ensures that -in-flight transactions can't be censored by LUTs being closed and recreated in -the same slot. The deactivation period is approximately 513 slots. +When a lookup table (LUT) is no longer needed, you can deactivate it to reclaim +its rent balance. Deactivating a LUT puts it into a "cool-down" period +(approximately 513 slots) during which it can still be used by transactions. +This prevents transactions from being censored by deactivating and recreating +LUTs within the same slot. -To deactivate an LUT, use the `deactivateLookupTable` method and pass in the -following parameters: +To deactivate a LUT, use the `deactivateLookupTable` method with the following +parameters: -- `lookupTable` - the address of the LUT to be deactivated -- `authority` - the account with permission to deactivate the LUT +- `lookupTable` - the address of the lookup table to be deactivated. +- `authority` - the account with the authority to deactivate the LUT. ```typescript -const deactivateInstruction = - web3.AddressLookupTableProgram.deactivateLookupTable({ - lookupTable: lookupTableAddress, // The address of the lookup table to deactivate - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - }); +const deactivateInstruction = AddressLookupTableProgram.deactivateLookupTable({ + lookupTable: lookupTableAddress, // Address of the lookup table to deactivate + authority: user.publicKey, // Authority to modify the lookup table +}); ``` #### Close a lookup table -To close a lookup table after its deactivation period, use the -`closeLookupTable` method. This method creates an instruction to close a -deactivated lookup table and reclaim its rent balance. It takes the following -parameters: +Once a LUT has been deactivated and the cool-down period has passed, you can +close the lookup table to reclaim its rent balance. Use the `closeLookupTable` +method, which requires the following parameters: -- `lookupTable` - the address of the LUT to be closed -- `authority` - the account with permission to close the LUT -- `recipient` - the account that will receive the reclaimed rent balance +- `lookupTable` - the address of the LUT to be closed. +- `authority` - the account with the authority to close the LUT. +- `recipient` - the account that will receive the reclaimed rent balance. ```typescript -const closeInstruction = web3.AddressLookupTableProgram.closeLookupTable({ - lookupTable: lookupTableAddress, // The address of the lookup table to close - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - recipient: user.publicKey, // The recipient of closed account lamports +const closeInstruction = AddressLookupTableProgram.closeLookupTable({ + lookupTable: lookupTableAddress, // Address of the lookup table to close + authority: user.publicKey, // Authority to close the LUT + recipient: user.publicKey, // Recipient of the reclaimed rent balance }); ``` -Attempting to close a lookup table before it's been fully deactivated will -result in an error. +Attempting to close a LUT before it has been fully deactivated will result in +the following error: ``` "Program AddressLookupTab1e1111111111111111111111111 invoke [1]", @@ -317,21 +302,21 @@ result in an error. In addition to standard CRUD operations, you can "freeze" a lookup table. This makes it immutable so that it can no longer be extended, deactivated, or closed. -You freeze a lookup table with the `freezeLookupTable` method. It takes the +The `freezeLookupTable` method is used for this operation and takes the following parameters: -- `lookupTable` - the address of the LUT to be frozen -- `authority` - the account with permission to freeze the LUT +- `lookupTable` - the address of the LUT to freeze. +- `authority` - the account with the authority to freeze the LUT. ```typescript -const freezeInstruction = web3.AddressLookupTableProgram.freezeLookupTable({ - lookupTable: lookupTableAddress, // The address of the lookup table to freeze - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) +const freezeInstruction = AddressLookupTableProgram.freezeLookupTable({ + lookupTable: lookupTableAddress, // Address of the lookup table to freeze + authority: user.publicKey, // Authority to freeze the LUT }); ``` -Once an LUT is frozen, any further attempts to modify it will result in an -error. +Once a LUT is frozen, any attempt to modify it will result in an error like the +following: ``` "Program AddressLookupTab1e1111111111111111111111111 invoke [1]", @@ -341,28 +326,29 @@ error. #### Using lookup tables in versioned transactions -To use a lookup table in a versioned transaction, you need to retrieve the -lookup table account using its address. +To utilize a lookup table in a versioned transaction, first retrieve the lookup +table account using its address: ```typescript +// Fetch the lookup table account from the blockchain using its address const lookupTableAccount = ( - await connection.getAddressLookupTable(lookupTableAddress) + await connection.getAddressLookupTable(new PublicKey(lookupTableAddress)) ).value; ``` -You can then create a list of instructions to include in a transaction as usual. -When creating the `TransactionMessage`, you can include any lookup table -accounts by passing them as an array to the `compileToV0Message()` method. You -can also provide multiple lookup table accounts. +Once you have the lookup table account, you can create the list of instructions +for the transaction. When constructing the `TransactionMessage`, pass the lookup +table accounts as an array to the `compileToV0Message()` method. You can include +multiple lookup table accounts if needed. ```typescript const message = new web3.TransactionMessage({ - payerKey: payer.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - recentBlockhash: blockhash, // The blockhash of the most recent block - instructions: instructions, // The instructions to include in the transaction + payerKey: payer.publicKey, // Public key of the account paying for the transaction + recentBlockhash: blockhash, // Blockhash of the most recent block + instructions: instructions, // Instructions to be included in the transaction }).compileToV0Message([lookupTableAccount]); // Include lookup table accounts -// Create the versioned transaction using the message +// Create a versioned transaction using the compiled message const transaction = new web3.VersionedTransaction(message); // Sign the transaction @@ -376,261 +362,324 @@ const transactionSignature = await connection.sendTransaction(transaction); Let's go ahead and practice using lookup tables! -this lab will guide you through the steps of creating, extending, and then using -a lookup table in a versioned transaction. - -#### 1. Get the starter code - -To begin, download the starter code from the starter branch of this -[repository](https://github.com/Unboxed-Software/solana-versioned-transactions/tree/starter). -Once you have the starter code, run `npm install` in the terminal to install the -required dependencies. - -The starter code includes an example of creating a legacy transaction that -intends to atomically transfer SOL to 22 recipients. The transaction contains 22 -instructions where each instruction transfers SOL from the signer to a different -recipient. - -The purpose of the starter code is to illustrate the limitation on the number of -addresses that can be included in a legacy transaction. The transaction built in -the starter code is expected to fail when sent. - -The following starter code can be found in the `index.ts` file. - -```typescript -import { initializeKeypair } from "./initializeKeypair"; -import * as web3 from "@solana/web3.js"; +This lab will guide you through creating, extending, and using a lookup table in +a versioned transaction. + +#### 1. Create the `try-large-transaction.ts` file + +To begin, create a new file named `try-large-transaction.ts` in your project +directory. This file will contain the code to illustrate a scenario where a +legacy transaction is created to transfer SOL to 22 recipients in a single +atomic transaction. The transaction will include 22 separate instructions, each +transferring SOL from the payer (signer) to a different recipient. + +This example highlights a key limitation of legacy transactions when trying to +accommodate many account addresses within a single transaction. As expected, +when attempting to send this transaction, it will likely fail due to exceeding +the transaction size limits. + +Here’s the code to include in `try-large-transaction.ts`: + +```typescript filename="try-large-transaction.ts" +import { + Connection, + clusterApiUrl, + Keypair, + Transaction, + SystemProgram, + LAMPORTS_PER_SOL, + sendAndConfirmTransaction, +} from "@solana/web3.js"; +import { + initializeKeypair, + makeKeypairs, + getExplorerLink, +} from "@solana-developers/helpers"; +import dotenv from "dotenv"; +dotenv.config(); async function main() { - // Connect to the devnet cluster - const connection = new web3.Connection(web3.clusterApiUrl("devnet")); + // Connect to the local Solana cluster + const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); - // Initialize the user's keypair - const user = await initializeKeypair(connection); - console.log("PublicKey:", user.publicKey.toBase58()); + // Initialize the keypair from the environment variable or create a new one + const payer = await initializeKeypair(connection); - // Generate 22 addresses - const recipients = []; - for (let i = 0; i < 22; i++) { - recipients.push(web3.Keypair.generate().publicKey); - } + // Generate 22 recipient keypairs using makeKeypairs + const recipients = makeKeypairs(22).map(keypair => keypair.publicKey); - // Create an array of transfer instructions - const transferInstructions = []; + // Create a legacy transaction + const transaction = new Transaction(); - // Add a transfer instruction for each address - for (const address of recipients) { - transferInstructions.push( - web3.SystemProgram.transfer({ - fromPubkey: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - toPubkey: address, // The destination account for the transfer - lamports: web3.LAMPORTS_PER_SOL * 0.01, // The amount of lamports to transfer + // Add 22 transfer instructions to the transaction + recipients.forEach(recipient => { + transaction.add( + SystemProgram.transfer({ + fromPubkey: payer.publicKey, + toPubkey: recipient, + lamports: LAMPORTS_PER_SOL * 0.01, // Transfer 0.01 SOL to each recipient }), ); - } - - // Create a transaction and add the transfer instructions - const transaction = new web3.Transaction().add(...transferInstructions); - - // Send the transaction to the cluster (this will fail in this example if addresses > 21) - const txid = await connection.sendTransaction(transaction, [user]); - - // Get the latest blockhash and last valid block height - const { lastValidBlockHeight, blockhash } = - await connection.getLatestBlockhash(); - - // Confirm the transaction - await connection.confirmTransaction({ - blockhash: blockhash, - lastValidBlockHeight: lastValidBlockHeight, - signature: txid, }); - // Log the transaction URL on the Solana Explorer - console.log(`https://explorer.solana.com/tx/${txid}?cluster=devnet`); + // Sign and send the transaction + try { + const signature = await sendAndConfirmTransaction(connection, transaction, [ + payer, + ]); + console.log( + `Transaction successful with signature: ${getExplorerLink("tx", signature, "devnet")}`, + ); + } catch (error) { + console.error("Transaction failed:", error); + } } ``` -To execute the code, run `npm start`. This will create a new keypair, write it -to the `.env` file, airdrop devnet SOL to the keypair, and send the transaction -built in the starter code. The transaction is expected to fail with the error -message `Transaction too large`. +To run the example, execute `npx esrun try-large-transaction.ts`. This process +will: + +- Generate a new keypair. +- Store the keypair details in the `.env` file. +- Request airdrop of devnet SOL to the generated keypair. +- Attempt to send the transaction. +- Since the transaction includes 22 instructions, it is expected to fail with + the error: "Transaction too large". ``` Creating .env file Current balance is 0 Airdropping 1 SOL... New balance is 1 -PublicKey: 5ZZzcDbabFHmoZU8vm3VzRzN5sSQhkf91VJzHAJGNM7B +PublicKey: 7YsGYC4EBs6Dxespe4ZM3wfCp856xULWoLw7QUcVb6VG Error: Transaction too large: 1244 > 1232 ``` -In the next steps, we'll go over how to use lookup tables with versioned -transactions to increase the number of addresses that can be included in a -single transaction. - -Before we start, go ahead and delete the content of the `main` function to leave -only the following: +#### 2. Create the `use-lookup-tables.ts` File + +Next, we'll explore how to use lookup tables in combination with versioned +transactions to overcome the limitation of legacy transactions and include a +greater number of addresses in a single transaction. + +Create a new file named `use-lookup-tables.ts` in your project directory. This +file will contain the code to demonstrate the use of lookup tables. + +Here’s the starter code to include in `use-lookup-tables.ts` file: + +```typescript filename="use-lookup-tables.ts" +import { + Connection, + clusterApiUrl, + Keypair, + TransactionInstruction, + AddressLookupTableAccount, + SystemProgram, + VersionedTransaction, + TransactionMessage, + AddressLookupTableProgram, + LAMPORTS_PER_SOL, + getSlot, +} from "@solana/web3.js"; +import { + initializeKeypair, + makeKeypairs, + getExplorerLink, +} from "@solana-developers/helpers"; +import dotenv from "dotenv"; +dotenv.config(); -```typescript async function main() { - // Connect to the devnet cluster - const connection = new web3.Connection(web3.clusterApiUrl("devnet")); + // Connect to the local Solana cluster + const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); - // Initialize the user's keypair - const user = await initializeKeypair(connection); - console.log("PublicKey:", user.publicKey.toBase58()); + // Initialize the keypair from the environment variable or create a new one + const payer = await initializeKeypair(connection); - // Generate 22 addresses - const addresses = []; - for (let i = 0; i < 22; i++) { - addresses.push(web3.Keypair.generate().publicKey); - } + // Generate 22 recipient keypairs using makeKeypairs + const recipients = makeKeypairs(22).map(keypair => keypair.publicKey); } ``` -#### 2. Create a `sendV0Transaction` helper function +Next, we will create a few helper functions that will be crucial for working +with versioned transactions and lookup tables. These functions will simplify our +process and make our code more modular and reusable. -We'll be sending multiple "version 0" transactions, so let's create a helper -function to facilitate this. +#### 3. Create a `sendV0Transaction` helper function -This function should take parameters for a connection, a user's keypair, an -array of transaction instructions, and an optional array of lookup table -accounts. +To handle versioned transactions, we will create a helper function in +`use-lookup-tables.ts` file, called `sendV0Transaction`, to simplify the +process. This function will accept the following parameters: -The function then performs the following tasks: +- `connection`: the solana connection to the cluster (e.g., devnet). +- `user`: the keypair of the user (payer) signing the transaction. +- `instructions`: an array of TransactionInstruction objects to include in the + transaction. +- `lookupTableAccounts` (optional): an array of lookup table accounts, if + applicable, to reference additional addresses. -- Retrieves the latest blockhash and last valid block height from the Solana - network -- Creates a new transaction message using the provided instructions -- Signs the transaction using the user's keypair -- Sends the transaction to the Solana network -- Confirms the transaction -- Logs the transaction URL on the Solana Explorer +This helper function will: -```typescript +- Retrieve the latest blockhash and last valid block height from the Solana + network. +- Compile a versioned transaction message using the provided instructions. +- Sign the transaction using the user's keypair. +- Send the transaction to the network. +- Confirm the transaction and log the transaction's URL using Solana Explorer. + +```typescript filename="use-lookup-tables.ts" async function sendV0Transaction( - connection: web3.Connection, - user: web3.Keypair, - instructions: web3.TransactionInstruction[], - lookupTableAccounts?: web3.AddressLookupTableAccount[], + connection: Connection, + user: Keypair, + instructions: TransactionInstruction[], + lookupTableAccounts?: AddressLookupTableAccount[], ) { // Get the latest blockhash and last valid block height - const { lastValidBlockHeight, blockhash } = + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); // Create a new transaction message with the provided instructions - const messageV0 = new web3.TransactionMessage({ + const messageV0 = new TransactionMessage({ payerKey: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) recentBlockhash: blockhash, // The blockhash of the most recent block instructions, // The instructions to include in the transaction - }).compileToV0Message(lookupTableAccounts ? lookupTableAccounts : undefined); - - // Create a new transaction object with the message - const transaction = new web3.VersionedTransaction(messageV0); + }).compileToV0Message(lookupTableAccounts); - // Sign the transaction with the user's keypair - transaction.sign([user]); + // Create a versioned transaction from the message + const transaction = new VersionedTransaction(messageV0); - // Send the transaction to the cluster - const txid = await connection.sendTransaction(transaction); - - // Confirm the transaction - await connection.confirmTransaction( + // Use the helper function to send and confirm the transaction + const txid = await sendAndConfirmTransactionV0( + connection, + transaction, + [user], { - blockhash: blockhash, - lastValidBlockHeight: lastValidBlockHeight, - signature: txid, + commitment: "finalized", // Ensures the transaction is confirmed at the highest level }, - "finalized", ); - // Log the transaction URL on the Solana Explorer - console.log(`https://explorer.solana.com/tx/${txid}?cluster=devnet`); + // Log the transaction URL on the Solana Explorer using the helper + const explorerLink = getExplorerLink("tx", txid, "devnet"); + console.log( + `Transaction successful! View it on Solana Explorer: ${explorerLink}`, + ); } ``` -#### 3. Create a `waitForNewBlock` helper function +#### 4. Create a `waitForNewBlock` helper function -Recall that lookup tables and the addresses contained in them can't be -referenced immediately after creation or extension. This means we'll end up -needing to wait for a new block before submitting transactions that reference -the newly created or extended lookup table. To make this simpler down the road, -let's create a `waitForNewBlock` helper function that we'll use to wait for -lookup tables to activate between sending transactions. +When working with lookup tables, it's important to remember that newly created +or extended lookup tables cannot be referenced immediately. Therefore, before +submitting transactions that reference these tables, we need to wait for a new +block to be generated. -This function will have parameters for a connection and a target block height. -It then starts an interval that checks the current block height of the network -every 1000ms. Once the new block height exceeds the target height, the interval -is cleared and the promise is resolved. +We will create a `waitForNewBlock` helper function that accepts: -```typescript -function waitForNewBlock(connection: web3.Connection, targetHeight: number) { - console.log(`Waiting for ${targetHeight} new blocks`); - return new Promise(async (resolve: any) => { - // Get the last valid block height of the blockchain - const { lastValidBlockHeight } = await connection.getLatestBlockhash(); +- `connection`: the Solana network connection. +- `targetBlockHeight`: the target block height to wait for. + +This function will: + +- Start an interval that checks the current block height of the network every + second (1000ms). +- Resolve the promise once the current block height exceeds the target block + height. - // Set an interval to check for new blocks every 1000ms +```typescript filename="use-lookup-tables.ts" +async function waitForNewBlock( + connection: Connection, + targetHeight: number, +): Promise { + console.log(`Waiting for ${targetHeight} new blocks...`); + + // Get the initial block height of the blockchain + const { lastValidBlockHeight: initialBlockHeight } = + await connection.getLatestBlockhash(); + + return new Promise(resolve => { + const SECOND = 1000; + const checkInterval = 1 * SECOND; // Interval to check for new blocks (1000ms) + + // Set an interval to check for new block heights const intervalId = setInterval(async () => { - // Get the new valid block height - const { lastValidBlockHeight: newValidBlockHeight } = - await connection.getLatestBlockhash(); - // console.log(newValidBlockHeight) - - // Check if the new valid block height is greater than the target block height - if (newValidBlockHeight > lastValidBlockHeight + targetHeight) { - // If the target block height is reached, clear the interval and resolve the promise + try { + // Get the current block height + const { lastValidBlockHeight: currentBlockHeight } = + await connection.getLatestBlockhash(); + + // If the current block height exceeds the target, resolve and clear interval + if (currentBlockHeight >= initialBlockHeight + targetHeight) { + clearInterval(intervalId); + console.log(`New block height reached: ${currentBlockHeight}`); + resolve(); + } + } catch (error) { + console.error("Error fetching block height:", error); clearInterval(intervalId); - resolve(); + resolve(); // Resolve to avoid hanging in case of errors } - }, 1000); + }, checkInterval); }); } ``` -#### 4. Create an `initializeLookupTable` function +#### 5. Create an `initializeLookupTable` function -Now that we have some helper functions ready to go, declare a function named -`initializeLookupTable`. This function has parameters `user`, `connection`, and -`addresses`. The function will: +Next, we need to initialize a lookup table to hold the addresses of the +recipients. The `initializeLookupTable` function will accept the following +parameters: -1. Retrieve the current slot -2. Generate an instruction for creating a lookup table -3. Generate an instruction for extending the lookup table with the provided - addresses -4. Send and confirm a transaction with the instructions for creating and - extending the lookup table -5. Return the address of the lookup table +- `user`: the user's keypair (payer and authority). +- `connection`: the Solana network connection. +- `addresses`: an array of recipient addresses (public keys) to add to the + lookup table. -```typescript +The function will: + +- Retrieve the current slot to derive the lookup table's address. +- Generate the necessary instructions to create and extend the lookup table with + the provided recipient addresses. +- Send and confirm a transaction that includes these instructions. +- Return the address of the newly created lookup table. + +Although the transaction includes the full recipient addresses, using the lookup +table allows Solana to reference those addresses with significantly fewer bytes +in the actual transaction. By including the lookup table in the versioned +transaction, the framework optimizes the transaction size, replacing addresses +with pointers to the lookup table. + +This design is crucial for enabling the transaction to support more recipients +by staying within Solana’s transaction size limits. + +```typescript filename="use-lookup-tables.ts" async function initializeLookupTable( - user: web3.Keypair, - connection: web3.Connection, - addresses: web3.PublicKey[], -): Promise { - // Get the current slot - const slot = await connection.getSlot(); + user: Keypair, + connection: Connection, + addresses: PublicKey[], +): Promise { + // Get the current slot using a helper function from @solana/web3.js + const slot = await getSlot(connection); // Create an instruction for creating a lookup table // and retrieve the address of the new lookup table const [lookupTableInst, lookupTableAddress] = - web3.AddressLookupTableProgram.createLookupTable({ - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - recentSlot: slot - 1, // The recent slot to derive lookup table's address + AddressLookupTableProgram.createLookupTable({ + authority: user.publicKey, // The authority to modify the lookup table + payer: user.publicKey, // The payer for transaction fees + recentSlot: slot - 1, // The slot for lookup table address derivation }); - console.log("lookup table address:", lookupTableAddress.toBase58()); + + console.log("Lookup Table Address:", lookupTableAddress.toBase58()); // Create an instruction to extend a lookup table with the provided addresses - const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({ - payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - lookupTable: lookupTableAddress, // The address of the lookup table to extend - addresses: addresses.slice(0, 30), // The addresses to add to the lookup table + const extendInstruction = AddressLookupTableProgram.extendLookupTable({ + payer: user.publicKey, // The payer of transaction fees + authority: user.publicKey, // The authority to extend the lookup table + lookupTable: lookupTableAddress, // Address of the lookup table to extend + addresses: addresses.slice(0, 30), // Add up to 30 addresses per instruction }); - await sendV0Transaction(connection, user, [ + // Use the helper function to send a versioned transaction + await sendVersionedTransaction(connection, user, [ lookupTableInst, extendInstruction, ]); @@ -639,73 +688,84 @@ async function initializeLookupTable( } ``` -#### 5. Modify `main` to use lookup tables +#### 6. Modify `main` to use lookup tables -Now that we can initialize a lookup table with all of the recipients' addresses, -let's update `main` to use versioned transactions and lookup tables. We'll need -to: +With the helper functions in place, we are now ready to modify the `main` +function to utilize versioned transactions and address lookup tables. To do so, +we will follow these steps: -1. Call `initializeLookupTable` -2. Call `waitForNewBlock` -3. Get the lookup table using `connection.getAddressLookupTable` -4. Create the transfer instruction for each recipient -5. Send the v0 transaction with all of the transfer instructions +1. Call `initializeLookupTable`: Create and extend the lookup table with the + recipients' addresses. +2. Call `waitForNewBlock`: Ensure the lookup table is activated by waiting for a + new block. +3. Retrieve the Lookup Table: Use `connection.getAddressLookupTabl`e to fetch + the lookup table and reference it in the transaction. +4. Create Transfer Instructions: Generate a transfer instruction for each + recipient. +5. Send the Versioned Transaction: Use `sendV0Transaction` to send a single + transaction with all transfer instructions, referencing the lookup table. -```typescript +```typescript filename="use-lookup-tables.ts" async function main() { - // Connect to the devnet cluster - const connection = new web3.Connection(web3.clusterApiUrl("devnet")); + // Connect to the devnet Solana cluster + const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); - // Initialize the user's keypair - const user = await initializeKeypair(connection); - console.log("PublicKey:", user.publicKey.toBase58()); - - // Generate 22 addresses - const recipients = []; - for (let i = 0; i < 22; i++) { - recipients.push(web3.Keypair.generate().publicKey); - } + // Initialize the keypair from the environment variable or create a new one + const payer = await initializeKeypair(connection); + // Generate 22 recipient keypairs using makeKeypairs + const recipients = makeKeypairs(22).map(keypair => keypair.publicKey); + // Initialize the lookup table with the generated recipients const lookupTableAddress = await initializeLookupTable( user, connection, recipients, ); + // Wait for a new block before using the lookup table await waitForNewBlock(connection, 1); + // Fetch the lookup table account const lookupTableAccount = ( await connection.getAddressLookupTable(lookupTableAddress) ).value; + // Check if the lookup table was successfully fetched if (!lookupTableAccount) { throw new Error("Lookup table not found"); } - const transferInstructions = recipients.map(recipient => { - return web3.SystemProgram.transfer({ - fromPubkey: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - toPubkey: recipient, // The destination account for the transfer - lamports: web3.LAMPORTS_PER_SOL * 0.01, // The amount of lamports to transfer - }); - }); + // Create transfer instructions for each recipient + const transferInstructions = recipients.map(recipient => + SystemProgram.transfer({ + fromPubkey: user.publicKey, // The payer + toPubkey: recipient, // The recipient + lamports: LAMPORTS_PER_SOL * 0.01, // Amount to transfer + }), + ); - await sendV0Transaction(connection, user, transferInstructions, [ - lookupTableAccount, - ]); + // Send the versioned transaction including the lookup table + const txid = await sendVersionedTransaction( + connection, + user, + transferInstructions, + [lookupTableAccount], + ); + + // Log the transaction link for easy access + console.log(`Transaction URL: ${getExplorerLink("tx", txid, "devnet")}`); } ``` -Notice that you create the transfer instructions with the full recipient address -even though we created a lookup table. That's because by including the lookup -table in the versioned transaction, you tell the `web3.js` framework to replace -any recipient addresses that match addresses in the lookup table with pointers -to the lookup table instead. By the time the transaction is sent to the network, -addresses that exist in the lookup table will be referenced by a single byte -rather than the full 32 bytes. +Even though we will create transfer instructions with full recipient addresses, +the use of lookup tables allows the `@solana/web3.js` framework to optimize the +transaction size. The addresses in the transaction that match entries in the +lookup table will be replaced with compact pointers referencing the lookup +table. By doing this, addresses will be represented using only a single byte in +the final transaction, significantly reducing the transaction's size. -Use `npm start` in the command line to execute the `main` function. You should -see an output similar to the following: +Use `npx esrun use-lookup-tables.ts` in the command line to execute the `main` +function. You should see an output similar to the following: ```bash Current balance is 1.38866636 @@ -726,7 +786,7 @@ Remember, this same transaction was failing when you first downloaded the starter code. Now that we're using lookup tables, we can do all 22 transfers in a single transaction. -#### 6. Add more address to the lookup table +#### 6. Add more addresses to the lookup table Keep in mind that the solution we've come up with so far only supports transfers to up to 30 accounts since we only extend the lookup table once. When you factor @@ -738,55 +798,54 @@ All we need to do is go into `initializeLookupTable` and do two things: 1. Modify the existing call to `extendLookupTable` to only add the first 30 addresses (any more than that and the transaction will be too large) -2. Add a loop that will keep extending a lookup table 30 addresses at a time +2. Add a loop that will keep extending a lookup table of 30 addresses at a time until all addresses have been added -```typescript +```typescript filename="use-lookup-tables.ts" async function initializeLookupTable( - user: web3.Keypair, - connection: web3.Connection, - addresses: web3.PublicKey[], -): Promise { + user: Keypair, + connection: Connection, + addresses: PublicKey[], +): Promise { // Get the current slot const slot = await connection.getSlot(); - // Create an instruction for creating a lookup table - // and retrieve the address of the new lookup table + // Create the lookup table and retrieve its address const [lookupTableInst, lookupTableAddress] = - web3.AddressLookupTableProgram.createLookupTable({ - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - recentSlot: slot - 1, // The recent slot to derive lookup table's address + AddressLookupTableProgram.createLookupTable({ + authority: user.publicKey, // The authority to modify the lookup table + payer: user.publicKey, // The payer for the transaction fees + recentSlot: slot - 1, // Recent slot to derive lookup table's address }); - console.log("lookup table address:", lookupTableAddress.toBase58()); + console.log("Lookup table address:", lookupTableAddress.toBase58()); - // Create an instruction to extend a lookup table with the provided addresses - const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({ - payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - lookupTable: lookupTableAddress, // The address of the lookup table to extend - addresses: addresses.slice(0, 30), // The addresses to add to the lookup table - }); + // Helper function to extend the lookup table in batches + const extendLookupTable = async (remainingAddresses: PublicKey[]) => { + while (remainingAddresses.length > 0) { + const toAdd = remainingAddresses.slice(0, 30); // Add up to 30 addresses + remainingAddresses = remainingAddresses.slice(30); - await sendV0Transaction(connection, user, [ - lookupTableInst, - extendInstruction, - ]); + const extendInstruction = AddressLookupTableProgram.extendLookupTable({ + payer: user.publicKey, + authority: user.publicKey, + lookupTable: lookupTableAddress, + addresses: toAdd, + }); - var remaining = addresses.slice(30); + // Send the transaction to extend the lookup table with the new addresses + await sendVersionedTransaction(connection, user, [extendInstruction]); + } + }; - while (remaining.length > 0) { - const toAdd = remaining.slice(0, 30); - remaining = remaining.slice(30); - const extendInstruction = web3.AddressLookupTableProgram.extendLookupTable({ - payer: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees) - authority: user.publicKey, // The authority (i.e., the account with permission to modify the lookup table) - lookupTable: lookupTableAddress, // The address of the lookup table to extend - addresses: toAdd, // The addresses to add to the lookup table - }); + // Send the initial transaction to create the lookup table and add the first 30 addresses + const initialBatch = addresses.slice(0, 30); + const remainingAddresses = addresses.slice(30); - await sendV0Transaction(connection, user, [extendInstruction]); - } + await sendVersionedTransaction(connection, user, [lookupTableInst]); + + // Extend the lookup table with the remaining addresses, if any + await extendLookupTable(initialBatch); + await extendLookupTable(remainingAddresses); return lookupTableAddress; } @@ -799,7 +858,7 @@ look at the final solution code you can ## Challenge -As a challenge, experiment with deactivating, closing and freezing lookup +As a challenge, experiment with deactivating, closing, and freezing lookup tables. Remember that you need to wait for a lookup table to finish deactivating before you can close it. Also, if a lookup table is frozen, it cannot be modified (deactivated or closed), so you will have to test separately or use From 27e73ed1fe41f4d2a044f7dfb9504ac5e13302b0 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Wed, 2 Oct 2024 18:29:29 +1000 Subject: [PATCH 22/54] Prettier. --- .../tokens-and-nfts/token-program-advanced.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/content/courses/tokens-and-nfts/token-program-advanced.md b/content/courses/tokens-and-nfts/token-program-advanced.md index 1fcff33e3..b979dd803 100644 --- a/content/courses/tokens-and-nfts/token-program-advanced.md +++ b/content/courses/tokens-and-nfts/token-program-advanced.md @@ -10,18 +10,21 @@ description: ### Summary -- **Burning tokens** reduces the total supply of a token by removing them - from circulation. -- **Approving a delegate**, allows another account to transfer or burn a specified amount of tokens from a token account while retaining original account ownership. -- **Revoking a delegate**, removes their authority to act on behalf of the token account owner. +- **Burning tokens** reduces the total supply of a token by removing them from + circulation. +- **Approving a delegate**, allows another account to transfer or burn a + specified amount of tokens from a token account while retaining original + account ownership. +- **Revoking a delegate**, removes their authority to act on behalf of the token + account owner. - Each of these operations is facilitated through the `spl-token` library, utilizing specific functions for each action. ### Lesson -In this lesson, we'll cover burning tokens and delegation. You may not have a need for these -in your own application, so if you're more interested in NFTs, feel free to skip -ahead to +In this lesson, we'll cover burning tokens and delegation. You may not have a +need for these in your own application, so if you're more interested in NFTs, +feel free to skip ahead to [creating NFTs with Metaplex](/content/courses/tokens-and-nfts/nfts-with-metaplex.md)! #### Burn Tokens From b28529f23f8cc1be8d5e0e5113e6c69216798fa6 Mon Sep 17 00:00:00 2001 From: Jimii <30603522+jim4067@users.noreply.github.com> Date: Thu, 3 Oct 2024 05:19:30 +0300 Subject: [PATCH 23/54] feat: update compressed-nfts.md to use umi and bubblegum plugin (#510) * feat: update course to use umi and bubblegum plugin * Update content/courses/state-compression/compressed-nfts.md Co-authored-by: Mike MacCana * feat: resolve requested changes * Update content/courses/state-compression/compressed-nfts.md --------- Co-authored-by: Mike MacCana --- .../state-compression/compressed-nfts.md | 1738 ++++++----------- .../unboxed/solana-explorer-create-tree.png | Bin 0 -> 95579 bytes ...na-explorer-showing-cnft-transfer-logs.png | Bin 0 -> 171868 bytes 3 files changed, 610 insertions(+), 1128 deletions(-) create mode 100644 public/assets/courses/unboxed/solana-explorer-create-tree.png create mode 100644 public/assets/courses/unboxed/solana-explorer-showing-cnft-transfer-logs.png diff --git a/content/courses/state-compression/compressed-nfts.md b/content/courses/state-compression/compressed-nfts.md index e0a7ba59d..63927fe34 100644 --- a/content/courses/state-compression/compressed-nfts.md +++ b/content/courses/state-compression/compressed-nfts.md @@ -1,27 +1,27 @@ --- title: Compressed NFTs objectives: - - Create a compressed NFT collection using Metaplex's Bubblegum program - - Mint compressed NFTs using the Bubblegum TS SDK - - Transfer compressed NFTs using the Bubblegum TS SDK + - Create a compressed NFT collection using Metaplex’s Bubblegum program + - Mint compressed NFTs using the Bubblegum program + - Transfer compressed NFTs using the Bubblegum program - Read compressed NFT data using the Read API description: "How to mint, transfer and read large-scale NFT collections using Metaplex's - Bubblegum SDK." + Bubblegum Program." --- ## Summary - **Compressed NFTs (cNFTs)** use **State Compression** to hash NFT data and store the hash onchain in an account using a **concurrent Merkle tree** - structure -- The cNFT data hash can't be used to infer the cNFT data, but it can be used to - **verify** if the cNFT data you're seeing is correct + structure. +- The cNFT data hash can’t be used to infer the cNFT data, but it can be used to + **verify** if the cNFT data you’re seeing is correct. - Supporting RPC providers **index** cNFT data offchain when the cNFT is minted so that you can use the **Read API** to access the data - The **Metaplex Bubblegum program** is an abstraction on top of the **State Compression** program that enables you to more simply create, mint, and manage - cNFT collections + cNFT collections. ## Lesson @@ -32,10 +32,10 @@ drastically reduces costs. Solana's transaction costs are so cheap that most users never think about how expensive minting NFTs can be at scale. The cost to set up and mint 1 million -traditional NFTs is approximately 24,000 SOL. By comparison, cNFTs can be -structured to where the same setup and mint costs 10 SOL or less. That means -anyone using NFTs at scale could cut costs by more than 1000x by using cNFTs -over traditional NFTs. +traditional NFTs using the Token Metadata Program is approximately 24,000 SOL. +By comparison, cNFTs can be structured to where the same setup and mint costs 10 +SOL or less. That means anyone using NFTs at scale could cut costs by more than +1000x by using cNFTs over traditional NFTs. However, cNFTs can be tricky to work with. Eventually, the tooling required to work with them will be sufficiently abstracted from the underlying technology @@ -47,16 +47,15 @@ pieces, so let's dig in! Most of the costs associated with traditional NFTs come down to account storage space. Compressed NFTs use a concept called State Compression to store data in -the blockchain's cheaper **ledger state**, using more expensive account space -only to store a “fingerprint”, or **hash**, of the data. This hash allows you to +the blockchain’s **ledger state**, only using the account state to store a +“fingerprint”, or **hash**, of the data. This hash allows you to cryptographically verify that data has not been tampered with. To both store hashes and enable verification, we use a special binary tree structure known as a **concurrent Merkle tree**. This tree structure lets us hash data together in a deterministic way to compute a single, final hash that -gets stored onchain. This final hash is significantly smaller in size than all -the original data combined, hence the “compression.” The steps to this process -are: +gets stored onchain. This final hash is significantly smaller than all the +original data combined, hence the “compression.” The steps to this process are: 1. Take any piece of data 2. Create a hash of this data @@ -80,11 +79,11 @@ track and index this data as the transactions occur. This ensures there is an offchain “cache” of the data that anyone can access and subsequently verify against the onchain root hash. -This process is _very complex_. We'll cover some of the key concepts below but -don't worry if you don't understand it right away. We'll talk more theory in the -state compression lesson and focus primarily on application to NFTs in this -lesson. You'll be able to work with cNFTs by the end of this lesson even if you -don't fully understand every piece of the state compression puzzle. +This process is _very complex_. We’ll cover some key concepts below but don’t +worry if you don’t understand it right away. We’ll talk more theory in the state +compression lesson and focus primarily on application to NFTs in this lesson. +You’ll be able to work with cNFTs by the end of this lesson even if you don’t +fully understand every piece of the state compression puzzle. #### Concurrent Merkle trees @@ -130,9 +129,8 @@ node adds 32 bytes to a transaction, so large trees would quickly exceed the maximum transaction size limit without caching proof nodes onchain. Each of these three values, max depth, max buffer size, and canopy depth, comes -with a tradeoff. Increasing the value of any of these values increases the size -of the account used to store the tree, thus increasing the cost to create the -tree. +with a tradeoff. Increasing any of these values increases the size of the +account used to store the tree, thus increasing the cost to create the tree. Choosing the max depth is fairly straightforward as it directly relates to the number of leafs and therefore the amount of data you can store. If you need @@ -183,8 +181,9 @@ the Noop instruction logs related to your data will vary based on the validator, but eventually you'll lose access to it if you're relying directly on instruction logs. -Technically, you _can_ replay transaction state back to the genesis block but -the average team isn't going to do that, and it certainly won't be performant. +Technically, you _can_ replay transaction state back to the genesis block, but +the average team isn’t going to do that, and it certainly won’t be performant. + Instead, you should use an indexer that will observe the events sent to the Noop program and store the relevant data off chain. That way you don't need to worry about old data becoming inaccessible. @@ -197,13 +196,9 @@ main point of this lesson: how to create a cNFT collection. Fortunately, you can use tools created by Solana Foundation, the Solana developer community, and Metaplex to simplify the process. Specifically, we'll be using the `@solana/spl-account-compression` SDK, the Metaplex Bubblegum -program, and the Bubblegum program's corresponding TS SDK -`@metaplex-foundation/mpl-bugglegum`. - -

#### Prepare metadata @@ -214,53 +209,56 @@ something like this: ```json { - "name": "12_217_47", - "symbol": "RGB", - "description": "Random RGB Color", - "seller_fee_basis_points": 0, - "image": "https://raw.githubusercontent.com/ZYJLiu/rgb-png-generator/master/assets/12_217_47/12_217_47.png", + "name": "My Collection", + "symbol": "MC", + "description": "My Collection description", + "image": "https://lvvg33dqzykc2mbfa4ifua75t73tchjnfjbcspp3n3baabugh6qq.arweave.net/XWpt7HDOFC0wJQcQWgP9n_cxHS0qQik9-27CAAaGP6E", "attributes": [ { - "trait_type": "R", - "value": "12" + "trait_type": "Background", + "value": "transparent" }, { - "trait_type": "G", - "value": "217" + "trait_type": "Shape", + "value": "sphere" }, { - "trait_type": "B", - "value": "47" + "trait_type": "Resolution", + "value": "1920x1920" } ] } ``` -Depending on your use case, you may be able to generate this dynamically or you -might want to have a JSON file prepared for each cNFT beforehand. You'll also -need any other assets referenced by the JSON, such as the `image` url shown in +Depending on your use case, you may be able to generate this dynamically, or you +might want to have a JSON file prepared for each cNFT beforehand. You’ll also +need any other assets referenced by the JSON, such as the `image` URL shown in the example above. #### Create Collection NFT -If you want your cNFTs to be part of a collection, you'll need to create a -Collection NFT **before** you start minting cNFTs. This is a traditional NFT -that acts as the reference binding your cNFTs together into a single collection. -You can create this NFT using the `@metaplex-foundation/js` library. Just make -sure you set `isCollection` to `true`. +NFTs are intrinsically unique, compared to fungible tokens which have a supply. +However, it is important to bind NFTs produced by the same series together, +using a Collection. Collections allow people to discover other NFTs in the same +collection, and verify that individual NFTs are actually members of the +Collection (and not look-alikes produced by someone else). + +To have your cNFTs to be part of a collection, you’ll need to create a +Collection NFT **before** you start minting cNFTs. This is a traditional Token +Metadata Program NFT that acts as the reference binding your cNFTs together into +a single collection. The procedure to create this NFT is outlined in our +[NFTs with Metaplex lesson](https://solana.com/developers/courses/tokens-and-nfts/nfts-with-metaplex#add-the-nft-to-a-collection) ```typescript -const collectionNft = await metaplex.nfts().create({ - uri: someUri, - name: "Collection NFT", - sellerFeeBasisPoints: 0, - updateAuthority: somePublicKey, - mintAuthority: somePublicKey, - tokenStandard: 0, - symbol: "Collection", - isMutable: true, - isCollection: true, -}); +const collectionMint = generateSigner(umi); + +await createNft(umi, { + mint: collectionMint, + name: `My Collection`, + uri, + sellerFeeBasisPoints: percentAmount(0), + isCollection: true, // mint as collection NFT +}).sendAndConfirm(umi); ``` #### Create Merkle tree Account @@ -326,215 +324,151 @@ is 32 bytes it's possible to max out transaction sizes very quickly. For example, if your tree has a very low canopy depth, an NFT marketplace may only be able to support simple NFTs transfers rather than support an onchain -bidding system for your cNFTs. The canopy effectively caches proof nodes onchain -so you don't have to pass all of them into the transaction, allowing for more -complex transactions. +bidding system for your cNFTs. The canopy effectively caches proof nodes +onchain, so you don’t have to pass all of them into the transaction, allowing +for more complex transactions. Increasing any of these three values increases the size of the account, thereby increasing the cost associated with creating it. Weigh the benefits accordingly when choosing the values. -Once you know these values, you can use the `createAllocTreeIx` helper function -from the `@solana/spl-account-compression` TS SDK to create the instruction for -creating the empty account. +Once you know these values, you can use the `createTree` method from the +`@metaplex-foundation/mpl-bubblegum` package to create your tree. This +instruction creates and initializes two accounts: -```typescript -import { createAllocTreeIx } from "@solana/spl-account-compression" +1. A `Merkle Tree` account - this holds the merkle hash and is used to verify + the authenticity of data stored. + +2. A `Tree Config` account - this holds additional data specific to compressed + NFTs such as the tree creator, whether the tree is public, and + [other fields - see the Bubblehum program source](https://github.com/metaplex-foundation/mpl-bubblegum/blob/42ffed35da6b2a673efacd63030a360eac3ae64e/programs/bubblegum/program/src/state/mod.rs#L17). + +#### Setting up Umi -const treeKeypair = Keypair.generate() +The `mpl-bubblegum` package is a plugin and cannot be used without the Umi +library from Metaplex. Umi is a framework for making JS/TS clients for onchain +programs that was created by Metaplex. -const allocTreeIx = await createAllocTreeIx( - connection, - treeKeypair.publicKey, - payer.publicKey, - { maxDepth: 20; maxBufferSize: 256 }, - canopyDepth -) +Note that Umi has different implementations for many concepts than web3.js, +including Keypairs, PublicKeys, and Connections. However, it is easy to convert +from web3.js versions of these items to the Umi equivalents. + +To get started, we need to create an Umi instance + +```typescript +import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; +import { clusterApiUrl } from "@solana/web3.js"; + +const umi = createUmi(clusterApiUrl("devnet")); ``` -Note that this is simply a helper function for calculating the size required by -the account and creating the instruction to send to the System Program for -allocating the account. This function doesn't interact with any -compression-specific programs yet. +The above code initializes an empty Umi instance without any signer or plugin +attached to it. You can find the exhaustive list of the plugins available +[on this Metaplex docs page](https://developers.metaplex.com/umi/metaplex-umi-plugins) -#### Use Bubblegum to Initialize Your Tree +The next part is to add in our imports and attach a signer to our Umi instance. -With the empty tree account created, you then use the Bubblegum program to -initialize the tree. In addition to the Merkle tree account, Bubblegum creates a -tree config account to add cNFT-specific tracking and functionality. +```typescript +import { dasApi } from "@metaplex-foundation/digital-asset-standard-api"; +import { createTree, mplBubblegum } from "@metaplex-foundation/mpl-bubblegum"; +import { keypairIdentity } from "@metaplex-foundation/umi"; +import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; +import { getKeypairFromFile } from "@solana-developers/helpers"; +import { clusterApiUrl } from "@solana/web3.js"; -Version 0.7 of the `@metaplex-foundation/mpl-bubblegum` TS SDK provides the -helper function `createCreateTreeInstruction` for calling the `create_tree` -instruction on the Bubblegum program. As part of the call, you'll need to derive -the `treeAuthority` PDA expected by the program. This PDA uses the tree's -address as a seed. +const umi = createUmi(clusterApiUrl("devnet")); + +// load keypair from local file system +// See https://github.com/solana-developers/helpers?tab=readme-ov-file#get-a-keypair-from-a-keypair-file +const localKeypair = await getKeypairFromFile(); + +// convert to Umi compatible keypair +const umiKeypair = umi.eddsa.createKeypairFromSecretKey(localKeypair.secretKey); + +// load the MPL Bubblegum program, dasApi plugin and assign a signer to our umi instance +umi.use(keypairIdentity(umiKeypair)).use(mplBubblegum()).use(dasApi()); + +console.log("Loaded UMI with Bubblegum"); +``` + +#### Use Bubblegum to Initialize Your Tree + +With Umi instantiated, we are ready to call the `createTree` method to +instantiate the Merkle tree and tree config accounts. ```typescript -import { - createAllocTreeIx, - SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - SPL_NOOP_PROGRAM_ID, -} from "@solana/spl-account-compression" -import { - PROGRAM_ID as BUBBLEGUM_PROGRAM_ID, - createCreateTreeInstruction, -} from "@metaplex-foundation/mpl-bubblegum" - -... - -const [treeAuthority, _bump] = PublicKey.findProgramAddressSync( - [treeKeypair.publicKey.toBuffer()], - BUBBLEGUM_PROGRAM_ID -) - -const createTreeIx = createCreateTreeInstruction( - { - treeAuthority, - merkleTree: treeKeypair.publicKey, - payer: payer.publicKey, - treeCreator: payer.publicKey, - logWrapper: SPL_NOOP_PROGRAM_ID, - compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - }, - { - maxBufferSize: 256, - maxDepth: 20, - public: false, - }, - BUBBLEGUM_PROGRAM_ID -) +const merkleTree = generateSigner(umi); +const builder = await createTree(umi, { + merkleTree, + maxDepth: 14, + maxBufferSize: 64, +}); +await builder.sendAndConfirm(umi); ``` -The list below shows the required input for this helper function: - -- `accounts` - An object representing the accounts required by the instruction. - This includes: - - `treeAuthority` - Bubblegum expects this to be a PDA derived using the - Merkle tree address as a seed - - `merkleTree` - The Merkle tree account - - `payer` - The address paying for transaction fees, rent, etc. - - `treeCreator` - The address to list as the tree creator - - `logWrapper` - The program to use to expose the data to indexers through - logs; this should be the address of the SPL Noop program unless you have - some other custom implementation - - `compressionProgram` - The compression program to use for initializing the - Merkle tree; this should be the address of the SPL State Compression program - unless you have some other custom implementation -- `args` - An object representing additional arguments required by the - instruction. This includes: - - `maxBufferSize` - The max buffer size of the Merkle tree - - `maxDepth` - The max depth of the Merkle tree - - `public` - When set to `true`, anyone will be able to mint cNFTs from the - tree; when set to `false`, only the tree creator or tree delegate will be - able to min cNFTs from the tree - -When submitted, this will invoke the `create_tree` instruction on the Bubblegum -program. This instruction does three things: - -1. Creates the tree config PDA account -2. Initializes the tree config account with appropriate initial values -3. Issues a CPI to the State Compression program to initialize the empty Merkle - tree account - -Feel free to take a look at the program code -[here](https://github.com/metaplex-foundation/mpl-bubblegum/blob/main/programs/bubblegum/program/src/lib.rs#L887). +The three values supplied i.e. the `merkleTree`, `maxDepth` and `maxBufferSize` +are required in order to create the tree while the rest are optional. For +example, the`tree creator` defaults to the Umi instance identity, while the +`public field to false. + +When set to true, `public` allows anyone to mint from the initialized tree and +if false, only the tree creator will be able to mint from the tree. + +Feel free to look at the code for the +[create_tree instruction handler](https://github.com/metaplex-foundation/mpl-bubblegum/blob/42ffed35da6b2a673efacd63030a360eac3ae64e/programs/bubblegum/program/src/processor/create_tree.rs#L40) +and +[create_tree's expected accounts](https://github.com/metaplex-foundation/mpl-bubblegum/blob/42ffed35da6b2a673efacd63030a360eac3ae64e/programs/bubblegum/program/src/processor/create_tree.rs#L20). #### Mint cNFTs With the Merkle tree account and its corresponding Bubblegum tree config account -initialized, it's possible to mint cNFTs to the tree. The Bubblegum instruction -to use will be either `mint_v1` or `mint_to_collection_v1`, depending on whether -or not you want to the minted cNFT to be part of a collection. +initialized, it’s possible to mint cNFTs to the tree. The Bubblegum library, +provides two instructions we can make use of depending on whether the minted +asset will belong to a collection. -Version 0.7 of the `@metaplex-foundation/mpl-bubblegum` TS SDK provides helper -functions `createMintV1Instruction` and `createMintToCollectionV1Instruction` to -make it easier for you to create the instructions. +The two instructions are -Both functions will require you to pass in the NFT metadata and a list of -accounts required to mint the cNFT. Below is an example of minting to a -collection: +1. **MintV1** ```typescript -const mintWithCollectionIx = createMintToCollectionV1Instruction( - { - payer: payer.publicKey, - merkleTree: treeAddress, - treeAuthority, - treeDelegate: payer.publicKey, - leafOwner: destination, - leafDelegate: destination, - collectionAuthority: payer.publicKey, - collectionAuthorityRecordPda: BUBBLEGUM_PROGRAM_ID, - collectionMint: collectionDetails.mint, - collectionMetadata: collectionDetails.metadata, - editionAccount: collectionDetails.masterEditionAccount, - compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - logWrapper: SPL_NOOP_PROGRAM_ID, - bubblegumSigner, - tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID, - }, - { - metadataArgs: Object.assign(nftMetadata, { - collection: { key: collectionDetails.mint, verified: false }, - }), +await mintV1(umi, { + leafOwner, + merkleTree, + metadata: { + name: "My Compressed NFT", + uri: "https://example.com/my-cnft.json", + sellerFeeBasisPoints: 0, // 0% + collection: none(), + creators: [ + { address: umi.identity.publicKey, verified: false, share: 100 }, + ], }, -); +}).sendAndConfirm(umi); ``` -Notice that there are two arguments for the helper function: `accounts` and -`args`. The `args` parameter is simply the NFT metadata, while `accounts` is an -object listing the accounts required by the instruction. There are admittedly a -lot of them: - -- `payer` - the account that will pay for the transaction fees, rent, etc. -- `merkleTree` - the Merkle tree account -- `treeAuthority` - the tree authority; should be the same PDA you derived - previously -- `treeDelegate` - the tree delegate; this is usually the same as the tree - creator -- `leafOwner` - the desired owner of the compressed NFT being minted -- `leafDelegate` - the desired delegate of the compressed NFT being minted; this - is usually the same as the leaf owner -- `collectionAuthority` - the authority of the collection NFT -- `collectionAuthorityRecordPda` - optional collection authority record PDA; - there typically is none, in which case you should put the Bubblegum program - address -- `collectionMint` - the mint account for the collection NFT -- `collectionMetadata` - the metadata account for the collection NFT -- `editionAccount` - the master edition account of the collection NFT -- `compressionProgram` - the compression program to use; this should be the - address of the SPL State Compression program unless you have some other custom - implementation -- `logWrapper` - the program to use to expose the data to indexers through logs; - this should be the address of the SPL Noop program unless you have some other - custom implementation -- `bubblegumSigner` - a PDA used by the Bubblegrum program to handle collection - verification -- `tokenMetadataProgram` - the token metadata program that was used for the - collection NFT; this is usually always the Metaplex Token Metadata program - -Minting without a collection requires fewer accounts, none of which are -exclusive to minting without a collection. You can take a look at the example -below. +2. **mintToCollectionV1** ```typescript -const mintWithoutCollectionIx = createMintV1Instruction( - { - payer: payer.publicKey, - merkleTree: treeAddress, - treeAuthority, - treeDelegate: payer.publicKey, - leafOwner: destination, - leafDelegate: destination, - compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - logWrapper: SPL_NOOP_PROGRAM_ID, - }, - { - message: nftMetadata, +await mintToCollectionV1(umi, { + leafOwner, + merkleTree, + collectionMint, + metadata: { + name: "My Compressed NFT", + uri: "https://example.com/my-cnft.json", + sellerFeeBasisPoints: 0, // 0% + collection: { key: collectionMint, verified: false }, + creators: [ + { address: umi.identity.publicKey, verified: false, share: 100 }, + ], }, -); +}).sendAndConfirm(umi); ``` +Both functions will require you to pass in the NFT metadata and a list of +accounts required to mint the cNFT such as the `leafOwner`, `merkleTree` account +etc. + ### Interact with cNFTs It's important to note that cNFTs _are not_ SPL tokens. That means your code @@ -544,17 +478,26 @@ fetching, querying, transferring, etc. #### Fetch cNFT data The simplest way to fetch data from an existing cNFT is to use the -[Digital Asset Standard Read API](https://solana.com/developers/guides/javascript/compressed-nfts#reading-compressed-nfts-metadata) -(Read API). Note that this is separate from the standard JSON RPC. To use the -Read API, you'll need to use a supporting RPC Provider. Metaplex maintains a -(likely non-exhaustive) -[list of RPC providers](https://developers.metaplex.com/bubblegum/rpcs) that -support the Read API. In this lesson we'll be using +[Digital Asset Standard Read API](https://developers.metaplex.com/das-api) (Read +API). Note that this is separate from the standard JSON RPC. To use the Read +API, you’ll need to use a supporting RPC Provider. Metaplex maintains a (likely +non-exhaustive) +[list of RPC providers that support the DAS Read API](https://developers.metaplex.com/rpc-providers#rpcs-with-das-support). + +In this lesson we’ll be using [Helius](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api) as they have free support for Devnet. -To use the Read API to fetch a specific cNFT, you need to have the cNFT's asset -ID. However, after minting cNFTs, you'll have at most two pieces of information: +You might need to update your RPC connection endpoint in the Umi instantiation + +```typescript +const umi = createUmi( + "https://devnet.helius-rpc.com/?api-key=YOUR-HELIUS-API-KEY", +); +``` + +To use the Read API to fetch a specific cNFT, you need to have the cNFT’s asset +ID. However, after minting cNFTs, you’ll have at most two pieces of information: 1. The transaction signature 2. The leaf index (possibly) @@ -562,8 +505,8 @@ ID. However, after minting cNFTs, you'll have at most two pieces of information: The only real guarantee is that you'll have the transaction signature. It is **possible** to locate the leaf index from there, but it involves some fairly complex parsing. The short story is you must retrieve the relevant instruction -logs from the Noop program and parse them to find the leaf index. We'll cover -this more in depth in a future lesson. For now, we'll assume you know the leaf +logs from the `Noop program` and parse them to find the leaf index. We’ll cover +this more in depth in a future lesson. For now, we’ll assume you know the leaf index. This is a reasonable assumption for most mints given that the minting will be @@ -579,32 +522,32 @@ ID and the following seeds: 2. The Merkle tree address 3. The leaf index -The indexer essentially observes transaction logs from the Noop program as they -happen and stores the cNFT metadata that was hashed and stored in the Merkle -tree. This enables them to surface that data when requested. This asset id is -what the indexer uses to identify the particular asset. +The indexer essentially observes transaction logs from the `Noop program` as +they happen and stores the cNFT metadata that was hashed and stored in the +Merkle tree. This enables them to surface that data when requested. This asset +ID is what the indexer uses to identify the particular asset. -For simplicity, you can just use the `getLeafAssetId` helper function from the -Bubblegum SDK. With the asset ID, fetching the cNFT is fairly straightforward. -Simply use the `getAsset` method provided by the supporting RPC provider: +For simplicity, you can just use the `findLeafAssetIdPda` helper function from +the Bubblegum library. ```typescript -const assetId = await getLeafAssetId(treeAddress, new BN(leafIndex)); -const response = await fetch(process.env.RPC_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "my-id", - method: "getAsset", - params: { - id: assetId, - }, - }), +const [assetId, bump] = await findLeafAssetIdPda(umi, { + merkleTree, + leafIndex, +}); +``` + +With the asset ID, fetching the cNFT is fairly straightforward. Simply use the +`getAsset` method provided by the supporting RPC provider and the `dasApi` +library: + +```typescript +const [assetId, bump] = await findLeafAssetIdPda(umi, { + merkleTree, + leafIndex, }); -const { result } = await response.json(); -console.log(JSON.stringify(result, null, 2)); +const rpcAsset = await umi.rpc.getAsset(assetId); ``` This will return a JSON object that is comprehensive of what a traditional NFT's @@ -662,138 +605,17 @@ In broad terms, this involves a five step process: 4. Prepare the asset proof as a list of `AccountMeta` objects 5. Build and send the Bubblegum transfer instruction -The first two steps are very similar. Using your supporting RPC provider, use -the `getAsset` and `getAssetProof` methods to fetch the asset data and proof, -respectively. +Fortunately, we can make use of the `transfer` method which takes care of all +these steps. ```typescript -const assetDataResponse = await fetch(process.env.RPC_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "my-id", - method: "getAsset", - params: { - id: assetId, - }, - }), -}); -const assetData = (await assetDataResponse.json()).result; - -const assetProofResponse = await fetch(process.env.RPC_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "my-id", - method: "getAssetProof", - params: { - id: assetId, - }, - }), -}); -const assetProof = (await assetProofResponse.json()).result; -``` - -The third step is to fetch the Merkle tree account. The simplest way to do this -is using the `ConcurrentMerkleTreeAccount` type from -`@solana/spl-account-compression`: - -```typescript -const treePublicKey = new PublicKey(assetData.compression.tree); - -const treeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( - connection, - treePublicKey, -); -``` - -Step four is the most conceptually challenging step. Using the three pieces of -information gathered, you'll need to assemble the proof path for the cNFT's -corresponding leaf. The proof path is represented as accounts passed to the -program instruction. The program uses each of the account addresses as proof -nodes to prove the leaf data is what you say it is. +const assetWithProof = await getAssetWithProof(umi, assetId); -The full proof is provided by the indexer as shown above in `assetProof`. -However, you can exclude the same number of tail-end accounts from the proof as -the depth of the canopy. - -```typescript -const canopyDepth = treeAccount.getCanopyDepth() || 0; - -const proofPath: AccountMeta[] = assetProof.proof - .map((node: string) => ({ - pubkey: new PublicKey(node), - isSigner: false, - isWritable: false, - })) - .slice(0, assetProof.proof.length - canopyDepth); -``` - -Finally, you can assemble the transfer instruction. The instruction helper -function, `createTransferInstruction`, requires the following arguments: - -- `accounts` - a list of instruction accounts, as expected; they are as follows: - - `merkleTree` - the Merkle tree account - - `treeAuthority` - the Merkle tree authority - - `leafOwner` - the owner of the leaf (cNFT) in question - - `leafDelegate` - the delegate of the leaf (cNFT) in question; if no delegate - has been added then this should be the same as `leafOwner` - - `newLeafOwner` - the address of the new owner post-transfer - - `logWrapper` - the program to use to expose the data to indexers through - logs; this should be the address of the SPL Noop program unless you have - some other custom implementation - - `compressionProgram` - the compression program to use; this should be the - address of the SPL State Compression program unless you have some other - custom implementation - - `anchorRemainingAccounts` - this is where you add the proof path -- `args` - additional arguments required by the instruction; they are: - - `root` - the root Merkle tree node from the asset proof; this is provided by - the indexer as a string and must be converted to bytes first - - `dataHash` - the hash of the asset data retrieved from the indexer; this is - provided by the indexer as a string and must be converted to bytes first - - `creatorHash` - the hash of the cNFT creator as retrieved from the indexer; - this is provided by the indexer as a string and must be converted to bytes - first - - `nonce` - used to ensure that no two leafs have the same hash; this value - should be the same as `index` - - `index` - the index where the cNFT's leaf is located on the Merkle tree - -An example of this is shown below. Note that the first 3 lines of code grab -additional information nested in the objects shown previously so they are ready -to go when assembling the instruction itself. - -```typescript -const treeAuthority = treeAccount.getAuthority(); -const leafOwner = new PublicKey(assetData.ownership.owner); -const leafDelegate = assetData.ownership.delegate - ? new PublicKey(assetData.ownership.delegate) - : leafOwner; - -const transferIx = createTransferInstruction( - { - merkleTree: treePublicKey, - treeAuthority, - leafOwner, - leafDelegate, - newLeafOwner: receiver, - logWrapper: SPL_NOOP_PROGRAM_ID, - compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - anchorRemainingAccounts: proofPath, - }, - { - root: [...new PublicKey(assetProof.root.trim()).toBytes()], - dataHash: [ - ...new PublicKey(assetData.compression.data_hash.trim()).toBytes(), - ], - creatorHash: [ - ...new PublicKey(assetData.compression.creator_hash.trim()).toBytes(), - ], - nonce: assetData.compression.leaf_id, - index: assetData.compression.leaf_id, - }, -); +await transfer(umi, { + ...assetWithProof, + leafOwner: currentLeafOwner, + newLeafOwner: newLeafOwner.publicKey, +}).sendAndConfirm(umi); ``` ### Conclusion @@ -802,15 +624,9 @@ We've covered the primary skills needed to interact with cNFTs, but haven't been fully comprehensive. You can also use Bubblegum to do things like burn, verify, delegate, and more. We won't go through these, but these instructions are similar to the mint and transfer process. If you need this additional -functionality, take a look at the -[Bubblegum client source code](https://github.com/metaplex-foundation/mpl-bubblegum/tree/main/clients/js-solita) -and leverage the helper functions it provides. - -Keep in mind that compression is fairly new. Available tooling will evolve -rapidly but the principles you've learned in this lesson will likely remain the -same. These principles can also be broadened to arbitrary state compression, so -be sure to master them here so you're ready for more fun stuff in future -lessons! +functionality, take a look at the +[Bubblegum docs](https://developers.metaplex.com/bubblegum) on how to leverage +the helper functions it provides. ## Lab @@ -820,540 +636,398 @@ from a Merkle tree. #### 1. Get the starter code -First things first, clone the starter code from the `starter` branch of our -[cNFT lab repository](https://github.com/Unboxed-Software/solana-cnft-demo). +To begin create and initialize an empty NPM project and change directory into +it. -`git clone https://github.com/Unboxed-Software/solana-cnft-demo.git` +```bash +mkdir cnft-demo +npm init -y +cd cnft-demo +``` -`cd solana-cnft-demo` +Install all the required dependencies -`npm install` +```bash +npm i @solana/web3.js @solana-developers/helpers@2.5.2 @metaplex-foundation/mpl-token-metadata @metaplex-foundation/mpl-bubblegum @metaplex-foundation/digital-asset-standard-api @metaplex-foundation/umi-bundle-defaults -Take some time to familiarize yourself with the starter code provided. Most -important are the helper functions provided in `utils.ts` and the URIs provided -in `uri.ts`. +npm i --save-dev esrun +``` -The `uri.ts` file provides 10k URIs that you can use for the offchain portion of -your NFT metadata. You can, of course, create your own metadata. But this lesson -isn't explicitly about preparing metadata so we've provided some for you. +In this first script, we will learn about creating a tree, hence let's create +the file `create-tree.ts` -The `utils.ts` file has a few helper functions to keep you from writing more -unnecessary boilerplate than you need to. They are as follows: +```bash +mkdir src && touch src/create-tree.ts +``` -- `getOrCreateKeypair` will create a new keypair for you and save it to a `.env` - file, or if there's already a private key in the `.env` file it will - initialize a keypair from that. -- `airdropSolIfNeeded` will airdrop some Devnet SOL to a specified address if - that address's balance is below 1 SOL. -- `createNftMetadata` will create the NFT metadata for a given creator public - key and index. The metadata it's getting is just dummy metadata using the URI - corresponding to the provided index from the `uri.ts` list of URIs. -- `getOrCreateCollectionNFT` will fetch the collection NFT from the address - specified in `.env` or if there is none it will create a new one and add the - address to `.env`. +This Umi instantiation code will be repated in a lot of files, so feel free to +create a wrapper file to instantiate it: -Finally, there's some boilerplate in `index.ts` that calls creates a new Devnet -connection, calls `getOrCreateKeypair` to initialize a “wallet,” and calls -`airdropSolIfNeeded` to fund the wallet if its balance is low. +```typescript filename="crate-tree.ts" +import { dasApi } from "@metaplex-foundation/digital-asset-standard-api"; +import { createTree, mplBubblegum } from "@metaplex-foundation/mpl-bubblegum"; +import { generateSigner, keypairIdentity } from "@metaplex-foundation/umi"; +import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; +import { + getExplorerLink, + getKeypairFromFile, +} from "@solana-developers/helpers"; +import { clusterApiUrl } from "@solana/web3.js"; -We will be writing all of our code in the `index.ts`. +const umi = createUmi(clusterApiUrl("devnet")); -#### 2. Create the Merkle tree account +// load keypair from local file system +// See https://github.com/solana-developers/helpers?tab=readme-ov-file#get-a-keypair-from-a-keypair-file +const localKeypair = await getKeypairFromFile(); -We'll start by creating the Merkle tree account. Let's encapsulate this in a -function that will eventually create _and_ initialize the account. We'll put it -below our `main` function in `index.ts`. Let's call it -`createAndInitializeTree`. For this function to work, it will need the following -parameters: - -- `connection` - a `Connection` to use for interacting with the network. -- `payer` - a `Keypair` that will pay for transactions. -- `maxDepthSizePair` - a `ValidDepthSizePair`. This type comes from - `@solana/spl-account-compression`. It's a simple object with properties - `maxDepth` and `maxBufferSize` that enforces a valid combination of the two - values. -- `canopyDepth` - a number for the canopy depth In the body of the function, - we'll generate a new address for the tree, then create the instruction for - allocating a new Merkle tree account by calling `createAllocTreeIx` from - `@solana/spl-account-compression`. +// convert to Umi compatible keypair +const umiKeypair = umi.eddsa.createKeypairFromSecretKey(localKeypair.secretKey); -```typescript -async function createAndInitializeTree( - connection: Connection, - payer: Keypair, - maxDepthSizePair: ValidDepthSizePair, - canopyDepth: number, -) { - const treeKeypair = Keypair.generate(); - - const allocTreeIx = await createAllocTreeIx( - connection, - treeKeypair.publicKey, - payer.publicKey, - maxDepthSizePair, - canopyDepth, - ); -} +// load the MPL Bubblegum program, dasApi plugin and assign a signer to our umi instance +umi.use(keypairIdentity(umiKeypair)).use(mplBubblegum()).use(dasApi()); ``` -#### 3. Use Bubblegum to initialize the Merkle tree and create the tree config account - -With the instruction for creating the tree ready to go, we can create an -instruction for invoking `create_tree` on the Bubblegum program. This will -initialize the Merkle tree account _and_ create a new tree config account on the -Bubblegum program. - -This instruction needs us to provide the following: - -- `accounts` - an object of required accounts; this includes: - - `treeAuthority` - this should be a PDA derived with the Merkle tree address - and the Bubblegum program - - `merkleTree` - the address of the Merkle tree - - `payer` - the transaction fee payer - - `treeCreator` - the address of the tree creator; we'll make this the same as - `payer` - - `logWrapper` - make this the `SPL_NOOP_PROGRAM_ID` - - `compressionProgram` - make this the `SPL_ACCOUNT_COMPRESSION_PROGRAM_ID` -- `args` - a list of instruction arguments; this includes: - - `maxBufferSize` - the buffer size from our function's `maxDepthSizePair` - parameter - - `maxDepth` - the max depth from our function's `maxDepthSizePair` parameter - - `public` - whether or no the tree should be public; we'll set this to - `false` - -Finally, we can add both instructions to a transaction and submit the -transaction. Keep in mind that the transaction needs to be signed by both the -`payer` and the `treeKeypair`. +In the code above, we load the user's keypair wallet from the system wallet +located at `.config/solana/id.json`, instantiate a new Umi instance and assign +the keypair to it. We also assign the Bubblegum and dasApi plugins to it as +well. -```typescript -async function createAndInitializeTree( - connection: Connection, - payer: Keypair, - maxDepthSizePair: ValidDepthSizePair, - canopyDepth: number, -) { - const treeKeypair = Keypair.generate(); - - const allocTreeIx = await createAllocTreeIx( - connection, - treeKeypair.publicKey, - payer.publicKey, - maxDepthSizePair, - canopyDepth, - ); - - const [treeAuthority, _bump] = PublicKey.findProgramAddressSync( - [treeKeypair.publicKey.toBuffer()], - BUBBLEGUM_PROGRAM_ID, - ); - - const createTreeIx = createCreateTreeInstruction( - { - treeAuthority, - merkleTree: treeKeypair.publicKey, - payer: payer.publicKey, - treeCreator: payer.publicKey, - logWrapper: SPL_NOOP_PROGRAM_ID, - compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - }, - { - maxBufferSize: maxDepthSizePair.maxBufferSize, - maxDepth: maxDepthSizePair.maxDepth, - public: false, - }, - ); - - const tx = new Transaction().add(allocTreeIx, createTreeIx); - tx.feePayer = payer.publicKey; - - try { - const txSignature = await sendAndConfirmTransaction( - connection, - tx, - [treeKeypair, payer], - { - commitment: "confirmed", - skipPreflight: true, - }, - ); - - console.log(`https://explorer.solana.com/tx/${txSignature}?cluster=devnet`); - - console.log("Tree Address:", treeKeypair.publicKey.toBase58()); - - return treeKeypair.publicKey; - } catch (err: any) { - console.error("\nFailed to create Merkle tree:", err); - throw err; - } -} +#### 2. Create the Merkle tree account + +We’ll start by creating the Merkle tree account. To do this we will use the +`createTree` method from Metaplex Bubblegum program. + +This function takes in three default values + +- `merkleTree` - The Merkle tree account address +- `maxDepth` - Determines the max number of leaves the tree will hold and + therefore the max number of cNFTs that the tree can contain. +- `maxBufferSize` - Determines how many concurrent changes can occur in the tree + in parallel. + +You can also supply in optional fields such as + +- `treeCreator` - The address of the tree authority, defaults to current + `umi.identity` instance. +- `public` - Determines whether anyone else apart from the tree creator will be + able to mint cNFTs from the tree. + +```typescript filename="crate-tree.ts" +const merkleTree = generateSigner(umi); +const builder = await createTree(umi, { + merkleTree, + maxDepth: 14, + maxBufferSize: 64, +}); +await builder.sendAndConfirm(umi); + +let explorerLink = getExplorerLink("address", merkleTree.publicKey, "devnet"); +console.log(`Explorer link: ${explorerLink}`); +console.log("Merkle tree address is :", merkleTree.publicKey); +console.log("✅ Finished successfully!"); ``` -If you want to test what you have so far, feel free to call -`createAndInitializeTree` from `main` and provide small values for the max depth -and max buffer size. +Run the `create-tree.ts` script using esrun -```typescript -async function main() { - const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); - const wallet = await getOrCreateKeypair("Wallet_1"); - await airdropSolIfNeeded(wallet.publicKey); - - const maxDepthSizePair: ValidDepthSizePair = { - maxDepth: 3, - maxBufferSize: 8, - }; - - const canopyDepth = 0; - - const treeAddress = await createAndInitializeTree( - connection, - wallet, - maxDepthSizePair, - canopyDepth, - ); -} +```bash +npx esrun create-tree.ts +``` + +Make sure to remember the Merkle tree address as we will be using it in the next +step when minting compressed NFTs. + +Your output will be similar to this + +```bash +Explorer link: https://explorer.solana.com/address/ZwzNxXw83PUmWSypXmqRH669gD3hF9rEjHWPpVghr5h?cluster=devnet +Merkle tree address is : ZwzNxXw83PUmWSypXmqRH669gD3hF9rEjHWPpVghr5h +✅ Finished successfully! ``` -Keep in mind that Devnet SOL is throttled so if you test too many times you -might run out of Devnet SOL before we get to minting. To test, in your terminal -run the following: +Congratulations! You've created a Bubblegum tree. Follow the Explorer link to +make sure that the process finished successfully, -`npm run start` +![Solana Explorer with details about created Merkle tree](/public/assets/courses/unboxed/solana-explorer-create-tree.png) -#### 4. Mint cNFTs to your tree +#### 3. Mint cNFTs to your tree Believe it or not, that's all you needed to do to set up your tree to compressed NFTs! Now let's turn our attention to minting. -First, let's declare a function called `mintCompressedNftToCollection`. It will -need the following parameters: - -- `connection` - a `Connection` to use for interacting with the network. -- `payer` - a `Keypair` that will pay for transactions. -- `treeAddress` - the Merkle tree's address -- `collectionDetails` - the details of the collection as type - `CollectionDetails` from `utils.ts` -- `amount` - the number of cNFTs to mint - -The body of this function will do the following: - -1. Derive the tree authority just like before. Again, this is a PDA derived from - the Merkle tree address and the Bubblegum program. -2. Derive the `bubblegumSigner`. This is a PDA derived from the string - `"collection_cpi"` and the Bubblegum program and is essential for minting to - a collection. -3. Create the cNFT metadata by calling `createNftMetadata` from our `utils.ts` - file. -4. Create the mint instruction by calling `createMintToCollectionV1Instruction` - from the Bubblegum SDK. -5. Build and send a transaction with the mint instruction -6. Repeat steps 3-6 `amount` number of times - -The `createMintToCollectionV1Instruction` takes two arguments: `accounts` and -`args`. The latter is simply the NFT metadata. As with all complex instructions, -the primary hurdle is knowing which accounts to provide. So let's go through -them real quick: - -- `payer` - the account that will pay for the transaction fees, rent, etc. -- `merkleTree` - the Merkle tree account -- `treeAuthority` - the tree authority; should be the same PDA you derived - previously -- `treeDelegate` - the tree delegate; this is usually the same as the tree - creator -- `leafOwner` - the desired owner of the compressed NFT being minted -- `leafDelegate` - the desired delegate of the compressed NFT being minted; this - is usually the same as the leaf owner -- `collectionAuthority` - the authority of the collection NFT -- `collectionAuthorityRecordPda` - optional collection authority record PDA; - there typically is none, in which case you should put the Bubblegum program - address -- `collectionMint` - the mint account for the collection NFT -- `collectionMetadata` - the metadata account for the collection NFT -- `editionAccount` - the master edition account of the collection NFT -- `compressionProgram` - the compression program to use; this should be the - address of the SPL State Compression program unless you have some other custom - implementation -- `logWrapper` - the program to use to expose the data to indexers through logs; - this should be the address of the SPL Noop program unless you have some other - custom implementation -- `bubblegumSigner` - a PDA used by the Bubblegrum program to handle collection - verification -- `tokenMetadataProgram` - the token metadata program that was used for the - collection NFT; this is usually always the Metaplex Token Metadata program - -When you put it all together, this is what it'll look like: +First, let's create a new file called `mint-compressed-nft-to-collection.ts`, +add our imports and instantiate Umi -```typescript -async function mintCompressedNftToCollection( - connection: Connection, - payer: Keypair, - treeAddress: PublicKey, - collectionDetails: CollectionDetails, - amount: number, -) { - // Derive the tree authority PDA ('TreeConfig' account for the tree account) - const [treeAuthority] = PublicKey.findProgramAddressSync( - [treeAddress.toBuffer()], - BUBBLEGUM_PROGRAM_ID, - ); - - // Derive the bubblegum signer, used by the Bubblegum program to handle "collection verification" - // Only used for `createMintToCollectionV1` instruction - const [bubblegumSigner] = PublicKey.findProgramAddressSync( - [Buffer.from("collection_cpi", "utf8")], - BUBBLEGUM_PROGRAM_ID, - ); - - for (let i = 0; i < amount; i++) { - // Compressed NFT Metadata - const compressedNFTMetadata = createNftMetadata(payer.publicKey, i); - - // Create the instruction to "mint" the compressed NFT to the tree - const mintIx = createMintToCollectionV1Instruction( - { - payer: payer.publicKey, // The account that will pay for the transaction - merkleTree: treeAddress, // The address of the tree account - treeAuthority, // The authority of the tree account, should be a PDA derived from the tree account address - treeDelegate: payer.publicKey, // The delegate of the tree account, should be the same as the tree creator by default - leafOwner: payer.publicKey, // The owner of the compressed NFT being minted to the tree - leafDelegate: payer.publicKey, // The delegate of the compressed NFT being minted to the tree - collectionAuthority: payer.publicKey, // The authority of the "collection" NFT - collectionAuthorityRecordPda: BUBBLEGUM_PROGRAM_ID, // Must be the Bubblegum program id - collectionMint: collectionDetails.mint, // The mint of the "collection" NFT - collectionMetadata: collectionDetails.metadata, // The metadata of the "collection" NFT - editionAccount: collectionDetails.masterEditionAccount, // The master edition of the "collection" NFT - compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - logWrapper: SPL_NOOP_PROGRAM_ID, - bubblegumSigner, - tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID, - }, - { - metadataArgs: Object.assign(compressedNFTMetadata, { - collection: { key: collectionDetails.mint, verified: false }, - }), - }, - ); - - try { - // Create new transaction and add the instruction - const tx = new Transaction().add(mintIx); - - // Set the fee payer for the transaction - tx.feePayer = payer.publicKey; - - // Send the transaction - const txSignature = await sendAndConfirmTransaction( - connection, - tx, - [payer], - { commitment: "confirmed", skipPreflight: true }, - ); - - console.log( - `https://explorer.solana.com/tx/${txSignature}?cluster=devnet`, - ); - } catch (err) { - console.error("\nFailed to mint compressed NFT:", err); - throw err; +```typescript filename="mint-compressed-nft-to-collection.ts" +import { dasApi } from "@metaplex-foundation/digital-asset-standard-api"; +import { + findLeafAssetIdPda, + LeafSchema, + mintToCollectionV1, + mplBubblegum, + parseLeafFromMintToCollectionV1Transaction, +} from "@metaplex-foundation/mpl-bubblegum"; +import { + keypairIdentity, + publicKey as UMIPublicKey, +} from "@metaplex-foundation/umi"; +import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; +import { getKeypairFromFile } from "@solana-developers/helpers"; +import { clusterApiUrl } from "@solana/web3.js"; + +const umi = createUmi(clusterApiUrl("devnet")); + +// load keypair from local file system +// See https://github.com/solana-developers/helpers?tab=readme-ov-file#get-a-keypair-from-a-keypair-file +const localKeypair = await getKeypairFromFile(); + +// convert to Umi compatible keypair +const umiKeypair = umi.eddsa.createKeypairFromSecretKey(localKeypair.secretKey); + +// load the MPL Bubblegum program, dasApi plugin and assign a signer to our umi instance +umi.use(keypairIdentity(umiKeypair)).use(mplBubblegum()).use(dasApi()); +``` + +I am going to be +[recycling a Collection NFT](https://explorer.solana.com/address/D2zi1QQmtZR5fk7wpA1Fmf6hTY2xy8xVMyNgfq6LsKy1?cluster=devnet) +I already created in the NFTs with Metaplex lesson, but if you'd like to create +a new collection for this lesson, check out the code +[on this repo](https://github.com/solana-developers/professional-education/blob/main/labs/metaplex-umi/create-collection.ts) + + +Find the code to create a Metaplex Collection NFT in our [NFTs with Metaplex lesson](https://solana.com/developers/courses/tokens-and-nfts/nfts-with-metaplex#add-the-nft-to-a-collection). + + +To mint a compressed NFT to a collection we will need + +- `leafOwner` - The recipient of the compressed NFT + +- `merkleTree` - The Merkle tree address we created in the previous step + +- `collection` - The collection our cNFT will belong to. This is not required, + and you can leave it out if your cNFT doesn't belong to a collection. + +- `metadata` - Your offchain metadata. This lesson won't focus onto how to + prepare your metadata, but you can check out the + [recommended structure from Metaplex](https://developers.metaplex.com/token-metadata/token-standard#the-non-fungible-standard). + +Our cNFT will use this structure we already prepared earlier. + +```json filename="nft.json" +{ + "name": "My NFT", + "symbol": "MN", + "description": "My NFT Description", + "image": "https://lycozm33rkk5ozjqldiuzc6drazmdp5d5g3g7foh3gz6rz5zp7va.arweave.net/XgTss3uKlddlMFjRTIvDiDLBv6Pptm-Vx9mz6Oe5f-o", + "attributes": [ + { + "trait_type": "Background", + "value": "transparent" + }, + { + "trait_type": "Shape", + "value": "sphere" } - } + ] } ``` -This is a great point to test with a small tree. Simply update `main` to call -`getOrCreateCollectionNFT` then `mintCompressedNftToCollection`: +Putting it all into code, we will have -```typescript -async function main() { - const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); - const wallet = await getOrCreateKeypair("Wallet_1"); - await airdropSolIfNeeded(wallet.publicKey); - - const maxDepthSizePair: ValidDepthSizePair = { - maxDepth: 3, - maxBufferSize: 8, - }; - - const canopyDepth = 0; - - const treeAddress = await createAndInitializeTree( - connection, - wallet, - maxDepthSizePair, - canopyDepth, - ); - - const collectionNft = await getOrCreateCollectionNFT(connection, wallet); - - await mintCompressedNftToCollection( - connection, - wallet, - treeAddress, - collectionNft, - 2 ** maxDepthSizePair.maxDepth, - ); -} +```typescript filename="mint-compressed-nft-to-collection.ts" +const merkleTree = UMIPublicKey("ZwzNxXw83PUmWSypXmqRH669gD3hF9rEjHWPpVghr5h"); + +const collectionMint = UMIPublicKey( + "D2zi1QQmtZR5fk7wpA1Fmf6hTY2xy8xVMyNgfq6LsKy1", +); + +const uintSig = await( + await mintToCollectionV1(umi, { + leafOwner: umi.identity.publicKey, + merkleTree, + collectionMint, + metadata: { + name: "My NFT", + uri: "https://chocolate-wet-narwhal-846.mypinata.cloud/ipfs/QmeBRVEmASS3pyK9YZDkRUtAham74JBUZQE3WD4u4Hibv9", + sellerFeeBasisPoints: 0, // 0% + collection: { key: collectionMint, verified: false }, + creators: [ + { + address: umi.identity.publicKey, + verified: false, + share: 100, + }, + ], + }, + }).sendAndConfirm(umi), +).signature; + +const b64Sig = base58.deserialize(uintSig); +console.log(b64Sig); ``` -Again, to run, in your terminal type: `npm run start` +The difference between the first statement is that we are returning the byte +array representing the transaction signature. -#### 5. Read existing cNFT data +We need this has in order to be able to get the leaf schema and with this schema +derive the asset ID. -Now that we've written code to mint cNFTs, let's see if we can actually fetch -their data. This is tricky because the onchain data is just the Merkle tree -account, the data from which can be used to verify existing information as -accurate but is useless in conveying what the information is. +```typescript filename="mint-compressed-nft-to-collection.ts" +const leaf: LeafSchema = await parseLeafFromMintToCollectionV1Transaction( + umi, + uintSig, +); +const assetId = findLeafAssetIdPda(umi, { + merkleTree, + leafIndex: leaf.nonce, +})[0]; +``` -Let's start by declaring a function `logNftDetails` that takes as parameters -`treeAddress` and `nftsMinted`. +With everything in place, we can now run our script +`mint-compressed-nft-to-collection.ts` + +```bash +npx esrun mint-compressed-nft-to-collection.ts +``` -At this point we don't actually have a direct identifier of any kind that points -to our cNFT. To get that, we'll need to know the leaf index that was used when -we minted our cNFT. We can then use that to derive the asset ID used by the Read -API and subsequently use the Read API to fetch our cNFT data. +Your output should resemble -In our case, we created a non-public tree and minted 8 cNFTs, so we know that -the leaf indexes used were 0-7. With this, we can use the `getLeafAssetId` -function from `@metaplex-foundation/mpl-bubblegum` to get the asset ID. +```bash +asset id: D4A8TYkKE5NzkqBQ4mPybgFbAUDN53fwJ64b8HwEEuUS +✅ Finished successfully! +``` + +We aren't returning the Explorer link because this address won't exists on the +Solana state but is indexed by RPCs that support the DAS API. -Finally, we can use an RPC that supports the -[Read API](https://solana.com/developers/guides/javascript/compressed-nfts) to -fetch the asset. We'll be using -[Helius](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api), -but feel free to choose your own RPC provider. To use Helius, you'll need to get -a free API Key from [the Helius website](https://dev.helius.xyz/). Then add your -`RPC_URL` to your `.env` file. For example: +In the next step we will query this address to fetch out cNFT details. +#### 4. Read existing cNFT data + +Now that we’ve written code to mint cNFTs, let’s see if we can actually fetch +their data. + +Create a new file `fetch-cnft-details.ts` + ```bash -## Add this -RPC_URL=https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY +fetch-cnft-details.ts ``` -Then simply issue a POST request to your provided RPC URL and put the `getAsset` -information in the body: +Import our packages and instantiate Umi. Here we will finally make use of the +`umi.use(dasApi())` we've been importing. -```typescript -async function logNftDetails(treeAddress: PublicKey, nftsMinted: number) { - for (let i = 0; i < nftsMinted; i++) { - const assetId = await getLeafAssetId(treeAddress, new BN(i)); - console.log("Asset ID:", assetId.toBase58()); - const response = await fetch(process.env.RPC_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "my-id", - method: "getAsset", - params: { - id: assetId, - }, - }), - }); - const { result } = await response.json(); - console.log(JSON.stringify(result, null, 2)); - } -} +In the instantiation of Umi, we are going to make a change to our connection +endpoint and use an RPC that supports the DAS API. + +Be sure to update this with your Helius API keys which you can get from the +[developer dashboard page](https://dashboard.helius.dev/signup?redirectTo=onboarding) + +```typescript filename="fetch-cnft-details.ts" +import { dasApi } from "@metaplex-foundation/digital-asset-standard-api"; +import { mplBubblegum } from "@metaplex-foundation/mpl-bubblegum"; +import { + keypairIdentity, + publicKey as UMIPublicKey, +} from "@metaplex-foundation/umi"; +import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; +import { getKeypairFromFile } from "@solana-developers/helpers"; + +const umi = createUmi( + "https://devnet.helius-rpc.com/?api-key=YOUR-HELIUS-API-KEY", +); + +// load keypair from local file system +// See https://github.com/solana-developers/helpers?tab=readme-ov-file#get-a-keypair-from-a-keypair-file +const localKeypair = await getKeypairFromFile(); + +// convert to Umi compatible keypair +const umiKeypair = umi.eddsa.createKeypairFromSecretKey(localKeypair.secretKey); + +// load the MPL Bubblegum program, dasApi plugin and assign a signer to our umi instance +umi.use(keypairIdentity(umiKeypair)).use(mplBubblegum()).use(dasApi()); +``` + +Fetching a compressed NFT details is as simple as calling the `getAsset` method +with the `assetId` from the previous step. + +```typescript filename="fetch-cnft-details.ts" +const assetId = UMIPublicKey("D4A8TYkKE5NzkqBQ4mPybgFbAUDN53fwJ64b8HwEEuUS"); + +// @ts-ignore +const rpcAsset = await umi.rpc.getAsset(assetId); +console.log(rpcAsset); ``` -Helius essentially observes transaction logs as they happen and stores the NFT -metadata that was hashed and stored in the Merkle tree. This enables them to -surface that data when requested. +Let’s start by declaring a function `logNftDetails` that takes as parameters +`treeAddress` and `nftsMinted`. -If we add a call to this function at the end of `main` and re-run your script, -the data we get back in the console is very comprehensive. It includes all of -the data you'd expect in both the onchain and offchain portion of a traditional -NFT. You can find the cNFT's attributes, files, ownership and creator -information, and more. +The output of our console.log would output ```json { - "interface": "V1_NFT", - "id": "48Bw561h1fGFK4JGPXnmksHp2fpniEL7hefEc6uLZPWN", - "content": { - "$schema": "https://schema.metaplex.com/nft1.0.json", - "json_uri": "https://raw.githubusercontent.com/Unboxed-Software/rgb-png-generator/master/assets/183_89_78/183_89_78.json", - "files": [ - { - "uri": "https://raw.githubusercontent.com/Unboxed-Software/rgb-png-generator/master/assets/183_89_78/183_89_78.png", - "cdn_uri": "https://cdn.helius-rpc.com/cdn-cgi/image//https://raw.githubusercontent.com/Unboxed-Software/rgb-png-generator/master/assets/183_89_78/183_89_78.png", - "mime": "image/png" - } - ], - "metadata": { - "attributes": [ - { - "value": "183", - "trait_type": "R" - }, - { - "value": "89", - "trait_type": "G" - }, - { - "value": "78", - "trait_type": "B" - } - ], - "description": "Random RGB Color", - "name": "CNFT", - "symbol": "CNFT" + interface: 'V1_NFT', + id: 'D4A8TYkKE5NzkqBQ4mPybgFbAUDN53fwJ64b8HwEEuUS', + content: { + '$schema': 'https://schema.metaplex.com/nft1.0.json', + json_uri: 'https://chocolate-wet-narwhal-846.mypinata.cloud/ipfs/QmeBRVEmASS3pyK9YZDkRUtAham74JBUZQE3WD4u4Hibv9', + files: [ [Object] ], + metadata: { + attributes: [Array], + description: 'My NFT Description', + name: 'My NFT', + symbol: '', + token_standard: 'NonFungible' }, - "links": { - "image": "https://raw.githubusercontent.com/Unboxed-Software/rgb-png-generator/master/assets/183_89_78/183_89_78.png" + links: { + image: 'https://lycozm33rkk5ozjqldiuzc6drazmdp5d5g3g7foh3gz6rz5zp7va.arweave.net/XgTss3uKlddlMFjRTIvDiDLBv6Pptm-Vx9mz6Oe5f-o' } }, - "authorities": [ + authorities: [ { - "address": "DeogHav5T2UV1zf5XuH4DTwwE5fZZt7Z4evytUUtDtHd", - "scopes": ["full"] + address: '4sk8Ds1T4bYnN4j23sMbVyHYABBXQ53NoyzVrXGd3ja4', + scopes: [Array] } ], - "compression": { - "eligible": false, - "compressed": true, - "data_hash": "3RsXHMBDpUPojPLZuMyKgZ1kbhW81YSY3PYmPZhbAx8K", - "creator_hash": "Di6ufEixhht76sxutC9528H7PaWuPz9hqTaCiQxoFdr", - "asset_hash": "2TwWjQPdGc5oVripPRCazGBpAyC5Ar1cia8YKUERDepE", - "tree": "7Ge8nhDv2FcmnpyfvuWPnawxquS6gSidum38oq91Q7vE", - "seq": 8, - "leaf_id": 7 + compression: { + eligible: false, + compressed: true, + data_hash: '2UgKwnTkguefRg3P5J33UPkNebunNMFLZTuqvnBErqhr', + creator_hash: '4zKvSQgcRhJFqjQTeCjxuGjWydmWTBVfCB5eK4YkRTfm', + asset_hash: '2DwKkMFYJHDSgTECiycuBApMt65f3N1ZwEbRugRZymwJ', + tree: 'ZwzNxXw83PUmWSypXmqRH669gD3hF9rEjHWPpVghr5h', + seq: 4, + leaf_id: 3 }, - "grouping": [ + grouping: [ { - "group_key": "collection", - "group_value": "9p2RqBUAadMznAFiBEawMJnKR9EkFV98wKgwAz8nxLmj" + group_key: 'collection', + group_value: 'D2zi1QQmtZR5fk7wpA1Fmf6hTY2xy8xVMyNgfq6LsKy1' } ], - "royalty": { - "royalty_model": "creators", - "target": null, - "percent": 0, - "basis_points": 0, - "primary_sale_happened": false, - "locked": false + royalty: { + royalty_model: 'creators', + target: null, + percent: 0, + basis_points: 0, + primary_sale_happened: false, + locked: false }, - "creators": [ + creators: [ { - "address": "HASk3AoTPAvC1KnXSo6Qm73zpkEtEhbmjLpXLgvyKBkR", - "share": 100, - "verified": false + address: '4kg8oh3jdNtn7j2wcS7TrUua31AgbLzDVkBZgTAe44aF', + share: 100, + verified: false } ], - "ownership": { - "frozen": false, - "delegated": false, - "delegate": null, - "ownership_model": "single", - "owner": "HASk3AoTPAvC1KnXSo6Qm73zpkEtEhbmjLpXLgvyKBkR" + ownership: { + frozen: false, + delegated: false, + delegate: null, + ownership_model: 'single', + owner: '4kg8oh3jdNtn7j2wcS7TrUua31AgbLzDVkBZgTAe44aF' }, - "supply": { - "print_max_supply": 0, - "print_current_supply": 0, - "edition_nonce": 0 - }, - "mutable": false, - "burnt": false + supply: { print_max_supply: 0, print_current_supply: 0, edition_nonce: null }, + mutable: true, + burnt: false } ``` @@ -1362,274 +1036,85 @@ owner, creator, etc., and more. Be sure to look through the [Helius docs](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api) to see what's available. -#### 6. Transfer a cNFT +#### 5. Transfer a cNFT The last thing we're going to add to our script is a cNFT transfer. Just as with a standard SPL token transfer, security is paramount. Unlike with a standard SPL token transfer, however, to build a secure transfer with state compression of any kind, the program performing the transfer needs the entire asset data. -The program, Bubblegum in this case, needs to be provided with the entire data -that was hashed and stored on the corresponding leaf _and_ needs to be given the -“proof path” for the leaf in question. That makes cNFT transfers a bit trickier -than SPL token transfers. +Fortunately for us can get the asset data with the `getAssetWithProof` method. -Remember, the general steps are: +Le't first create a new file `transfer-asset.ts`, and populate it with the code +for instantiating a new Umi client. -1. Fetch the cNFT's asset data from the indexer -2. Fetch the cNFT's proof from the indexer -3. Fetch the Merkle tree account from the Solana blockchain -4. Prepare the asset proof as a list of `AccountMeta` objects -5. Build and send the Bubblegum transfer instruction +```typescript filename="transfer-asset.ts" +import { dasApi } from "@metaplex-foundation/digital-asset-standard-api"; +import { + getAssetWithProof, + mplBubblegum, + transfer, +} from "@metaplex-foundation/mpl-bubblegum"; +import { + keypairIdentity, + publicKey as UMIPublicKey, +} from "@metaplex-foundation/umi"; +import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; +import { base58 } from "@metaplex-foundation/umi/serializers"; +import { + getExplorerLink, + getKeypairFromFile, +} from "@solana-developers/helpers"; +import { clusterApiUrl } from "@solana/web3.js"; -Let's start by declaring a `transferNft` function that takes the following: +const umi = createUmi(clusterApiUrl("devnet")); -- `connection` - a `Connection` object -- `assetId` - a `PublicKey` object -- `sender` - a `Keypair` object so we can sign the transaction -- `receiver` - a `PublicKey` object representing the new owner +// load keypair from local file system +// See https://github.com/solana-developers/helpers?tab=readme-ov-file#get-a-keypair-from-a-keypair-file +const localKeypair = await getKeypairFromFile(); -Inside that function, let's fetch the asset data again then also fetch the asset -proof. For good measure, let's wrap everything in a `try catch`. +// convert to Umi compatible keypair +const umiKeypair = umi.eddsa.createKeypairFromSecretKey(localKeypair.secretKey); -```typescript -async function transferNft( - connection: Connection, - assetId: PublicKey, - sender: Keypair, - receiver: PublicKey, -) { - try { - const assetDataResponse = await fetch(process.env.RPC_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "my-id", - method: "getAsset", - params: { - id: assetId, - }, - }), - }); - const assetData = (await assetDataResponse.json()).result; - - const assetProofResponse = await fetch(process.env.RPC_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "my-id", - method: "getAssetProof", - params: { - id: assetId, - }, - }), - }); - const assetProof = (await assetProofResponse.json()).result; - } catch (err: any) { - console.error("\nFailed to transfer nft:", err); - throw err; - } -} +// load the MPL Bubblegum program, dasApi plugin and assign a signer to our umi instance +umi.use(keypairIdentity(umiKeypair)).use(mplBubblegum()).use(dasApi()); ``` -Next, let's fetch the Merkle tree account from the chain, get the canopy depth, -and assemble the proof path. We do this by mapping the asset proof we got from -Helius to a list of `AccountMeta` objects, then removing any proof nodes at the -end that are already cached onchain in the canopy. +We are not ready to transfer our asset. Using the `assetId` for our cNFT, we can +call the `transfer` method from the Bubblegum library -```typescript -async function transferNft( - connection: Connection, - assetId: PublicKey, - sender: Keypair, - receiver: PublicKey -) { - try { - ... - - const treePublicKey = new PublicKey(assetData.compression.tree) - - const treeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( - connection, - treePublicKey - ) - - const canopyDepth = treeAccount.getCanopyDepth() || 0 - - const proofPath: AccountMeta[] = assetProof.proof - .map((node: string) => ({ - pubkey: new PublicKey(node), - isSigner: false, - isWritable: false, - })) - .slice(0, assetProof.proof.length - canopyDepth) - } catch (err: any) { - console.error("\nFailed to transfer nft:", err) - throw err - } -} -``` +```typescript filename="transfer-asset.ts" +const assetId = UMIPublicKey("D4A8TYkKE5NzkqBQ4mPybgFbAUDN53fwJ64b8HwEEuUS"); -Finally, we build the instruction using `createTransferInstruction`, add it to a -transaction, then sign and send the transaction. This is what the entire -`transferNft` function looks like when finished: +//@ts-ignore +const assetWithProof = await getAssetWithProof(umi, assetId); -```typescript -async function transferNft( - connection: Connection, - assetId: PublicKey, - sender: Keypair, - receiver: PublicKey, -) { - try { - const assetDataResponse = await fetch(process.env.RPC_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "my-id", - method: "getAsset", - params: { - id: assetId, - }, - }), - }); - const assetData = (await assetDataResponse.json()).result; - - const assetProofResponse = await fetch(process.env.RPC_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "my-id", - method: "getAssetProof", - params: { - id: assetId, - }, - }), - }); - const assetProof = (await assetProofResponse.json()).result; - - const treePublicKey = new PublicKey(assetData.compression.tree); - - const treeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( - connection, - treePublicKey, - ); - - const canopyDepth = treeAccount.getCanopyDepth() || 0; - - const proofPath: AccountMeta[] = assetProof.proof - .map((node: string) => ({ - pubkey: new PublicKey(node), - isSigner: false, - isWritable: false, - })) - .slice(0, assetProof.proof.length - canopyDepth); - - const treeAuthority = treeAccount.getAuthority(); - const leafOwner = new PublicKey(assetData.ownership.owner); - const leafDelegate = assetData.ownership.delegate - ? new PublicKey(assetData.ownership.delegate) - : leafOwner; - - const transferIx = createTransferInstruction( - { - merkleTree: treePublicKey, - treeAuthority, - leafOwner, - leafDelegate, - newLeafOwner: receiver, - logWrapper: SPL_NOOP_PROGRAM_ID, - compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, - anchorRemainingAccounts: proofPath, - }, - { - root: [...new PublicKey(assetProof.root.trim()).toBytes()], - dataHash: [ - ...new PublicKey(assetData.compression.data_hash.trim()).toBytes(), - ], - creatorHash: [ - ...new PublicKey(assetData.compression.creator_hash.trim()).toBytes(), - ], - nonce: assetData.compression.leaf_id, - index: assetData.compression.leaf_id, - }, - ); - - const tx = new Transaction().add(transferIx); - tx.feePayer = sender.publicKey; - const txSignature = await sendAndConfirmTransaction( - connection, - tx, - [sender], - { - commitment: "confirmed", - skipPreflight: true, - }, - ); - console.log(`https://explorer.solana.com/tx/${txSignature}?cluster=devnet`); - } catch (err: any) { - console.error("\nFailed to transfer nft:", err); - throw err; - } -} +let uintSig = await( + await transfer(umi, { + ...assetWithProof, + leafOwner: umi.identity.publicKey, + newLeafOwner: UMIPublicKey("J63YroB8AwjDVjKuxjcYFKypVM3aBeQrfrVmNBxfmThB"), + }).sendAndConfirm(umi), +).signature; + +const b64sig = base58.deserialize(uintSig); + +let explorerLink = getExplorerLink("transaction", b64sig, "devnet"); +console.log(`Explorer link: ${explorerLink}`); +console.log("✅ Finished successfully!"); ``` -Lets transfer our first compressed NFT at index 0 to someone else. First we'll -need to spin up another wallet with some funds, then grab the assetID at index 0 -using `getLeafAssetId`. Then we'll do the transfer. Finally, we'll print out the -entire collection using our function `logNftDetails`. You'll note that the NFT -at index zero will now belong to our new wallet in the `ownership` field. +Running our script with `npx esrun transfer-asset.ts`, should output something +similar to this if successful: -```typescript -async function main() { - const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); - const wallet = await getOrCreateKeypair("Wallet_1"); - await airdropSolIfNeeded(wallet.publicKey); - - const maxDepthSizePair: ValidDepthSizePair = { - maxDepth: 3, - maxBufferSize: 8, - }; - - const canopyDepth = 0; - - const treeAddress = await createAndInitializeTree( - connection, - wallet, - maxDepthSizePair, - canopyDepth, - ); - - const collectionNft = await getOrCreateCollectionNFT(connection, wallet); - - await mintCompressedNftToCollection( - connection, - wallet, - treeAddress, - collectionNft, - 2 ** maxDepthSizePair.maxDepth, - ); - - const recieverWallet = await getOrCreateKeypair("Wallet_2"); - const assetId = await getLeafAssetId(treeAddress, new BN(0)); - await airdropSolIfNeeded(recieverWallet.publicKey); - - console.log( - `Transfering ${assetId.toString()} from ${wallet.publicKey.toString()} to ${recieverWallet.publicKey.toString()}`, - ); - - await transferNft(connection, assetId, wallet, recieverWallet.publicKey); - - await logNftDetails(treeAddress, 8); -} +```bash +Explorer link: https://explorer.solana.com/tx/3sNgN7Gnh5FqcJ7ZuUEXFDw5WeojpwkDjdfvTNWy68YCEJUF8frpnUJdHhHFXAtoopsytzkKewh39Rf7phFQ2hCF?cluster=devnet +✅ Finished successfully! ``` -Go ahead and run your script. The whole thing should execute without failing, -and all for close to 0.01 SOL! +Open the explorer link, and scroll to the bottom to observer your tx logs, + +![Solana Explorer showing logs of the transfer cnft instruction](/public/assets/courses/unboxed/solana-explorer-showing-cnft-transfer-logs.png) Congratulations! Now you know how to mint, read, and transfer cNFTs. If you wanted, you could update the max depth, max buffer size, and canopy depth to @@ -1637,12 +1122,9 @@ larger values and as long as you have enough Devnet SOL, this script will let you mint up to 10k cNFTs for a small fraction of what it would cost to mint 10k traditional NFTs. -If you plan to mint a large amount of NFTs you might want -to try and batch these instructions for fewer total transactions. - -If you need more time with this lab, feel free to go through it again and/or -take a look at the solution code on the `solution` branch of the -[lab repo](https://github.com/Unboxed-Software/solana-cnft-demo/tree/solution). +Inspect the cNFT on Solana Explorer! Just like previously, if you have any +issues, you should fix them yourself, but if needed the +[solution code](https://github.com/solana-foundation/compressed-nfts) is available. ### Challenge diff --git a/public/assets/courses/unboxed/solana-explorer-create-tree.png b/public/assets/courses/unboxed/solana-explorer-create-tree.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe10dff553127d1530749b942bc949bb0b78075 GIT binary patch literal 95579 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 literal 0 HcmV?d00001 diff --git a/public/assets/courses/unboxed/solana-explorer-showing-cnft-transfer-logs.png b/public/assets/courses/unboxed/solana-explorer-showing-cnft-transfer-logs.png new file mode 100644 index 0000000000000000000000000000000000000000..2cf699e0ba5b7678776a92bad0b333307ced1104 GIT binary patch literal 171868 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�HIk%`m3Tr-_V-r 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 literal 0 HcmV?d00001 From 04b79a968a3020db1985884b295e1ada0918fdc2 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Thu, 3 Oct 2024 13:24:24 +1000 Subject: [PATCH 24/54] Fix headline missed in PR --- content/courses/state-compression/compressed-nfts.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/courses/state-compression/compressed-nfts.md b/content/courses/state-compression/compressed-nfts.md index 63927fe34..2b29101b4 100644 --- a/content/courses/state-compression/compressed-nfts.md +++ b/content/courses/state-compression/compressed-nfts.md @@ -199,7 +199,6 @@ be using the `@solana/spl-account-compression` SDK, the Metaplex Bubblegum program `@metaplex-foundation/mpl-bubblegum` through the Umi library from Metaplex. - #### Prepare metadata Prior to starting, you'll prepare your NFT metadata similarly to how you would @@ -624,7 +623,7 @@ We've covered the primary skills needed to interact with cNFTs, but haven't been fully comprehensive. You can also use Bubblegum to do things like burn, verify, delegate, and more. We won't go through these, but these instructions are similar to the mint and transfer process. If you need this additional -functionality, take a look at the +functionality, take a look at the [Bubblegum docs](https://developers.metaplex.com/bubblegum) on how to leverage the helper functions it provides. @@ -634,7 +633,7 @@ Let's jump in and practice creating and working with cNFTs. Together, we'll build as simple a script as possible that will let us mint a cNFT collection from a Merkle tree. -#### 1. Get the starter code +#### 1. Create a new project To begin create and initialize an empty NPM project and change directory into it. @@ -693,7 +692,7 @@ the keypair to it. We also assign the Bubblegum and dasApi plugins to it as well. #### 2. Create the Merkle tree account - + We’ll start by creating the Merkle tree account. To do this we will use the `createTree` method from Metaplex Bubblegum program. @@ -907,7 +906,7 @@ Now that we’ve written code to mint cNFTs, let’s see if we can actually fetc their data. Create a new file `fetch-cnft-details.ts` - + ```bash fetch-cnft-details.ts ``` @@ -1124,7 +1123,8 @@ traditional NFTs. Inspect the cNFT on Solana Explorer! Just like previously, if you have any issues, you should fix them yourself, but if needed the -[solution code](https://github.com/solana-foundation/compressed-nfts) is available. +[solution code](https://github.com/solana-foundation/compressed-nfts) is +available. ### Challenge From ec5933506a4b9f617d6345daa7976a89fa9fda23 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Thu, 3 Oct 2024 13:42:05 +1000 Subject: [PATCH 25/54] Fix minor typos --- content/courses/state-compression/compressed-nfts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/courses/state-compression/compressed-nfts.md b/content/courses/state-compression/compressed-nfts.md index 2b29101b4..95f131af3 100644 --- a/content/courses/state-compression/compressed-nfts.md +++ b/content/courses/state-compression/compressed-nfts.md @@ -659,10 +659,10 @@ the file `create-tree.ts` mkdir src && touch src/create-tree.ts ``` -This Umi instantiation code will be repated in a lot of files, so feel free to +This Umi instantiation code will be repeated in a lot of files, so feel free to create a wrapper file to instantiate it: -```typescript filename="crate-tree.ts" +```typescript filename="create-tree.ts" import { dasApi } from "@metaplex-foundation/digital-asset-standard-api"; import { createTree, mplBubblegum } from "@metaplex-foundation/mpl-bubblegum"; import { generateSigner, keypairIdentity } from "@metaplex-foundation/umi"; From 7dbac06c36922b79c90495a2c93035615b3c07f5 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Thu, 3 Oct 2024 13:42:46 +1000 Subject: [PATCH 26/54] More minor typos --- content/courses/state-compression/compressed-nfts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/courses/state-compression/compressed-nfts.md b/content/courses/state-compression/compressed-nfts.md index 95f131af3..d196ffefa 100644 --- a/content/courses/state-compression/compressed-nfts.md +++ b/content/courses/state-compression/compressed-nfts.md @@ -711,7 +711,7 @@ You can also supply in optional fields such as - `public` - Determines whether anyone else apart from the tree creator will be able to mint cNFTs from the tree. -```typescript filename="crate-tree.ts" +```typescript filename="create-tree.ts" const merkleTree = generateSigner(umi); const builder = await createTree(umi, { merkleTree, From 78d9078305c7b5d81d2334649ddad98cc053394c Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Thu, 3 Oct 2024 13:56:51 +1000 Subject: [PATCH 27/54] Update old links --- content/courses/connecting-to-offchain-data/oracles.md | 2 +- .../verifiable-randomness-functions.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/content/courses/connecting-to-offchain-data/oracles.md b/content/courses/connecting-to-offchain-data/oracles.md index 674ebd9dd..82ce77a90 100644 --- a/content/courses/connecting-to-offchain-data/oracles.md +++ b/content/courses/connecting-to-offchain-data/oracles.md @@ -1406,7 +1406,7 @@ the data feed account does not exist anymore, withdraw the user's escrowed funds. A potential solution to this challenge can be found -[in the Github repository on the `challenge-solution` branch](https://github.com/Unboxed-Software/michael-burry-escrow/tree/challenge-solution). +[in the Github repository on the `challenge-solution` branch](https://github.com/solana-developers/burry-escrow/tree/challenge-solution). diff --git a/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md b/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md index 3f4445c28..e586b4090 100644 --- a/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md +++ b/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md @@ -448,7 +448,7 @@ lesson's codebase. If you don't want to complete the Oracle lesson, the starter code for this lab is provided for you in -[the main branch of the lab Github repository](https://github.com/Unboxed-Software/michael-burry-escrow). +[the main branch of the lab Github repository](https://github.com/solana-developers/burry-escrow). The repo contains a "Michael Burry" escrow program. This is a program that allows a user to lock up some solana funds in escrow that cannot be withdrawn @@ -463,8 +463,8 @@ from escrow regardless of the SOL price. If you are cloning the repo from the previous lesson make sure to do the following: -1. `git clone https://github.com/Unboxed-Software/michael-burry-escrow` -2. `cd michael-burry-escrow` +1. `git clone https://github.com/solana-developers/burry-escrow` +2. `cd burry-escrow` 3. `anchor build` 4. `anchor keys list` 1. Take the resulting key and put it into `Anchor.toml` and @@ -1620,7 +1620,7 @@ And there you have it! You should be able to run and pass all of the tests using If something is not working, go back and find where you went wrong. Alternatively feel free to try out the -[solution code on the `vrf` branch](https://github.com/Unboxed-Software/michael-burry-escrow/tree/vrf). +[solution code on the `vrf` branch](https://github.com/solana-developers/burry-escrow/tree/vrf). Remember to update your program keys and wallet path like we did in the [the Setup step](#1-program-setup). @@ -1633,7 +1633,7 @@ they roll 3 times without rolling doubles, they should be able to withdraw their funds, just like getting out of jail in Monopoly. If you get stuck, we have the solution in the -[`vrf-challenge-solution` branch](https://github.com/Unboxed-Software/michael-burry-escrow/tree/vrf-challenge-solution). +[`vrf-challenge-solution` branch](https://github.com/solana-developers/burry-escrow/tree/vrf-challenge-solution). Push your code to GitHub and From d0afeabf5e72ea1e7c4bd8e881df2ef9f20d8eb1 Mon Sep 17 00:00:00 2001 From: John <75003086+ZYJLiu@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:43:18 -0500 Subject: [PATCH 28/54] Add Anchor section to docs (#428) * basic anchor doc pages * add pda anc cpi pages for anchor section * rewording and proofreading * delete deploying program pages * reorder sections, delete developing program overview content * fix links to deleted pages * misc * delete intro anchor guide page, fix links --- content/guides/games/hello-world.md | 6 +- .../getstarted/hello-world-in-your-browser.md | 9 +- .../getstarted/how-to-cpi-with-signer.md | 5 +- content/guides/getstarted/how-to-cpi.md | 5 +- content/guides/getstarted/intro-to-anchor.md | 750 ------------------ .../guides/getstarted/intro-to-native-rust.md | 2 +- content/guides/getstarted/rust-to-solana.md | 6 +- .../token-extensions/dynamic-meta-data-nft.md | 6 +- docs/advanced/index.md | 2 +- docs/advanced/versions.md | 7 +- docs/core/fees.md | 5 +- docs/core/programs.md | 12 +- docs/intro/dev.md | 9 +- .../intro/quick-start/reading-from-network.md | 4 +- docs/intro/wallets.md | 4 +- docs/programs/anchor/client-typescript.md | 354 +++++++++ docs/programs/anchor/cpi.md | 551 +++++++++++++ docs/programs/anchor/idl.md | 463 +++++++++++ docs/programs/anchor/index.md | 377 +++++++++ docs/programs/anchor/pda.md | 325 ++++++++ docs/programs/anchor/program-structure.md | 399 ++++++++++ docs/programs/debugging.md | 273 ------- docs/programs/deploying.md | 17 +- docs/programs/examples.md | 65 +- docs/programs/faq.md | 225 ------ docs/programs/index.md | 2 +- docs/programs/lang-c.md | 191 ----- docs/programs/lang-rust.md | 399 ---------- docs/programs/limitations.md | 87 -- docs/programs/overview.md | 105 --- docs/programs/testing.md | 14 +- docs/terminology.md | 2 +- package.json | 2 +- 33 files changed, 2537 insertions(+), 2146 deletions(-) delete mode 100644 content/guides/getstarted/intro-to-anchor.md create mode 100644 docs/programs/anchor/client-typescript.md create mode 100644 docs/programs/anchor/cpi.md create mode 100644 docs/programs/anchor/idl.md create mode 100644 docs/programs/anchor/index.md create mode 100644 docs/programs/anchor/pda.md create mode 100644 docs/programs/anchor/program-structure.md delete mode 100644 docs/programs/debugging.md delete mode 100644 docs/programs/faq.md delete mode 100644 docs/programs/lang-c.md delete mode 100644 docs/programs/lang-rust.md delete mode 100644 docs/programs/limitations.md delete mode 100644 docs/programs/overview.md diff --git a/content/guides/games/hello-world.md b/content/guides/games/hello-world.md index 23e4b4bab..cff9a9c9f 100644 --- a/content/guides/games/hello-world.md +++ b/content/guides/games/hello-world.md @@ -25,9 +25,9 @@ keywords: In this development guide, we will walkthrough a simple on-chain game using the Solana blockchain. This game, lovingly called _Tiny Adventure_, is a beginner-friendly Solana program created using the -[Anchor framework](/content/guides/getstarted/intro-to-anchor.md). The goal of -this program is to show you how to create a simple game that allows players to -track their position and move left or right. +[Anchor framework](/docs/programs/anchor). The goal of this program is to show +you how to create a simple game that allows players to track their position and +move left or right. > You can find the complete source code, available to deploy from your browser, > in this diff --git a/content/guides/getstarted/hello-world-in-your-browser.md b/content/guides/getstarted/hello-world-in-your-browser.md index 23e78f8fc..1fece1426 100644 --- a/content/guides/getstarted/hello-world-in-your-browser.md +++ b/content/guides/getstarted/hello-world-in-your-browser.md @@ -117,9 +117,8 @@ use solana_program::{ ### Write your program logic Every Solana program must define an `entrypoint` that tells the Solana runtime -where to start executing your onchain code. Your program's -[entrypoint](/docs/programs/lang-rust.md#program-entrypoint) should provide a -public function named `process_instruction`: +where to start executing your onchain code. Your program's entrypoint should +provide a public function named `process_instruction`: ```rust // declare and export the program's entrypoint @@ -143,8 +142,7 @@ Every onchain program should return the `Ok` [result enum](https://doc.rust-lang.org/std/result/) with a value of `()`. This tells the Solana runtime that your program executed successfully without errors. -Our program above will simply -[log a message](/docs/programs/debugging.md#logging) of "_Hello, world!_" to the +Our program above will simply log a message of "_Hello, world!_" to the blockchain cluster, then gracefully exit with `Ok(())`. ### Build your program @@ -324,4 +322,3 @@ your local development environment: - [Interacting with Tokens and NFTs](/developers/courses/tokens.md) - [Developer Guides](/developers/guides/) - [Developing Games](/content/guides/games/getting-started-with-game-development.md) -- [Learn more about developing Solana programs with Rust](/docs/programs/lang-rust.md) diff --git a/content/guides/getstarted/how-to-cpi-with-signer.md b/content/guides/getstarted/how-to-cpi-with-signer.md index 063559faa..5908d17f9 100644 --- a/content/guides/getstarted/how-to-cpi-with-signer.md +++ b/content/guides/getstarted/how-to-cpi-with-signer.md @@ -14,9 +14,8 @@ keywords: - tutorial --- -This guide uses the -[Anchor framework](/content/guides/getstarted/intro-to-anchor.md) to demonstrate -how to transfer SOL using a [Cross-Program Invocation (CPI)](/docs/core/cpi.md) +This guide uses the [Anchor framework](/docs/programs/anchor) to demonstrate how +to transfer SOL using a [Cross-Program Invocation (CPI)](/docs/core/cpi.md) where the sender is a PDA that the program must sign for. A typical use case for this scenario is a program that manages diff --git a/content/guides/getstarted/how-to-cpi.md b/content/guides/getstarted/how-to-cpi.md index e21105cc1..1a1b49283 100644 --- a/content/guides/getstarted/how-to-cpi.md +++ b/content/guides/getstarted/how-to-cpi.md @@ -13,9 +13,8 @@ keywords: - tutorial --- -This guide uses the -[Anchor framework](/content/guides/getstarted/intro-to-anchor.md) to demonstrate -how to transfer SOL using a [Cross Program Invocation (CPI)](/docs/core/cpi.md). +This guide uses the [Anchor framework](/docs/programs/anchor) to demonstrate how +to transfer SOL using a [Cross Program Invocation (CPI)](/docs/core/cpi.md). Included below are three different, but functionally equivalent implementations that you may come across when reading or writing Solana programs. Here is a final reference program on diff --git a/content/guides/getstarted/intro-to-anchor.md b/content/guides/getstarted/intro-to-anchor.md deleted file mode 100644 index 7b098c196..000000000 --- a/content/guides/getstarted/intro-to-anchor.md +++ /dev/null @@ -1,750 +0,0 @@ ---- -date: 2024-04-24T00:00:00Z -difficulty: beginner -title: "Getting Started with the Anchor Framework" -description: - "This guide provides a basic overview of the Anchor framework. Anchor is a - very popular Rust framework for building Solana programs (known as 'smart - contracts' on other chains) that removes boilerplate, provides secure - defaults, and builds client programs automatically." -tags: - - rust - - anchor -keywords: - - tutorial ---- - -The [Anchor framework](https://www.anchor-lang.com/) uses -[Rust macros](https://doc.rust-lang.org/book/ch19-06-macros.html) to reduce -boilerplate code and simplify the implementation of common security checks -required for writing Solana programs. - -Think of Anchor as a framework for Solana programs much like Next.js is for web -development. Just as Next.js allows developers to create websites using React -instead of relying solely on HTML and TypeScript, Anchor provides a set of tools -and abstractions that make building Solana programs more intuitive and secure. - -The main macros found in an Anchor program include: - -- [`declare_id`](#declare_id-macro): Specifies the program's on-chain address -- [`#[program]`](#program-macro): Specifies the module containing the program's - instruction logic -- [`#[derive(Accounts)]`](#derive-accounts-macro): Applied to structs to - indicate a list of accounts required for an instruction -- [`#[account]`](#account-macro): Applied to structs to create custom account - types specific to the program - -## Anchor Program - -Below is a simple Anchor program with a single instruction that creates a new -account. We'll walk through it to explain the basic structure of an Anchor -program. Here is the program on -[Solana Playground](https://beta.solpg.io/660f3a86cffcf4b13384d022). - -```rust filename="lib.rs" -use anchor_lang::prelude::*; - -declare_id!("11111111111111111111111111111111"); - -#[program] -mod hello_anchor { - use super::*; - pub fn initialize(ctx: Context, data: u64) -> Result<()> { - ctx.accounts.new_account.data = data; - msg!("Changed data to: {}!", data); - Ok(()) - } -} - -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, -} - -#[account] -pub struct NewAccount { - data: u64, -} -``` - -### declare_id macro - -The -[`declare_id`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/attribute/account/src/lib.rs#L430) -macro is used to specify the on-chain address of the program (program ID). - -```rust filename="lib.rs" {3} -use anchor_lang::prelude::*; - -declare_id!("11111111111111111111111111111111"); -``` - -When you build an Anchor program for the first time, the framework generates a -new keypair used to deploy the program (unless specified otherwise). The public -key from this keypair should be used as the program ID in the `declare_id` -macro. - -- When using [Solana Playground](https://beta.solpg.io/), the program ID is - updated automatically for you and can be exported using the UI. -- When building locally, the program keypair can be found in - `/target/deploy/your_program_name.json` - -### program macro - -The -[`#[program]`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/attribute/program/src/lib.rs#L12) -macro specifies the module containing all of your program's instructions. Each -public function in the module represents a separate instruction for the program. - -In every function, the first parameter is always a `Context` type. Subsequent -parameters, which are optional, define any additional `data` required by the -instruction. - -```rust filename="lib.rs" {5, 8-12} -use anchor_lang::prelude::*; - -declare_id!("11111111111111111111111111111111"); - -#[program] -mod hello_anchor { - use super::*; - pub fn initialize(ctx: Context, data: u64) -> Result<()> { - ctx.accounts.new_account.data = data; - msg!("Changed data to: {}!", data); - Ok(()) - } -} - -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, -} - -#[account] -pub struct NewAccount { - data: u64, -} -``` - -The -[`Context`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/src/context.rs#L24) -type provides the instruction with access to the following non-argument inputs: - -```rust -pub struct Context<'a, 'b, 'c, 'info, T> { - /// Currently executing program id. - pub program_id: &'a Pubkey, - /// Deserialized accounts. - pub accounts: &'b mut T, - /// Remaining accounts given but not deserialized or validated. - /// Be very careful when using this directly. - pub remaining_accounts: &'c [AccountInfo<'info>], - /// Bump seeds found during constraint validation. This is provided as a - /// convenience so that handlers don't have to recalculate bump seeds or - /// pass them in as arguments. - pub bumps: BTreeMap, -} -``` - -`Context` is a generic type where `T` represents the set of accounts required by -an instruction. When defining the instruction's `Context`, the `T` type is a -struct that implements the `Accounts` trait (`Context`). - -This context parameter allows the instruction to access: - -- `ctx.accounts`: The instruction's accounts -- `ctx.program_id`: The address of the program itself -- `ctx.remaining_accounts`: All remaining accounts provided to the instruction - but not specified in the `Accounts` struct -- `ctx.bumps`: Bump seeds for any - [Program Derived Address (PDA)](/docs/core/pda.md) accounts specified in the - `Accounts` struct - -### derive(Accounts) macro - -The -[`#[derive(Accounts)]`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/derive/accounts/src/lib.rs#L630) -macro is applied to a struct and implements the -[`Accounts`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/src/lib.rs#L105) -trait. This is used to specify and validate a set of accounts required for a -particular instruction. - -```rust /Accounts/ {1} -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, -} -``` - -Each field in the struct represents an account that is required by an -instruction. The naming of each field is arbitrary, but it is recommended to use -a descriptive name that indicates the purpose of the account. - -```rust /signer/2 /new_account/ /system_program/ -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, -} -``` - -When building Solana programs, it's essential to validate the accounts provided -by the client. This validation is achieved in Anchor through account constraints -and specifying appropriate account types: - -- [Account Constraints](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/syn/src/parser/accounts/constraints.rs): - Constraints define additional conditions that an account must satisfy to be - considered valid for the instruction. Constraints are applied using the - `#[account(..)]` attribute, which is placed above an account field in the - `Accounts` struct. - - ```rust {3, 5} - #[derive(Accounts)] - pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, - } - ``` - -- [Account Types](https://github.com/coral-xyz/anchor/tree/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/src/accounts): - Anchor provides various account types to help ensure that the account provided - by the client matches what the program expects. - - ```rust /Account/2 /Signer/ /Program/ - #[derive(Accounts)] - pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, - } - ``` - -Accounts within the `Accounts` struct are accessible in an instruction through -the `Context`, using the `ctx.accounts` syntax. - -```rust filename="lib.rs" /ctx.accounts.new_account/ /new_account/ /Initialize/ {15-22} -use anchor_lang::prelude::*; - -declare_id!("11111111111111111111111111111111"); - -#[program] -mod hello_anchor { - use super::*; - pub fn initialize(ctx: Context, data: u64) -> Result<()> { - ctx.accounts.new_account.data = data; - msg!("Changed data to: {}!", data); - Ok(()) - } -} - -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, -} - -#[account] -pub struct NewAccount { - data: u64, -} -``` - -When an instruction in an Anchor program is invoked, the program performs the -following checks as specified the in `Accounts` struct: - -- Account Type Verification: It verifies that the accounts passed into the - instruction correspond to the account types defined in the instruction - Context. - -- Constraint Checks: It checks the accounts against any additional constraints - specified. - -This helps ensure that the accounts passed to the instruction from the client -are valid. If any checks fail, then the instruction fails with an error before -reaching the main logic of the instruction handler function. - -For more detailed examples, refer to the -[constraints](https://www.anchor-lang.com/docs/account-constraints) and -[account types](https://www.anchor-lang.com/docs/account-types) sections in the -Anchor documentation. - -### account macro - -The -[`#[account]`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/attribute/account/src/lib.rs#L66) -macro is applied to structs to define the format of a custom data account type -for a program. Each field in the struct represents a field that will be stored -in the account data. - -```rust {3} -#[account] -pub struct NewAccount { - data: u64, -} -``` - -This macro implements various traits -[detailed here](https://docs.rs/anchor-lang/latest/anchor_lang/attr.account.html). -The key functionalities of the `#[account]` macro include: - -- [Assign Ownership](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/attribute/account/src/lib.rs#L119-L132): - When creating an account, the ownership of the account is automatically - assigned to the program specified in the `declare_id`. -- [Set Discriminator](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/attribute/account/src/lib.rs#L101-L117): - A unique 8-byte discriminator, specific to the account type, is added as the - first 8 bytes of account data during its initialization. This helps in - differentiating account types and account validation. -- [Data Serialization and Deserialization](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/lang/attribute/account/src/lib.rs#L202-L246): - The account data corresponding to the account type is automatically serialized - and deserialized. - -```rust filename="lib.rs" /data/2,6 /NewAccount/ {24-27} -use anchor_lang::prelude::*; - -declare_id!("11111111111111111111111111111111"); - -#[program] -mod hello_anchor { - use super::*; - pub fn initialize(ctx: Context, data: u64) -> Result<()> { - ctx.accounts.new_account.data = data; - msg!("Changed data to: {}!", data); - Ok(()) - } -} - -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, -} - -#[account] -pub struct NewAccount { - data: u64, -} -``` - -In Anchor, an account discriminator is an 8-byte identifier, unique to each -account type. This identifier is derived from the first 8 bytes of the SHA256 -hash of the account type's name. The first 8 bytes in an account's data are -specifically reserved for this discriminator. - -```rust /8/1 -#[account(init, payer = signer, space = 8 + 8)] -pub new_account: Account<'info, NewAccount>, -``` - -The discriminator is used during the following two scenarios: - -- Initialization: During the initialization of an account, the discriminator is - set with the account type's discriminator. -- Deserialization: When account data is deserialized, the discriminator within - the data is checked against the expected discriminator of the account type. - -If there's a mismatch, it indicates that the client has provided an unexpected -account. This mechanism serves as an account validation check in Anchor -programs, ensuring the correct and expected accounts are used. - -## IDL File - -When an Anchor program is built, Anchor generates an interface description -language (IDL) file representing the structure of the program. This IDL file -provides a standardized JSON-based format for building program instructions and -fetching program accounts. - -Below are examples of how an IDL file relates to the program code. - -### Instructions - -The `instructions` array in the IDL corresponds with the instructions on the -program and specifies the required accounts and parameters for each instruction. - -```json filename="IDL.json" {6,8-10, 12} -{ - "version": "0.1.0", - "name": "hello_anchor", - "instructions": [ - { - "name": "initialize", - "accounts": [ - { "name": "newAccount", "isMut": true, "isSigner": true }, - { "name": "signer", "isMut": true, "isSigner": true }, - { "name": "systemProgram", "isMut": false, "isSigner": false } - ], - "args": [{ "name": "data", "type": "u64" }] - } - ], - "accounts": [ - { - "name": "NewAccount", - "type": { - "kind": "struct", - "fields": [{ "name": "data", "type": "u64" }] - } - } - ] -} -``` - -```rust filename="lib.rs" {8, 18, 20, 21} -use anchor_lang::prelude::*; - -declare_id!("11111111111111111111111111111111"); - -#[program] -mod hello_anchor { - use super::*; - pub fn initialize(ctx: Context, data: u64) -> Result<()> { - ctx.accounts.new_account.data = data; - msg!("Changed data to: {}!", data); - Ok(()) - } -} - -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, -} - -#[account] -pub struct NewAccount { - data: u64, -} -``` - -### Accounts - -The `accounts` array in the IDL corresponds with structs in the program -annotated with the `#[account]` macro, which specifies the structure of the -program's data accounts. - -```json filename="IDL.json" {16-22} -{ - "version": "0.1.0", - "name": "hello_anchor", - "instructions": [ - { - "name": "initialize", - "accounts": [ - { "name": "newAccount", "isMut": true, "isSigner": true }, - { "name": "signer", "isMut": true, "isSigner": true }, - { "name": "systemProgram", "isMut": false, "isSigner": false } - ], - "args": [{ "name": "data", "type": "u64" }] - } - ], - "accounts": [ - { - "name": "NewAccount", - "type": { - "kind": "struct", - "fields": [{ "name": "data", "type": "u64" }] - } - } - ] -} -``` - -```rust filename="lib.rs" {24-27} -use anchor_lang::prelude::*; - -declare_id!("11111111111111111111111111111111"); - -#[program] -mod hello_anchor { - use super::*; - pub fn initialize(ctx: Context, data: u64) -> Result<()> { - ctx.accounts.new_account.data = data; - msg!("Changed data to: {}!", data); - Ok(()) - } -} - -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(init, payer = signer, space = 8 + 8)] - pub new_account: Account<'info, NewAccount>, - #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, -} - -#[account] -pub struct NewAccount { - data: u64, -} - -``` - -## Client - -Anchor provides a Typescript client library -([`@coral-xyz/anchor`](https://github.com/coral-xyz/anchor/tree/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor)) -that simplifies the process of interacting with Solana programs from the client. - -To use the client library, you first need to set up an instance of a -[`Program`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/index.ts#L58) -using the IDL file generated by Anchor. - -### Client Program - -Creating an instance of the `Program` requires the program's IDL, its on-chain -address (`programId`), and an -[`AnchorProvider`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/provider.ts#L55). -An `AnchorProvider` combines two things: - -- `Connection` - the connection to a [Solana cluster](/docs/core/clusters.md) - (i.e. localhost, devnet, mainnet) -- `Wallet` - (optional) a default wallet used to pay and sign transactions - -When building an Anchor program locally, the setup for creating an instance of -the `Program` is done automatically in the test file. The IDL file can be found -in the `/target` folder. - -```typescript showLineNumbers -import * as anchor from "@coral-xyz/anchor"; -import { Program, BN } from "@coral-xyz/anchor"; -import { HelloAnchor } from "../target/types/hello_anchor"; - -const provider = anchor.AnchorProvider.env(); -anchor.setProvider(provider); -const program = anchor.workspace.HelloAnchor as Program; -``` - -When integrating with a frontend using the -[wallet adapter](https://solana.com/developers/guides/wallets/add-solana-wallet-adapter-to-nextjs), -you'll need to manually set up the `AnchorProvider` and `Program`. - -```ts {8-9, 12} -import { Program, Idl, AnchorProvider, setProvider } from "@coral-xyz/anchor"; -import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react"; -import { IDL, HelloAnchor } from "./idl"; - -const { connection } = useConnection(); -const wallet = useAnchorWallet(); - -const provider = new AnchorProvider(connection, wallet, {}); -setProvider(provider); - -const programId = new PublicKey("..."); -const program = new Program(IDL, programId); -``` - -Alternatively, you can create an instance of the `Program` using only the IDL -and the `Connection` to a Solana cluster. This means if there is no default -`Wallet`, but allows you to use the `Program` to fetch accounts before a wallet -is connected. - -```ts {8-10} -import { Program } from "@coral-xyz/anchor"; -import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js"; -import { IDL, HelloAnchor } from "./idl"; - -const programId = new PublicKey("..."); -const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); - -const program = new Program(IDL, programId, { - connection, -}); -``` - -### Invoke Instructions - -Once the `Program` is set up, you can use the Anchor -[`MethodsBuilder`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/methods.ts#L155) -to build an instruction, a transaction, or build and send a transaction. The -basic format looks like this: - -- `program.methods` - This is the builder API for creating instruction calls - related to the program's IDL -- `.instructionName` - Specific instruction from the program IDL, passing in any - instruction data as comma-separated values -- `.accounts` - Pass in the address of each account required by the instruction - as specified in the IDL -- `.signers` - Optionally pass in an array of keypairs required as additional - signers by the instruction - -```ts -await program.methods - .instructionName(instructionData1, instructionData2) - .accounts({}) - .signers([]) - .rpc(); -``` - -Below are examples of how to invoke an instruction using the methods builder. - -#### rpc() - -The -[`rpc()`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/methods.ts#L283) -method -[sends a signed transaction](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/rpc.ts#L29) -with the specified instruction and returns a `TransactionSignature`. When using -`.rpc`, the `Wallet` from the `Provider` is automatically included as a signer. - -```ts {13} -// Generate keypair for the new account -const newAccountKp = new Keypair(); - -const data = new BN(42); -const transactionSignature = await program.methods - .initialize(data) - .accounts({ - newAccount: newAccountKp.publicKey, - signer: wallet.publicKey, - systemProgram: SystemProgram.programId, - }) - .signers([newAccountKp]) - .rpc(); -``` - -#### transaction() - -The -[`transaction()`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/methods.ts#L382) -method -[builds a `Transaction`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/transaction.ts#L18-L26) -and adds the specified instruction to the transaction (without automatically -sending). - -```ts {12} /transaction/1,2,4 -// Generate keypair for the new account -const newAccountKp = new Keypair(); - -const data = new BN(42); -const transaction = await program.methods - .initialize(data) - .accounts({ - newAccount: newAccountKp.publicKey, - signer: wallet.publicKey, - systemProgram: SystemProgram.programId, - }) - .transaction(); - -const transactionSignature = await connection.sendTransaction(transaction, [ - wallet.payer, - newAccountKp, -]); -``` - -#### instruction() - -The -[`instruction()`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/methods.ts#L348) -method -[builds a `TransactionInstruction`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/instruction.ts#L57-L61) -using the specified instruction. This is useful if you want to manually add the -instruction to a transaction and combine it with other instructions. - -```ts {12} /instruction/ -// Generate keypair for the new account -const newAccountKp = new Keypair(); - -const data = new BN(42); -const instruction = await program.methods - .initialize(data) - .accounts({ - newAccount: newAccountKp.publicKey, - signer: wallet.publicKey, - systemProgram: SystemProgram.programId, - }) - .instruction(); - -const transaction = new Transaction().add(instruction); - -const transactionSignature = await connection.sendTransaction(transaction, [ - wallet.payer, - newAccountKp, -]); -``` - -### Fetch Accounts - -The client `Program` also allows you to easily fetch and filter program -accounts. Simply use `program.account` and then specify the name of the account -type on the IDL. Anchor then deserializes and returns all accounts as specified. - -#### all() - -Use -[`all()`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/account.ts#L251) -to fetch all existing accounts for a specific account type. - -```ts /all/ -const accounts = await program.account.newAccount.all(); -``` - -#### memcmp - -Use `memcmp` to filter for accounts storing data that matches a specific value -at a specific offset. When calculating the offset, remember that the first 8 -bytes are reserved for the account discriminator in accounts created through an -Anchor program. Using `memcmp` requires you to understand the byte layout of the -data field for the account type you are fetching. - -```ts /memcmp/ -const accounts = await program.account.newAccount.all([ - { - memcmp: { - offset: 8, - bytes: "", - }, - }, -]); -``` - -#### fetch() - -Use -[`fetch()`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/account.ts#L165) -to get the account data for a specific account by passing in the account address - -```ts /fetch/ -const account = await program.account.newAccount.fetch(ACCOUNT_ADDRESS); -``` - -#### fetchMultiple() - -Use -[`fetchMultiple()`](https://github.com/coral-xyz/anchor/blob/852fcc77beb6302474a11e0f8e6f1e688021be36/ts/packages/anchor/src/program/namespace/account.ts#L200) -to get the account data for multiple accounts by passing in an array of account -addresses - -```ts /fetchMultiple/ -const accounts = await program.account.newAccount.fetchMultiple([ - ACCOUNT_ADDRESS_ONE, - ACCOUNT_ADDRESS_TWO, -]); -``` diff --git a/content/guides/getstarted/intro-to-native-rust.md b/content/guides/getstarted/intro-to-native-rust.md index 27e508e67..6f5125e6b 100644 --- a/content/guides/getstarted/intro-to-native-rust.md +++ b/content/guides/getstarted/intro-to-native-rust.md @@ -16,7 +16,7 @@ To write Solana programs without leveraging the Anchor framework, we use the This is the base library for writing onchain programs in Rust. For beginners, it is recommended to start with the -[Anchor framework](/content/guides/getstarted/intro-to-anchor.md). +[Anchor framework](/docs/programs/anchor). ## Program diff --git a/content/guides/getstarted/rust-to-solana.md b/content/guides/getstarted/rust-to-solana.md index 3c001d31b..c1d9d75a1 100644 --- a/content/guides/getstarted/rust-to-solana.md +++ b/content/guides/getstarted/rust-to-solana.md @@ -237,8 +237,7 @@ boilerplate code, speeding up the development cycle. Additionally, it provides some security checks by default, making Solana programs more secure. To create a new program, simply -[create a new Anchor project](https://solana.com/developers/guides/getstarted/intro-to-anchor) -in the Solana playground. +[create a new Anchor project](/docs/programs/anchor) in the Solana playground. Alternatively, [install the Anchor CLI](https://www.anchor-lang.com/docs/installation) locally, @@ -265,9 +264,6 @@ consider writing onchain programs in Rust, and offchain This guide has covered the basics of developing for Solana with Rust, from setup details and restrictions to development environments and frameworks. -For more Rust-related Solana resources, check out the -[Developing with Rust page](https://solana.com/docs/programs/lang-rust). - For other Solana program examples written with Rust, check out these [examples on GitHub](https://github.com/solana-labs/solana-program-library/tree/master/examples/rust). diff --git a/content/guides/token-extensions/dynamic-meta-data-nft.md b/content/guides/token-extensions/dynamic-meta-data-nft.md index 279400ac2..b52b6d83a 100644 --- a/content/guides/token-extensions/dynamic-meta-data-nft.md +++ b/content/guides/token-extensions/dynamic-meta-data-nft.md @@ -46,9 +46,9 @@ character's stats or inventory). ## Building the on-chain program In this developer guide, we will demonstrate how to build these Token Extension -based NFTs and custom metadata using an -[Anchor program](/content/guides/getstarted/intro-to-anchor.md). This program -will save the level and the collected resources of a game player within an NFT. +based NFTs and custom metadata using an [Anchor program](/docs/programs/anchor). +This program will save the level and the collected resources of a game player +within an NFT. This NFT will be created by the Anchor program so it is very easy to mint from the JavaScript client. Each NFT will have some basic structure provided via the diff --git a/docs/advanced/index.md b/docs/advanced/index.md index 933c4c67f..11ae9d7f4 100644 --- a/docs/advanced/index.md +++ b/docs/advanced/index.md @@ -1,5 +1,5 @@ --- metaOnly: true -title: Advanced Concepts +title: Advanced Topics sidebarSortOrder: 3 --- diff --git a/docs/advanced/versions.md b/docs/advanced/versions.md index c86528558..fea794409 100644 --- a/docs/advanced/versions.md +++ b/docs/advanced/versions.md @@ -13,10 +13,9 @@ Versioned Transactions are the new transaction format that allow for additional functionality in the Solana runtime, including [Address Lookup Tables](/docs/advanced/lookup-tables.md). -While changes to [onchain](/docs/programs/index.md) programs are **NOT** -required to support the new functionality of versioned transactions (or for -backwards compatibility), developers **WILL** need update their client side code -to prevent +While changes to onchain programs are **NOT** required to support the new +functionality of versioned transactions (or for backwards compatibility), +developers **WILL** need update their client side code to prevent [errors due to different transaction versions](#max-supported-transaction-version). ## Current Transaction Versions diff --git a/docs/core/fees.md b/docs/core/fees.md index 8467f23d1..aaa988c9f 100644 --- a/docs/core/fees.md +++ b/docs/core/fees.md @@ -197,9 +197,8 @@ syscalls, etc), each may consume a of compute units. > A program can log details about its compute usage, including how much remains -> in its alloted compute budget. See -> [program debugging](/docs/programs/debugging.md#monitoring-compute-budget-consumption) -> for more information. You can also find more information in this guide for +> in its alloted compute budget. You can also find more information in this +> guide for > [optimizing your compute usage](/content/guides/advanced/how-to-optimize-compute.md). Each transaction is alloted a [compute unit limit](#compute-unit-limit), either diff --git a/docs/core/programs.md b/docs/core/programs.md index 330fb3d0c..5622a4c0d 100644 --- a/docs/core/programs.md +++ b/docs/core/programs.md @@ -9,10 +9,6 @@ In the Solana ecosystem, "smart contracts" are called programs. Each stores executable logic, organized into specific functions referred to as [instructions](/docs/core/transactions.md#instruction). -For additional topics related to Solana programs, refer to the pages included -under the [Deploying Programs](/docs/programs/index.md) section of this -documentation. - ## Key Points - Programs are on-chain accounts that contain executable code. This code is @@ -33,10 +29,10 @@ Solana programs are predominantly written in the [Rust](https://doc.rust-lang.org/book/) programming language, with two common approaches for development: -- [Anchor](/content/guides/getstarted/intro-to-anchor.md): A framework designed - for Solana program development. It provides a faster and simpler way to write - programs, using Rust macros to significantly reduce boilerplate code. For - beginners, it is recommended to start with the Anchor framework. +- [Anchor](/docs/programs/anchor): A framework designed for Solana program + development. It provides a faster and simpler way to write programs, using + Rust macros to significantly reduce boilerplate code. For beginners, it is + recommended to start with the Anchor framework. - [Native Rust](/content/guides/getstarted/intro-to-native-rust.md): This approach involves writing Solana programs in Rust without leveraging any diff --git a/docs/intro/dev.md b/docs/intro/dev.md index 751e5a7b9..fd3f90e24 100644 --- a/docs/intro/dev.md +++ b/docs/intro/dev.md @@ -149,8 +149,7 @@ your program based on your language preference: If you do not want to develop your programs locally, there's also the [online IDE Solana Playground](https://beta.solpg.io). Solana Playground allows you to write, test, and deploy programs on Solana. You can get started with -Solana Playground by -[following our guide](https://solana.com/developers/guides/getstarted/hello-world-in-your-browser). +Solana Playground by [following our quick start guide](/docs/intro/quick-start). ### Developer Environments @@ -191,8 +190,4 @@ problem can find your question! ## Next steps -You're now ready to get started building on Solana! - -- [Deploy your first Solana program in the browser](/content/guides/getstarted/hello-world-in-your-browser.md) -- [Get started building programs locally with Rust](/content/guides/getstarted/local-rust-hello-world.md) -- [Overview of writing Solana programs](/docs/programs/index.md) +[You're now ready to get started building on Solana!](/docs/intro/quick-start) diff --git a/docs/intro/quick-start/reading-from-network.md b/docs/intro/quick-start/reading-from-network.md index 6111d89c2..669c5bdea 100644 --- a/docs/intro/quick-start/reading-from-network.md +++ b/docs/intro/quick-start/reading-from-network.md @@ -30,12 +30,12 @@ Account Model. For more details, refer to the ## Fetch Playground Wallet - - Let's start by looking at a familiar account - your own Playground Wallet! We'll fetch this account and examine its structure to understand what a basic Solana account looks like. + + ### Open Example 1 Click this [link](https://beta.solpg.io/6671c5e5cffcf4b13384d198) to open the diff --git a/docs/intro/wallets.md b/docs/intro/wallets.md index b2f29a098..0973a6de5 100644 --- a/docs/intro/wallets.md +++ b/docs/intro/wallets.md @@ -60,8 +60,8 @@ first will need to create a wallet.** ## Supported Wallets Several browser and mobile app based wallets support Solana. Find some options -that might be right for you on the -[Solana Ecosystem](https://solana.com/ecosystem/explore?categories=wallet) page. +that might be right for you on the [Solana Wallets](https://solana.com/wallets) +page. For advanced users or developers, the [command-line wallets](https://docs.solanalabs.com/cli/wallets) may be more diff --git a/docs/programs/anchor/client-typescript.md b/docs/programs/anchor/client-typescript.md new file mode 100644 index 000000000..ba5c096ec --- /dev/null +++ b/docs/programs/anchor/client-typescript.md @@ -0,0 +1,354 @@ +--- +title: JS/TS Client +description: + Learn how to use Anchor's TypeScript client library to interact with Solana + progra +sidebarLabel: JS/TS Client +sidebarSortOrder: 3 +--- + +Anchor provides a Typescript client library +([@coral-xyz/anchor](https://github.com/coral-xyz/anchor/tree/v0.30.1/ts/packages/anchor)) +that simplifies the process of interacting with Solana programs from the client +in JavaScript or TypeScript. + +## Client Program + +To use the client library, first create an instance of a +[`Program`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/index.ts#L58) +using the [IDL file](/docs/programs/anchor/idl) generated by Anchor. + +Creating an instance of the `Program` requires the program's IDL and an +[`AnchorProvider`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/provider.ts#L55). +An `AnchorProvider` is an abstraction that combines two things: + +- `Connection` - the connection to a [Solana cluster](/docs/core/clusters.md) + (i.e. localhost, devnet, mainnet) +- `Wallet` - (optional) a default wallet used to pay and sign transactions + + + + + +When integrating with a frontend using the +[wallet adapter](https://solana.com/developers/guides/wallets/add-solana-wallet-adapter-to-nextjs), +you'll need to set up the `AnchorProvider` and `Program`. + +```ts {9-10, 12-14} +import { Program, AnchorProvider, setProvider } from "@coral-xyz/anchor"; +import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react"; +import type { HelloAnchor } from "./idlType"; +import idl from "./idl.json"; + +const { connection } = useConnection(); +const wallet = useAnchorWallet(); + +const provider = new AnchorProvider(connection, wallet, {}); +setProvider(provider); + +export const program = new Program(idl as HelloAnchor, { + connection, +}); +``` + +In the code snippet above: + +- `idl.json` is the IDL file generated by Anchor, found at + `/target/idl/.json` in an Anchor project. +- `idlType.ts` is the IDL type (for use with TS), found at + `/target/types/.ts` in an Anchor project. + +Alternatively, you can create an instance of the `Program` using only the IDL +and the `Connection` to a Solana cluster. This means there is no default +`Wallet`, but allows you to use the `Program` to fetch accounts or build +instructions without a connected wallet. + +```ts {8-10} +import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js"; +import { Program } from "@coral-xyz/anchor"; +import type { HelloAnchor } from "./idlType"; +import idl from "./idl.json"; + +const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); + +export const program = new Program(idl as HelloAnchor, { + connection, +}); +``` + + + + +Anchor automatically sets up a `Program` instance in the default test file of +new projects. However, this setup differs from how you'd initialize a `Program` +outside the Anchor workspace, such as in React or Node.js applications. + +```typescript +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { HelloAnchor } from "../target/types/hello_anchor"; + +describe("hello_anchor", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.HelloAnchor as Program; + + it("Is initialized!", async () => { + // Add your test here. + const tx = await program.methods.initialize().rpc(); + console.log("Your transaction signature", tx); + }); +}); +``` + + + + +## Invoke Instructions + +Once the `Program` is set up using a program IDL, you can use the Anchor +[`MethodsBuilder`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/methods.ts#L155) +to: + +- Build individual instructions +- Build transactions +- Build and send transactions + +The basic format looks like the following: + + + + + +`program.methods` - This is the builder API for creating instruction calls from +the program's IDL + +```ts /methods/ {1} +await program.methods + .instructionName(instructionData) + .accounts({}) + .signers([]) + .rpc(); +``` + + + + +Following `.methods`, specify the name of an instruction from the program IDL, +passing in any required arguments as comma-separated values. + +```ts /instructionName/ /instructionData1/ /instructionData2/ {2} +await program.methods + .instructionName(instructionData1, instructionData2) + .accounts({}) + .signers([]) + .rpc(); +``` + + + + +`.accounts` - Pass in the address of the accounts required by the instruction as +specified in the IDL + +```ts /accounts/ {3} +await program.methods + .instructionName(instructionData) + .accounts({}) + .signers([]) + .rpc(); +``` + +Note that certain account addresses don't need to be explicitly provided, as the +Anchor client can automatically resolve them. These typically include: + +- Common accounts (ex. the System Program) +- Accounts where the address is a PDA (Program Derived Address) + + + + +`.signers` - Optionally pass in an array of keypairs required as additional +signers by the instruction. This is commonly used when creating new accounts +where the account address is the public key of a newly generated keypair. + +```ts /signers/ {4} +await program.methods + .instructionName(instructionData) + .accounts({}) + .signers([]) + .rpc(); +``` + +Note that `.signers` should only be used when also using `.rpc()`. When using +`.transaction()` or `.instruction()`, signers should be added to the transaction +before sending. + + + + +Anchor provides multiple methods for building program instructions: + + + + + +The +[`rpc()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/methods.ts#L283) +method +[sends a signed transaction](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/rpc.ts#L29) +with the specified instruction and returns a `TransactionSignature`. + +When using `.rpc`, the `Wallet` from the `Provider` is automatically included as +a signer. + +```ts {13} +// Generate keypair for the new account +const newAccountKp = new Keypair(); + +const data = new BN(42); +const transactionSignature = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + systemProgram: SystemProgram.programId, + }) + .signers([newAccountKp]) + .rpc(); +``` + + + + +The +[`transaction()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/methods.ts#L382) +method +[builds a `Transaction`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/transaction.ts#L18-L26) +with the specified instruction without sending the transaction. + +```ts {12} /transaction()/1,2,4 +// Generate keypair for the new account +const newAccountKp = new Keypair(); + +const data = new BN(42); +const transaction = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + systemProgram: SystemProgram.programId, + }) + .transaction(); + +const transactionSignature = await connection.sendTransaction(transaction, [ + wallet.payer, + newAccountKp, +]); +``` + + + + +The +[`instruction()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/methods.ts#L348) +method +[builds a `TransactionInstruction`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/instruction.ts#L57-L61) +using the specified instruction. This is useful if you want to manually add the +instruction to a transaction and combine it with other instructions. + +```ts {12} /instruction()/ +// Generate keypair for the new account +const newAccountKp = new Keypair(); + +const data = new BN(42); +const instruction = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + systemProgram: SystemProgram.programId, + }) + .instruction(); + +const transaction = new Transaction().add(instruction); + +const transactionSignature = await connection.sendTransaction(transaction, [ + wallet.payer, + newAccountKp, +]); +``` + + + + +## Fetch Accounts + +The `Program` client simplifies the process of fetching and deserializing +accounts created by your Anchor program. + +Use `program.account` followed by the name of the account type defined in the +IDL. Anchor provides multiple methods for fetching accounts. + + + + + +Use +[`all()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/account.ts#L251) +to fetch all existing accounts for a specific account type. + +```ts /all/ +const accounts = await program.account.newAccount.all(); +``` + + + + +Use `memcmp` (memory compare) to filter for account data that matches a specific +value at a specific offset. Using `memcmp` requires you to understand the byte +layout of the data field for the account type you are fetching. + +When calculating the offset, remember that the first 8 bytes in accounts created +by an Anchor program are reserved for the account discriminator. + +```ts /memcmp/ +const accounts = await program.account.newAccount.all([ + { + memcmp: { + offset: 8, + bytes: "", + }, + }, +]); +``` + + + + +Use +[`fetch()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/account.ts#L165) +to fetch the account data for a single account + +```ts /fetch/ +const account = await program.account.newAccount.fetch(ACCOUNT_ADDRESS); +``` + + + + +Use +[`fetchMultiple()`](https://github.com/coral-xyz/anchor/blob/v0.30.1/ts/packages/anchor/src/program/namespace/account.ts#L200) +to fetch the account data for multiple accounts by passing in an array of +account addresses + +```ts /fetchMultiple/ +const accounts = await program.account.newAccount.fetchMultiple([ + ACCOUNT_ADDRESS_ONE, + ACCOUNT_ADDRESS_TWO, +]); +``` + + + diff --git a/docs/programs/anchor/cpi.md b/docs/programs/anchor/cpi.md new file mode 100644 index 000000000..092b9481f --- /dev/null +++ b/docs/programs/anchor/cpi.md @@ -0,0 +1,551 @@ +--- +title: CPIs with Anchor +description: + Learn how to implement Cross Program Invocations (CPIs) in Anchor programs, + enabling interaction between different programs on Solana +sidebarLabel: CPIs with Anchor +sidebarSortOrder: 5 +--- + +[Cross Program Invocations (CPI)](/docs/core/cpi.md) refer to the process of one +program invoking instructions of another program, which enables the +composibility of programs on Solana. + +This section will cover the basics of implementing CPIs in an Anchor program, +using a simple SOL transfer instruction as a practical example. Once you +understand the basics of how to implement a CPI, you can apply the same concepts +for any instruction. + +## Cross Program Invocations + +Let's examine a program that implements a CPI to the System Program's transfer +instruction. Here is the example program on +[Solana Playground](https://beta.solpg.io/66df2751cffcf4b13384d35a). + +The `lib.rs` file includes a single `sol_transfer` instruction. When the +`sol_transfer` instruction on the Anchor program is invoked, the program +internally invokes the transfer instruction of the System Program. + +```rs filename="lib.rs" /sol_transfer/ /transfer/ {23} +use anchor_lang::prelude::*; +use anchor_lang::system_program::{transfer, Transfer}; + +declare_id!("9AvUNHjxscdkiKQ8tUn12QCMXtcnbR9BVGq3ULNzFMRi"); + +#[program] +pub mod cpi { + use super::*; + + pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.sender.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, + ); + + transfer(cpi_context, amount)?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct SolTransfer<'info> { + #[account(mut)] + sender: Signer<'info>, + #[account(mut)] + recipient: SystemAccount<'info>, + system_program: Program<'info, System>, +} +``` + +The `cpi.test.ts` file shows how to invoke the Anchor program's `sol_transfer` +instruction and logs a link to the transaction details on SolanaFM. + +```ts filename="cpi.test.ts" +it("SOL Transfer Anchor", async () => { + const transactionSignature = await program.methods + .solTransfer(new BN(transferAmount)) + .accounts({ + sender: sender.publicKey, + recipient: recipient.publicKey, + }) + .rpc(); + + console.log( + `\nTransaction Signature:` + + `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, + ); +}); +``` + +You can build, deploy, and run the test for this example on Playground to view +the transaction details on the [SolanaFM explorer](https://solana.fm/). + +The transaction details will show that the Anchor program was first invoked +(instruction 1), which then invokes the System Program (instruction 1.1), +resulting in a successful SOL transfer. + +![Transaction Details](/assets/docs/core/cpi/transaction-details.png) + +### Example 1 Explanation + +Implementing a CPI follows the same pattern as building an instruction to add to +a transaction. When implementing a CPI, we must specify the program ID, +accounts, and instruction data for the instruction being called. + +The System Program's transfer instruction requires two accounts: + +- `from`: The account sending SOL. +- `to`: The account receiving SOL. + +In the example program, the `SolTransfer` struct specifies the accounts required +by the transfer instruction. The System Program is also included because the CPI +invokes the System Program. + +```rust /sender/ /recipient/ /system_program/ +#[derive(Accounts)] +pub struct SolTransfer<'info> { + #[account(mut)] + sender: Signer<'info>, // from account + #[account(mut)] + recipient: SystemAccount<'info>, // to account + system_program: Program<'info, System>, // program ID +} +``` + +The following tabs present three approaches to implementing Cross Program +Invocations (CPIs), each at a different level of abstraction. All examples are +functionally equivalent. The main purpose is to illustrate the implementation +details of the CPI. + + + + + +The `sol_transfer` instruction included in the example code shows a typical +approach for constructing CPIs using the Anchor framework. + +This approach involves creating a +[`CpiContext`](https://docs.rs/anchor-lang/latest/anchor_lang/context/struct.CpiContext.html), +which includes the `program_id` and accounts required for the instruction being +called, followed by a helper function (`transfer`) to invoke a specific +instruction. + +```rust +use anchor_lang::system_program::{transfer, Transfer}; +``` + +```rust /cpi_context/ {14} +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.sender.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, + ); + + transfer(cpi_context, amount)?; + Ok(()) +} +``` + +The `cpi_context` variable specifies the program ID (System Program) and +accounts (sender and recipient) required by the transfer instruction. + +```rust /program_id/ /from_pubkey/ /to_pubkey/ +let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, +); +``` + +The `cpi_context` and `amount` are then passed into the `transfer` function to +execute the CPI invoking the transfer instruction of the System Program. + +```rust +transfer(cpi_context, amount)?; +``` + + + + +This example shows a different approach to implementing a CPI using the `invoke` +function and +[`system_instruction::transfer`](https://github.com/solana-labs/solana/blob/27eff8408b7223bb3c4ab70523f8a8dca3ca6645/sdk/program/src/system_instruction.rs#L881), +which is generally seen in native Rust programs. + +Under the hood, the previous example is an abstraction of this implementation. +The example below is functionally equivalent to the previous example. + +```rust +use anchor_lang::solana_program::{program::invoke, system_instruction}; +``` + +```rust /instruction/1,3 {9} +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.sender.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let instruction = + &system_instruction::transfer(&from_pubkey.key(), &to_pubkey.key(), amount); + + invoke(instruction, &[from_pubkey, to_pubkey, program_id])?; + Ok(()) +} +``` + + + + +You can also manually build the instruction to pass into the `invoke()` +function. This is useful when there is no crate available to help build the +instruction you want to invoke. This approach requires you to specify the +`AccountMeta`s for the instruction and correctly create the instruction data +buffer. + +The `sol_transfer` instruction below is a manual implementation of a CPI to the +System Program's transfer instruction. + +```rust /instruction/10,13 {28} +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.sender.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + // Prepare instruction AccountMetas + let account_metas = vec![ + AccountMeta::new(from_pubkey.key(), true), + AccountMeta::new(to_pubkey.key(), false), + ]; + + // SOL transfer instruction discriminator + let instruction_discriminator: u32 = 2; + + // Prepare instruction data + let mut instruction_data = Vec::with_capacity(4 + 8); + instruction_data.extend_from_slice(&instruction_discriminator.to_le_bytes()); + instruction_data.extend_from_slice(&amount.to_le_bytes()); + + // Create instruction + let instruction = Instruction { + program_id: program_id.key(), + accounts: account_metas, + data: instruction_data, + }; + + // Invoke instruction + invoke(&instruction, &[from_pubkey, to_pubkey, program_id])?; + Ok(()) +} +``` + +The `sol_transfer` instruction above replicates this +[example](/docs/core/transactions.md#manual-sol-transfer) of manually building a +SOL transfer instruction. It follows the same pattern as building an +[instruction](/docs/core/transactions.md#instruction) to add to a transaction. + +When building an instruction in Rust, use the following syntax to specify the +`AccountMeta` for each account: + +```rust +AccountMeta::new(account1_pubkey, true), // writable, signer +AccountMeta::new(account2_pubkey, false), // writable, not signer +AccountMeta::new_readonly(account3_pubkey, false), // not writable, not signer +AccountMeta::new_readonly(account4_pubkey, true), // writable, signer +``` + + + + +Here is a reference program on +[Solana Playground](https://beta.solpg.io/github.com/ZYJLiu/doc-examples/tree/main/cpi) +which includes all 3 examples. + +## Cross Program Invocations with PDA Signers + +Next, let's examine a program that implements a CPI to the System Program's +transfer instruction where the sender is a Program Derived Address (PDA) that +must be "signed" for by the program. Here is the example program on +[Solana Playground](https://beta.solpg.io/66df2bd2cffcf4b13384d35b). + +The `lib.rs` file includes the following program with a single `sol_transfer` +instruction. + +```rust filename="lib.rs" +use anchor_lang::prelude::*; +use anchor_lang::system_program::{transfer, Transfer}; + +declare_id!("3455LkCS85a4aYmSeNbRrJsduNQfYRY82A7eCD3yQfyR"); + +#[program] +pub mod cpi { + use super::*; + + pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.pda_account.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let seed = to_pubkey.key(); + let bump_seed = ctx.bumps.pda_account; + let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]]; + + let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, + ) + .with_signer(signer_seeds); + + transfer(cpi_context, amount)?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct SolTransfer<'info> { + #[account( + mut, + seeds = [b"pda", recipient.key().as_ref()], + bump, + )] + pda_account: SystemAccount<'info>, + #[account(mut)] + recipient: SystemAccount<'info>, + system_program: Program<'info, System>, +} +``` + +The `cpi.test.ts` file shows how to invoke the Anchor program's `sol_transfer` +instruction and logs a link to the transaction details on SolanaFM. + +It shows how to derive the PDA using the seeds specified in the program: + +```ts /pda/ /wallet.publicKey/ +const [PDA] = PublicKey.findProgramAddressSync( + [Buffer.from("pda"), wallet.publicKey.toBuffer()], + program.programId, +); +``` + +The first step in this example is to fund the PDA account with a basic SOL +transfer from the Playground wallet. + +```ts filename="cpi.test.ts" +it("Fund PDA with SOL", async () => { + const transferInstruction = SystemProgram.transfer({ + fromPubkey: wallet.publicKey, + toPubkey: PDA, + lamports: transferAmount, + }); + + const transaction = new Transaction().add(transferInstruction); + + const transactionSignature = await sendAndConfirmTransaction( + connection, + transaction, + [wallet.payer], // signer + ); + + console.log( + `\nTransaction Signature:` + + `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, + ); +}); +``` + +Once the PDA is funded with SOL, invoke the `sol_transfer` instruction. This +instruction transfers SOL from the PDA account back to the `wallet` account via +a CPI to the System Program, which is "signed" for by the program. + +```ts +it("SOL Transfer with PDA signer", async () => { + const transactionSignature = await program.methods + .solTransfer(new BN(transferAmount)) + .accounts({ + pdaAccount: PDA, + recipient: wallet.publicKey, + }) + .rpc(); + + console.log( + `\nTransaction Signature: https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, + ); +}); +``` + +You can build, deploy, and run the test to view the transaction details on the +[SolanaFM explorer](https://solana.fm/). + +The transaction details will show that the custom program was first invoked +(instruction 1), which then invokes the System Program (instruction 1.1), +resulting in a successful SOL transfer. + +![Transaction Details](/assets/docs/core/cpi/transaction-details-pda.png) + +### Example 2 Explanation + +In the example code, the `SolTransfer` struct specifies the accounts required by +the transfer instruction. + +The sender is a PDA that the program must sign for. The `seeds` to derive the +address for the `pda_account` include the hardcoded string "pda" and the address +of the `recipient` account. This means the address for the `pda_account` is +unique for each `recipient`. + +```rust /pda_account/ /recipient/2 /system_program/ +#[derive(Accounts)] +pub struct SolTransfer<'info> { + #[account( + mut, + seeds = [b"pda", recipient.key().as_ref()], + bump, + )] + pda_account: SystemAccount<'info>, + #[account(mut)] + recipient: SystemAccount<'info>, + system_program: Program<'info, System>, +} +``` + +The Javascript equivalent to derive the PDA is included in the test file. + +```ts /pda/ /wallet.publicKey/ +const [PDA] = PublicKey.findProgramAddressSync( + [Buffer.from("pda"), wallet.publicKey.toBuffer()], + program.programId, +); +``` + +The following tabs present two approaches to implementing Cross Program +Invocations (CPIs), each at a different level of abstraction. Both examples are +functionally equivalent. The main purpose is to illustrate the implementation +details of the CPI. + + + + + +The `sol_transfer` instruction included in the example code shows a typical +approach for constructing CPIs using the Anchor framework. + +This approach involves creating a +[`CpiContext`](https://docs.rs/anchor-lang/latest/anchor_lang/context/struct.CpiContext.html), +which includes the `program_id` and accounts required for the instruction being +called, followed by a helper function (`transfer`) to invoke a specific +instruction. + +```rust /cpi_context/ {19} +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.pda_account.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let seed = to_pubkey.key(); + let bump_seed = ctx.bumps.pda_account; + let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]]; + + let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, + ) + .with_signer(signer_seeds); + + transfer(cpi_context, amount)?; + Ok(()) +} +``` + +When signing with PDAs, the seeds and bump seed are included in the +`cpi_context` as `signer_seeds` using `with_signer()`. The bump seed for a PDA +can be accessed using `ctx.bumps` followed by the name of the PDA account. + +```rust /signer_seeds/ /bump_seed/ {3} +let seed = to_pubkey.key(); +let bump_seed = ctx.bumps.pda_account; +let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]]; + +let cpi_context = CpiContext::new( + program_id, + Transfer { + from: from_pubkey, + to: to_pubkey, + }, +) +.with_signer(signer_seeds); +``` + +The `cpi_context` and `amount` are then passed into the `transfer` function to +execute the CPI. + +```rust +transfer(cpi_context, amount)?; +``` + +When the CPI is processed, the Solana runtime will validate that the provided +seeds and caller program ID derive a valid PDA. The PDA is then added as a +signer on the invocation. This mechanism allows for programs to sign for PDAs +that are derived from their program ID. + + + + +Under the hood, the previous example is a wrapper around the `invoke_signed()` +function which uses +[`system_instruction::transfer`](https://github.com/solana-labs/solana/blob/27eff8408b7223bb3c4ab70523f8a8dca3ca6645/sdk/program/src/system_instruction.rs#L881) +to build the instruction. + +This example shows how to use the `invoke_signed()` function to make a CPI +signed for by a PDA. + +```rust +use anchor_lang::solana_program::{program::invoke_signed, system_instruction}; +``` + +```rust /instruction/1,3 {13} +pub fn sol_transfer(ctx: Context, amount: u64) -> Result<()> { + let from_pubkey = ctx.accounts.pda_account.to_account_info(); + let to_pubkey = ctx.accounts.recipient.to_account_info(); + let program_id = ctx.accounts.system_program.to_account_info(); + + let seed = to_pubkey.key(); + let bump_seed = ctx.bumps.pda_account; + let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]]; + + let instruction = + &system_instruction::transfer(&from_pubkey.key(), &to_pubkey.key(), amount); + + invoke_signed(instruction, &[from_pubkey, to_pubkey, program_id], signer_seeds)?; + Ok(()) +} +``` + +This implementation is functionally equivalent to the previous example. The +`signer_seeds` are passed into the `invoke_signed` function. + + + + +Here is a reference program on +[Solana Playground](https://beta.solpg.io/github.com/ZYJLiu/doc-examples/tree/main/cpi-pda) +which includes both examples. diff --git a/docs/programs/anchor/idl.md b/docs/programs/anchor/idl.md new file mode 100644 index 000000000..924980b42 --- /dev/null +++ b/docs/programs/anchor/idl.md @@ -0,0 +1,463 @@ +--- +title: IDL File +description: + Learn about the Interface Description Language (IDL) file in Anchor, its + purpose, benefits, and how it simplifies program-client interactions +sidebarLabel: IDL File +sidebarSortOrder: 2 +--- + +An Interface Description Language (IDL) file provides a standardized JSON file +describing the program's instructions and accounts. This file simplifies the +process of integrating your on-chain program with client applications. + +Key Benefits of the IDL: + +- Standardization: Provides a consistent format for describing the program's + instructions and accounts +- Client Generation: Used to generate client code to interact with the program + +The `anchor build` command generates an IDL file located at +`/target/idl/.json`. + +The code snippets below highlights how the program, IDL, and client relate to +each other. + +## Program Instructions + +The `instructions` array in the IDL corresponds directly to the instructions +defined in your program. It specifies the required accounts and parameters for +each instruction. + + + + + +The program below includes an `initialize` instruction, specifying the accounts +and parameters it requires. + +```rust {8-12, 15-22} +use anchor_lang::prelude::*; + +declare_id!("BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[account] +pub struct NewAccount { + data: u64, +} +``` + + + + +The generated IDL file includes the instruction in a standardized JSON format, +including its name, accounts, arguments, and discriminator. + +```json filename="JSON" {11-12, 14-27, 30-33} +{ + "address": "BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd", + "metadata": { + "name": "hello_anchor", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "accounts": [ + { + "name": "new_account", + "writable": true, + "signer": true + }, + { + "name": "signer", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "data", + "type": "u64" + } + ] + } + ], + "accounts": [ + { + "name": "NewAccount", + "discriminator": [176, 95, 4, 118, 91, 177, 125, 232] + } + ], + "types": [ + { + "name": "NewAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "data", + "type": "u64" + } + ] + } + } + ] +} +``` + + + + +The IDL file is then used to generate a client for interacting with the program, +simplifying the process of invoking the program instruction. + +```ts {19-26} +import * as anchor from "@coral-xyz/anchor"; +import { Program, BN } from "@coral-xyz/anchor"; +import { HelloAnchor } from "../target/types/hello_anchor"; +import { Keypair } from "@solana/web3.js"; +import assert from "assert"; + +describe("hello_anchor", () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + const wallet = provider.wallet as anchor.Wallet; + const program = anchor.workspace.HelloAnchor as Program; + + it("initialize", async () => { + // Generate keypair for the new account + const newAccountKp = new Keypair(); + + // Send transaction + const data = new BN(42); + const transactionSignature = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + }) + .signers([newAccountKp]) + .rpc(); + + // Fetch the created account + const newAccount = await program.account.newAccount.fetch( + newAccountKp.publicKey, + ); + + console.log("Transaction signature: ", transactionSignature); + console.log("On-chain data is:", newAccount.data.toString()); + assert(data.eq(newAccount.data)); + }); +}); +``` + + + + +## Program Accounts + +The `accounts` array in the IDL corresponds to the structs in a program +annotated with the `#[account]` macro. These structs define the data stored in +accounts created by the program. + + + + + +The program below defines a `NewAccount` struct with a single `data` field of +type `u64`. + +```rust {24-27} +use anchor_lang::prelude::*; + +declare_id!("BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[account] +pub struct NewAccount { + data: u64, +} +``` + + + + +The generated IDL file includes the account in a standardized JSON format, +including its name, discriminator, and fields. + +```json filename="JSON" {39-40, 45-54} +{ + "address": "BYFW1vhC1ohxwRbYoLbAWs86STa25i9sD5uEusVjTYNd", + "metadata": { + "name": "hello_anchor", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "accounts": [ + { + "name": "new_account", + "writable": true, + "signer": true + }, + { + "name": "signer", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "data", + "type": "u64" + } + ] + } + ], + "accounts": [ + { + "name": "NewAccount", + "discriminator": [176, 95, 4, 118, 91, 177, 125, 232] + } + ], + "types": [ + { + "name": "NewAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "data", + "type": "u64" + } + ] + } + } + ] +} +``` + + + + +The IDL file is then used to generate a client for interacting with the program, +simplifying the process of fetching and deserializing account data. + +```ts {29-31} +import * as anchor from "@coral-xyz/anchor"; +import { Program, BN } from "@coral-xyz/anchor"; +import { HelloAnchor } from "../target/types/hello_anchor"; +import { Keypair } from "@solana/web3.js"; +import assert from "assert"; + +describe("hello_anchor", () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + const wallet = provider.wallet as anchor.Wallet; + const program = anchor.workspace.HelloAnchor as Program; + + it("initialize", async () => { + // Generate keypair for the new account + const newAccountKp = new Keypair(); + + // Send transaction + const data = new BN(42); + const transactionSignature = await program.methods + .initialize(data) + .accounts({ + newAccount: newAccountKp.publicKey, + signer: wallet.publicKey, + }) + .signers([newAccountKp]) + .rpc(); + + // Fetch the created account + const newAccount = await program.account.newAccount.fetch( + newAccountKp.publicKey, + ); + + console.log("Transaction signature: ", transactionSignature); + console.log("On-chain data is:", newAccount.data.toString()); + assert(data.eq(newAccount.data)); + }); +}); +``` + + + + +## Discriminators + +Anchor assigns a unique 8 byte discriminator to each instruction and account +type in a program. These discriminators serve as identifiers to distinguish +between different instructions or account types. + +The discriminator is generated using the first 8 bytes of the Sha256 hash of a +prefix combined with the instruction or account name. As of Anchor v0.30, these +discriminators are included in the IDL file. + +Note that when working with Anchor, you typically won't need to interact +directly with these discriminators. This section is primarily to provide context +on how the discriminator is generated and used. + + + + + +The instruction discriminator is used by the program to determine which specific +instruction to execute when called. + +When an Anchor program instruction is invoked, the discriminator is included as +the first 8 bytes of the instruction data. This is done automatically by the +Anchor client. + +```json filename="IDL" {4} + "instructions": [ + { + "name": "initialize", + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + ... + } + ] +``` + +The discriminator for an instruction is the first 8 bytes of the Sha256 hash of +the prefix `global` plus the instruction name. + +For example: + +``` +sha256("global:initialize") +``` + +Hexadecimal output: + +``` +af af 6d 1f 0d 98 9b ed d4 6a 95 07 32 81 ad c2 1b b5 e0 e1 d7 73 b2 fb bd 7a b5 04 cd d4 aa 30 +``` + +The first 8 bytes are used as the discriminator for the instruction. + +``` +af = 175 +af = 175 +6d = 109 +1f = 31 +0d = 13 +98 = 152 +9b = 155 +ed = 237 +``` + +You can find the implementation of the discriminator generation in the Anchor +codebase +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/codegen/program/common.rs#L5-L19), +which is used +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/codegen/program/instruction.rs#L27). + + + + +The account discriminator is used to identify the specific account type when +deserializing on-chain data and is set when the account is created. + +```json filename="IDL" {4} + "accounts": [ + { + "name": "NewAccount", + "discriminator": [176, 95, 4, 118, 91, 177, 125, 232] + } + ] +``` + +The discriminator for an account is the first 8 bytes of the Sha256 hash of the +prefix `account` plus the account name. + +For example: + +``` +sha256("account:NewAccount") +``` + +Hexadecimal output: + +``` +b0 5f 04 76 5b b1 7d e8 a1 93 57 2a d3 5e b1 ae e5 f0 69 e2 09 7e 5c d2 64 56 55 2a cb 4a e9 57 +``` + +The first 8 bytes are used as the discriminator for the account. + +``` +b0 = 176 +5f = 95 +04 = 4 +76 = 118 +5b = 91 +b1 = 177 +7d = 125 +e8 = 232 +``` + +You can find the implementation of the discriminator generation in the Anchor +codebase +[here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L101-L117). + +Note that different programs using identical account names will generate the +same discriminator. When deserializing account data, Anchor programs will also +check an account is owned by the expected program for a specified account type. + + + diff --git a/docs/programs/anchor/index.md b/docs/programs/anchor/index.md new file mode 100644 index 000000000..975568575 --- /dev/null +++ b/docs/programs/anchor/index.md @@ -0,0 +1,377 @@ +--- +title: Getting Started with Anchor +description: Getting Started with Anchor +sidebarLabel: Anchor Framework +sidebarSortOrder: 1 +--- + +The Anchor framework is a tool that simplifies the process of building Solana +programs. Whether you're new to blockchain development or an experienced +programmer, Anchor simplifies the process of writing, testing, and deploying +Solana programs. + +In this section, we'll walk through: + +- Creating a new Anchor project +- Building and testing your program +- Deploying to Solana clusters +- Understanding the project file structure + +## Prerequisites + +For detailed installation instructions, visit the +[installation](/docs/intro/installation) page. + +Before you begin, ensure you have the following installed: + +- Rust: The programming language for building Solana programs. +- Solana CLI: Command-line tool for Solana development. +- Anchor CLI: Command-line tool for the Anchor framework. + +To verify Anchor CLI installation, open your terminal and run: + +```shell filename="Terminal" +anchor --version +``` + +Expected output: + +```shell filename="Terminal" +anchor-cli 0.30.1 +``` + +## Getting Started + +This section covers the basic steps to create, build, and test your first local +Anchor program. + + + +### Create a new Project + +To start a new project, use the `anchor init` command followed by your project's +name. This command creates a new directory with the specified name and sets up a +default program and test file. + +```shell filename="Terminal" +anchor init my-program +``` + +Navigate to the new project directory and open it in your code editor. + +```shell filename="Terminal" copy +cd my-project +``` + +The default Anchor program is located at `/programs/my-project/src/lib.rs`. + + + + +The value in the `declare_id!` macro is the program ID, a unique identifier for +your program. + +By default, it is the public key of the keypair generated in +`/target/deploy/my_project-keypair.json`. + +```rs filename="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg"); + +#[program] +pub mod my_project { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + msg!("Greetings from: {:?}", ctx.program_id); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize {} +``` + + + + +The default Typescript test file is located at `/tests/my-project.ts`. + + + + +This file demonstrates how to invoke the default program's `initialize` +instruction in Typescript. + +```ts filename="my-project.ts" +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { MyProject } from "../target/types/my_project"; + +describe("my-project", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.MyProject as Program; + + it("Is initialized!", async () => { + // Add your test here. + const tx = await program.methods.initialize().rpc(); + console.log("Your transaction signature", tx); + }); +}); +``` + + + + +If you prefer Rust for testing, initialize your project with the +`--test-template rust` flag. + +```shell +anchor init --test-template rust my-program +``` + +The Rust test file will be at `/tests/src/test_initialize.rs`. + + + + +```rust filename="test_initialize.rs" +use std::str::FromStr; + +use anchor_client::{ + solana_sdk::{ + commitment_config::CommitmentConfig, pubkey::Pubkey, signature::read_keypair_file, + }, + Client, Cluster, +}; + +#[test] +fn test_initialize() { + let program_id = "3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg"; + let anchor_wallet = std::env::var("ANCHOR_WALLET").unwrap(); + let payer = read_keypair_file(&anchor_wallet).unwrap(); + + let client = Client::new_with_options(Cluster::Localnet, &payer, CommitmentConfig::confirmed()); + let program_id = Pubkey::from_str(program_id).unwrap(); + let program = client.program(program_id).unwrap(); + + let tx = program + .request() + .accounts(my_program::accounts::Initialize {}) + .args(my_program::instruction::Initialize {}) + .send() + .expect(""); + + println!("Your transaction signature {}", tx); +} +``` + + + + +### Build the Program + +Build the program by running `anchor build`. + +```shell filename="Terminal" copy +anchor build +``` + +The compiled program will be at `/target/deploy/my_project.so`. The content of +this file is what gets stored on the Solana network (as an executable account) +when you deploy your program. + +### Test the Program + +To test the program, run `anchor test`. + +```shell filename="Terminal" copy +anchor test +``` + +By default, the `Anchor.toml` config file specifies the `localnet` cluster. When +developing on `localnet`, `anchor test` will automatically: + +1. Start a local Solana validator +2. Build and deploy your program to the local cluster +3. Run the tests in the `tests` folder +4. Stop the local Solana validator + +Alternatively, you can manually start a local Solana validator and run tests +against it. This is useful if you want to keep the validator running while you +iterate on your program. It allows you to inspect accounts and transaction logs +on the [Solana Explorer](https://explorer.solana.com/?cluster=custom) while +developing locally. + +Open a new terminal and start a local Solana validator by running the +`solana-test-validator` command. + +```shell filename="Terminal" copy +solana-test-validator +``` + +In a separate terminal, run the tests against the local cluster. Use the +`--skip-local-validator` flag to skip starting the local validator since it's +already running. + +```shell filename="Terminal" copy +anchor test --skip-local-validator +``` + +### Deploy to Devnet + +By default, the `Anchor.toml` config file in an Anchor project specifies the +localnet cluster. + +```toml filename="Anchor.toml" {14} +[toolchain] + +[features] +resolution = true +skip-lint = false + +[programs.localnet] +my_program = "3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg" + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "Localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" +``` + +To deploy your program to devnet, change the `cluster` value to `Devnet`. Note +that this requires your wallet to have enough SOL on Devnet to cover deployment +cost. + +```diff +-cluster = "Localnet" ++cluster = "Devnet" +``` + +```toml filename="Anchor.toml" +[provider] +cluster = "Devnet" +wallet = "~/.config/solana/id.json" +``` + +Now when you run `anchor deploy`, your program will be deployed to the devnet +cluster. The `anchor test` command will also use the cluster specified in the +`Anchor.toml` file. + +```shell +anchor deploy +``` + +To deploy to mainnet, simply update the `Anchor.toml` file to specify the +mainnet cluster. + +```toml filename="Anchor.toml" +[provider] +cluster = "Mainnet" +wallet = "~/.config/solana/id.json" +``` + +### Update the Program + +Solana programs can be updated by redeploying the program to the same program +ID. + +To update a program, simply make changes to your program's code and run the +`anchor build` command to generated an updated `.so` file. + +```shell +anchor build +``` + +Then run the `anchor deploy` command to redeploy the updated program. + +```shell +anchor deploy +``` + +### Close the Program + +To reclaim the SOL allocated to a program account, you can close your Solana +program. + +To close a program, use the `solana program close ` command. For +example: + +```shell +solana program close 3ynNB373Q3VAzKp7m4x238po36hjAGFXFJB4ybN2iTyg --bypass-warning +``` + +Note that once a program is closed, the program ID cannot be reused to deploy a +new program. + + + +## Project File Structure + +Below is an overview of default file structure in an Anchor workspace: + +``` +. +├── .anchor +│ └── program-logs +├── app +├── migrations +├── programs +│ └── [project-name] +│ └── src +│ ├── lib.rs +│ ├── Cargo.toml +│ └── Xargo.toml +├── target +│ ├── deploy +│ │ └── [project-name]-keypair.json +│ ├── idl +│ │ └── [project-name].json +│ └── types +│ └── [project-name].ts +├── tests +│ └── [project-name].ts +├── Anchor.toml +├── Cargo.toml +└── package.json +``` + +### Programs Folder + +The `/programs` folder contains your project's Anchor programs. A single +workspace can contain multiple programs. + +### Tests Folder + +The `/tests` folder contains test files for your project. A default test file is +created for you when you create your project. + +### Target Folder + +The `/target` folder contains build outputs. The main subfolders include: + +- `/deploy`: Contains the keypair and program binary for your programs. +- `/idl`: Contains the JSON IDL for your programs. +- `/types`: Contains the TypeScript type for the IDL. + +### Anchor.toml File + +The `Anchor.toml` file configures workspace settings for your project. + +### .anchor Folder + +Includes a `program-logs` file that contains transaction logs from the last run +of test files. + +### App Folder + +The `/app` folder is an empty folder that can be optionally used for your +frontend code. diff --git a/docs/programs/anchor/pda.md b/docs/programs/anchor/pda.md new file mode 100644 index 000000000..bec281733 --- /dev/null +++ b/docs/programs/anchor/pda.md @@ -0,0 +1,325 @@ +--- +title: PDAs with Anchor +description: + Learn how to use Program Derived Addresses (PDAs) in Anchor programs, using + constraints, and implementing common PDA patterns +sidebarLabel: PDAs with Anchor +sidebarSortOrder: 4 +--- + +[Program Derived Addresses (PDA)](/docs/core/pda) refer to a feature of Solana +development that allows you to create a unique address derived deterministically +from pre-defined inputs (seeds) and a program ID. + +This section will cover basic examples of how to use PDAs in an Anchor program. + +## Anchor PDA Constraints + +When using PDAs in an Anchor program, you generally use Anchor's account +constraints to define the seeds used to derive the PDA. These constraints serve +as security checks to ensure that the correct address is derived. + +The constraints used to define the PDA seeds include: + +- `seeds`: An array of optional seeds used to derive the PDA. Seeds can be + static values or dynamic references to account data. +- `bump`: The bump seed used to derive the PDA. Used to ensure the address falls + off the Ed25519 curve and is a valid PDA. +- `seeds::program` - (Optional) The program ID used to derive the PDA address. + This constraint is only used to derive a PDA where the program ID is not the + current program. + +The `seeds` and `bump` constraints are required to be used together. + +### Usage Examples + +Below are examples demonstrating how to use PDA constraints in an Anchor +program. + + + + + +The `seeds` constraint specifies the optional values used to derive the PDA. + +#### No Optional Seeds + +- Use an empty array `[]` to define a PDA without optional seeds. + +```rs +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + seeds = [], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + +#### Single Static Seed + +- Specify optional seeds in the `seeds` constraint. + +```rs +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + seeds = [b"hello_world"], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + +#### Multiple Seeds and Account References + +- Multiple seeds can be specified in the `seeds` constraint. The `seeds` + constraint can also reference other account addresses or account data. + +```rs +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + pub signer: Signer<'info>, + #[account( + seeds = [b"hello_world", signer.key().as_ref()], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + +The example above uses both a static seed (`b"hello_world"`) and a dynamic seed +(the signer's public key). + + + + +The `bump` constraint specifies the bump seed used to derive the PDA. + +#### Automatic Bump Calculation + +When using the `bump` constraint without a value, the bump is automatically +calculated each time the instruction is invoked. + +```rs +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + seeds = [b"hello_world"], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + +#### Specify Bump Value + +You can explicitly provide the bump value, which is useful for optimizing +compute unit usage. This assumes that the PDA account has been created and the +bump seed is stored as a field on an existing account. + +```rs +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + seeds = [b"hello_world"], + bump = pda_account.bump_seed, + )] + pub pda_account: Account<'info, CustomAccount>, +} + +#[account] +pub struct CustomAccount { + pub bump_seed: u8, +} +``` + +By storing the bump value in the account's data, the program doesn't need to +recalculate it, saving compute units. The saved bump value can be stored on the +account itself or another account. + + + + +The `seeds::program` constraint specifies the program ID used to derive the PDA. +This constraint is only used when deriving a PDA from a different program. + +Use this constraint when your instruction needs to interact with PDA accounts +created by another program. + +```rs +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account( + seeds = [b"hello_world"], + bump, + seeds::program = other_program.key(), + )] + pub pda_account: SystemAccount<'info>, + pub other_program: Program<'info, OtherProgram>, +} +``` + + + + +The `init` constraint is commonly used with `seeds` and `bump` to create a new +account with an address that is a PDA. Under the hood, the `init` constraint +invokes the System Program to create the account. + +```rs +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + #[account(mut)] + pub signer: Signer<'info>, + #[account( + init, + seeds = [b"hello_world", signer.key().as_ref()], + bump, + payer = signer, + space = 8 + 1, + )] + pub pda_account: Account<'info, CustomAccount>, + pub system_program: Program<'info, System>, +} + +#[account] +pub struct CustomAccount { + pub bump_seed: u8, +} +``` + + + + +## PDA seeds in the IDL + +Program Derived Address (PDA) seeds defined in the `seeds` constraint are +included in the program's IDL file. This allows the Anchor client to +automatically resolve accounts using these seeds when constructing instructions. + +This example below shows the relationship between the program, IDL, and client. + + + + + +The program below defines a `pda_account` using a static seed (`b"hello_world"`) +and the signer's public key as a dynamic seed. + +```rs {18} /signer/ +use anchor_lang::prelude::*; + +declare_id!("BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5"); + +#[program] +mod hello_anchor { + use super::*; + pub fn test_instruction(ctx: Context) -> Result<()> { + msg!("PDA: {}", ctx.accounts.pda_account.key()); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct InstructionAccounts<'info> { + pub signer: Signer<'info>, + #[account( + seeds = [b"hello_world", signer.key().as_ref()], + bump, + )] + pub pda_account: SystemAccount<'info>, +} +``` + + + + +The program's IDL file includes the PDA seeds defined in the `seeds` constraint. + +- The static seed `b"hello_world"` is converted to byte values. +- The dynamic seed is included as reference to the signer account. + +```json {22-29} +{ + "address": "BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5", + "metadata": { + "name": "hello_anchor", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "test_instruction", + "discriminator": [33, 223, 61, 208, 32, 193, 201, 79], + "accounts": [ + { + "name": "signer", + "signer": true + }, + { + "name": "pda_account", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [104, 101, 108, 108, 111, 95, 119, 111, 114, 108, 100] + }, + { + "kind": "account", + "path": "signer" + } + ] + } + } + ], + "args": [] + } + ] +} +``` + + + + +The Anchor client can automatically resolve the PDA address using the IDL file. + +In the example below, Anchor automatically resolves the PDA address using the +provider wallet as the signer, and its public key as the dynamic seed for PDA +derivation. This removes the need to explicitly derive the PDA when building the +instruction. + +```ts {13} +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { HelloAnchor } from "../target/types/hello_anchor"; + +describe("hello_anchor", () => { + // Configure the client to use the local cluster. + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.HelloAnchor as Program; + + it("Is initialized!", async () => { + // Add your test here. + const tx = await program.methods.testInstruction().rpc(); + console.log("Your transaction signature", tx); + }); +}); +``` + +When the instruction is invoked, the PDA is printed to program logs as defined +in the program instruction. + +```{3} +Program BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5 invoke [1] +Program log: Instruction: TestInstruction +Program log: PDA: 3Hikt5mpKaSS4UNA5Du1TZJ8tp4o8VC8YWW6X9vtfVnJ +Program BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5 consumed 18505 of 200000 compute units +Program BZLiJ62bzRryYp9mRobz47uA66WDgtfTXhhgM25tJyx5 success +``` + + + diff --git a/docs/programs/anchor/program-structure.md b/docs/programs/anchor/program-structure.md new file mode 100644 index 000000000..a8b0f2475 --- /dev/null +++ b/docs/programs/anchor/program-structure.md @@ -0,0 +1,399 @@ +--- +title: Anchor Program Structure +description: + Learn about the structure of Anchor programs, including key macros and their + roles in simplifying Solana program development +sidebarLabel: Program Structure +sidebarSortOrder: 1 +--- + +The [Anchor framework](https://www.anchor-lang.com/) uses +[Rust macros](https://doc.rust-lang.org/book/ch19-06-macros.html) to reduce +boilerplate code and simplify the implementation of common security checks +required for writing Solana programs. + +The main macros found in an Anchor program include: + +- [`declare_id`](#declare-id-macro): Specifies the program's on-chain address +- [`#[program]`](#program-macro): Specifies the module containing the program’s + instruction logic +- [`#[derive(Accounts)]`](#derive-accounts-macro): Applied to structs to + indicate a list of accounts required by an instruction +- [`#[account]`](#account-macro): Applied to structs to create custom account + types for the program + +## Example Program + +Let's examine a simple program that demonstrates the usage of the macros +mentioned above to understand the basic structure of an Anchor program. + +The example program below creates a new account (`NewAccount`) that stores a +`u64` value passed to the `initialize` instruction. + +```rust filename="lib.rs" +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[account] +pub struct NewAccount { + data: u64, +} +``` + +## declare_id! macro + +The +[`declare_id`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L430) +macro specifies the on-chain address of the program, known as the program ID. + +```rust filename="lib.rs" {3} +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); +``` + +By default, the program ID is the public key of the keypair generated at +`/target/deploy/your_program_name.json`. + +To update the value of the program ID in the `declare_id` macro with the public +key of the keypair in the `/target/deploy/your_program_name.json` file, run the +following command: + +```shell filename="Terminal" +anchor keys sync +``` + +The `anchor keys sync` command is useful to run when cloning a repository where +the value of the program ID in a cloned repo's `declare_id` macro won't match +the one generated when you run `anchor build` locally. + +## #[program] macro + +The +[`#[program]`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/program/src/lib.rs#L12) +macro defines the module that contains all the instruction handlers for your +program. Each public function within this module corresponds to an instruction +that can be invoked. + +```rust filename="lib.rs" {5, 8-12} +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[account] +pub struct NewAccount { + data: u64, +} +``` + +### Instruction Context + +Instruction handlers are functions that define the logic executed when an +instruction is invoked. The first parameter of each handler is a `Context` +type, where `T` is a struct implementing the `Accounts` trait and specifies the +accounts the instruction requires. + +The +[`Context`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/context.rs#L24) +type provides the instruction with access to the following non-argument inputs: + +```rust +pub struct Context<'a, 'b, 'c, 'info, T> { + /// Currently executing program id. + pub program_id: &'a Pubkey, + /// Deserialized accounts. + pub accounts: &'b mut T, + /// Remaining accounts given but not deserialized or validated. + /// Be very careful when using this directly. + pub remaining_accounts: &'c [AccountInfo<'info>], + /// Bump seeds found during constraint validation. This is provided as a + /// convenience so that handlers don't have to recalculate bump seeds or + /// pass them in as arguments. + pub bumps: BTreeMap, +} +``` + +The `Context` fields can be accessed in an instruction using dot notation: + +- `ctx.accounts`: The accounts required for the instruction +- `ctx.program_id`: The program's public key (address) +- `ctx.remaining_accounts`: Additional accounts not specified in the `Accounts` + struct. +- `ctx.bumps`: Bump seeds for any + [Program Derived Address (PDA)](/docs/core/pda.md) accounts specified in the + `Accounts` struct + +Additional parameters are optional and can be included to specify arguments that +must be provided when the instruction is invoked. + +```rust filename="lib.rs" /Context/ /data/1 +pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) +} +``` + +In this example, the `Initialize` struct implements the `Accounts` trait where +each field in the struct represents an account required by the `initialize` +instruction. + +```rust filename="lib.rs" /Initialize/ /Accounts/ +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +## #[derive(Accounts)] macro + +The +[`#[derive(Accounts)]`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/derive/accounts/src/lib.rs#L630) +macro is applied to a struct to specify the accounts that must be provided when +an instruction is invoked. This macro implements the +[`Accounts`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/lib.rs#L105) +trait, which simplifies account validation and serialization and deserialization +of account data. + +```rust /Accounts/ {1} +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +Each field in the struct represents an account required by an instruction. The +naming of each field is arbitrary, but it is recommended to use a descriptive +name that indicates the purpose of the account. + +```rust /signer/2 /new_account/ /system_program/ +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +### Account Validation + +To prevent security vulnerabiliies, it's important to verify that accounts +provided to an instruction are the expected accounts. Accounts are validated in +Anchor programs in two ways that are generally used together: + +- [Account Constraints](https://www.anchor-lang.com/docs/account-constraints): + Constraints define additional conditions that an account must satisfy to be + considered valid for the instruction. Constraints are applied using the + `#[account(..)]` attribute, which is placed above a field in a struct that + implements the `Accounts` trait. + + You can find the implementation of the constraints + [here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/syn/src/parser/accounts/constraints.rs). + + ```rust {3, 5} + #[derive(Accounts)] + pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, + } + ``` + +- [Account Types](https://www.anchor-lang.com/docs/account-types): Anchor + provides various account types to help ensure that the account provided by the + client matches what the program expects. + + You can find the implementation of the account types + [here](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/src/accounts). + + ```rust /Account/2 /Signer/ /Program/ + #[derive(Accounts)] + pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, + } + ``` + +When an instruction in an Anchor program is invoked, the program first validates +the accounts provided before executing the instruction's logic. After +validation, these accounts can be accessed within the instruction using the +`ctx.accounts` syntax. + +```rust filename="lib.rs" /ctx.accounts.new_account/ /new_account/ /Initialize/ +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[account] +pub struct NewAccount { + data: u64, +} +``` + +## #[account] macro + +The +[`#[account]`](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L66) +macro is applied to structs that define the data stored in custom accounts +created by your program. + +```rust +#[account] +pub struct NewAccount { + data: u64, +} +``` + +This macro implements various traits +[detailed here](https://docs.rs/anchor-lang/latest/anchor_lang/attr.account.html). +The key functionalities of the `#[account]` macro include: + +- [Assign Program Owner](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L119-L132): + When creating an account, the program owner of the account is automatically + set to the program specified in `declare_id`. +- [Set Discriminator](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L101-L117): + A unique 8 byte discriminator, specific to the account type, is added as the + first 8 bytes of account data during its initialization. This helps in + differentiating account types and is used for account validation. +- [Data Serialization and Deserialization](https://github.com/coral-xyz/anchor/blob/v0.30.1/lang/attribute/account/src/lib.rs#L202-L246): + Account data is automatically serialized and deserialized as the account type. + +```rust filename="lib.rs" /data/2,6 /NewAccount/ {24-27} +use anchor_lang::prelude::*; + +declare_id!("11111111111111111111111111111111"); + +#[program] +mod hello_anchor { + use super::*; + pub fn initialize(ctx: Context, data: u64) -> Result<()> { + ctx.accounts.new_account.data = data; + msg!("Changed data to: {}!", data); + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = signer, space = 8 + 8)] + pub new_account: Account<'info, NewAccount>, + #[account(mut)] + pub signer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[account] +pub struct NewAccount { + data: u64, +} +``` + +### Account Discriminator + +An account discriminator in an Anchor program refers to an 8 byte identifier +unique to each account type. It's derived from the first 8 bytes of the SHA256 +hash of the string `account:`. This discriminator is stored as the +first 8 bytes of account data when an account is created. + +When creating an account in an Anchor program, 8 bytes must be allocated for the +discriminator. + +```rust /8/1 +#[account(init, payer = signer, space = 8 + 8)] +pub new_account: Account<'info, NewAccount>, +``` + +The discriminator is used during the following two scenarios: + +- Initialization: When an account is created, the discriminator is set as the + first 8 bytes of the account's data. +- Deserialization: When account data is deserialized, the first 8 bytes of + account data is checked against the discriminator of the expected account + type. + +If there's a mismatch, it indicates that the client has provided an unexpected +account. This mechanism serves as an account validation check in Anchor +programs. diff --git a/docs/programs/debugging.md b/docs/programs/debugging.md deleted file mode 100644 index 1f2ace740..000000000 --- a/docs/programs/debugging.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -title: "Debugging Programs" ---- - -Solana programs run on-chain, so debugging them in the wild can be challenging. -To make debugging programs easier, developers can write unit tests that directly -test their program's execution via the Solana runtime, or run a local cluster -that will allow RPC clients to interact with their program. - -## Running unit tests - -- [Testing with Rust](/docs/programs/lang-rust.md#how-to-test) -- [Testing with C](/docs/programs/lang-c.md#how-to-test) - -## Logging - -During program execution both the runtime and the program log status and error -messages. - -For information about how to log from a program see the language specific -documentation: - -- [Logging from a Rust program](/docs/programs/lang-rust.md#logging) -- [Logging from a C program](/docs/programs/lang-c.md#logging) - -When running a local cluster the logs are written to stdout as long as they are -enabled via the `RUST_LOG` log mask. From the perspective of program development -it is helpful to focus on just the runtime and program logs and not the rest of -the cluster logs. To focus in on program specific information the following log -mask is recommended: - -```shell -export RUST_LOG=solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=info,solana_bpf_loader=debug,solana_rbpf=debug -``` - -Log messages coming directly from the program (not the runtime) will be -displayed in the form: - -`Program log: ` - -## Error Handling - -The amount of information that can be communicated via a transaction error is -limited but there are many points of possible failures. The following are -possible failure points and information about what errors to expect and where to -get more information: - -- The SBF loader may fail to parse the program, this should not happen since the - loader has already _finalized_ the program's account data. - - `InstructionError::InvalidAccountData` will be returned as part of the - transaction error. -- The SBF loader may fail to setup the program's execution environment - - `InstructionError::Custom(0x0b9f_0001)` will be returned as part of the - transaction error. "0x0b9f_0001" is the hexadecimal representation of - [`VirtualMachineCreationFailed`](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/programs/bpf_loader/src/lib.rs#L44). -- The SBF loader may have detected a fatal error during program executions - (things like panics, memory violations, system call errors, etc...) - - `InstructionError::Custom(0x0b9f_0002)` will be returned as part of the - transaction error. "0x0b9f_0002" is the hexadecimal representation of - [`VirtualMachineFailedToRunProgram`](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/programs/bpf_loader/src/lib.rs#L46). -- The program itself may return an error - - `InstructionError::Custom()` will be returned. The "user - defined value" must not conflict with any of the - [builtin runtime program errors](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/sdk/program/src/program_error.rs#L87). - Programs typically use enumeration types to define error codes starting at - zero so they won't conflict. - -In the case of `VirtualMachineFailedToRunProgram` errors, more information about -the specifics of what failed are written to the -[program's execution logs](/docs/programs/debugging.md#logging). - -For example, an access violation involving the stack will look something like -this: - -```text -SBF program 4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM failed: out of bounds memory store (insn #615), addr 0x200001e38/8 -``` - -## Monitoring Compute Budget Consumption - -The program can log the remaining number of compute units it will be allowed -before program execution is halted. Programs can use these logs to wrap -operations they wish to profile. - -- [Log the remaining compute units from a Rust program](/docs/programs/lang-rust.md#compute-budget) -- [Log the remaining compute units from a C program](/docs/programs/lang-c.md#compute-budget) - -See [compute budget](/docs/core/fees.md#compute-budget) for more information. - -## ELF Dump - -The SBF shared object internals can be dumped to a text file to gain more -insight into a program's composition and what it may be doing at runtime. - -- [Create a dump file of a Rust program](/docs/programs/lang-rust.md#elf-dump) -- [Create a dump file of a C program](/docs/programs/lang-c.md#elf-dump) - -## Instruction Tracing - -During execution the runtime SBF interpreter can be configured to log a trace -message for each SBF instruction executed. This can be very helpful for things -like pin-pointing the runtime context leading up to a memory access violation. - -The trace logs together with the [ELF dump](#elf-dump) can provide a lot of -insight (though the traces produce a lot of information). - -To turn on SBF interpreter trace messages in a local cluster configure the -`solana_rbpf` level in `RUST_LOG` to `trace`. For example: - -`export RUST_LOG=solana_rbpf=trace` - -## Source level debugging - -Source level debugging of onchain programs written in Rust or C can be done -using the `program run` subcommand of `solana-ledger-tool`, and lldb, -distributed with Solana Rust and Clang compiler binary package platform-tools. - -The `solana-ledger-tool program run` subcommand loads a compiled on-chain -program, executes it in RBPF virtual machine and runs a gdb server that accepts -incoming connections from LLDB or GDB. Once lldb is connected to -`solana-ledger-tool` gdbserver, it can control execution of an on-chain program. -Run `solana-ledger-tool program run --help` for an example of specifying input -data for parameters of the program entrypoint function. - -To compile a program for debugging use cargo-build-sbf build utility with the -command line option `--debug`. The utility will generate two loadable files, one -a usual loadable module with the extension `.so`, and another the same loadable -module but containing Dwarf debug information, a file with extension `.debug`. - -To execute a program in debugger, run `solana-ledger-tool program run` with -`-e debugger` command line option. For example, a crate named 'helloworld' is -compiled and an executable program is built in `target/deploy` directory. There -should be three files in that directory - -- helloworld-keypair.json -- a keypair for deploying the program, -- helloworld.debug -- a binary file containing debug information, -- helloworld.so -- an executable file loadable into the virtual machine. The - command line for running `solana-ledger-tool` would be something like this - -```shell -solana-ledger-tool program run -l test-ledger -e debugger target/deploy/helloworld.so -``` - -Note that `solana-ledger-tool` always loads a ledger database. Most on-chain -programs interact with a ledger in some manner. Even if for debugging purpose a -ledger is not needed, it has to be provided to `solana-ledger-tool`. A minimal -ledger database can be created by running `solana-test-validator`, which creates -a ledger in `test-ledger` subdirectory. - -In debugger mode `solana-ledger-tool program run` loads an `.so` file and starts -listening for an incoming connection from a debugger - -```text -Waiting for a Debugger connection on "127.0.0.1:9001"... -``` - -To connect to `solana-ledger-tool` and execute the program, run lldb. For -debugging rust programs it may be beneficial to run solana-lldb wrapper to lldb, -i.e. at a new shell prompt (other than the one used to start -`solana-ledger-tool`) run the command: - -```shell -solana-lldb -``` - -This script is installed in platform-tools path. If that path is not added to -`PATH` environment variable, it may be necessary to specify the full path, e.g. - -```text -~/.cache/solana/v1.35/platform-tools/llvm/bin/solana-lldb -``` - -After starting the debugger, load the .debug file by entering the following -command at the debugger prompt - -```text -(lldb) file target/deploy/helloworld.debug -``` - -If the debugger finds the file, it will print something like this - -```text -Current executable set to '/path/helloworld.debug' (bpf). -``` - -Now, connect to the gdb server that `solana-ledger-tool` implements, and debug -the program as usual. Enter the following command at lldb prompt - -```text -(lldb) gdb-remote 127.0.0.1:9001 -``` - -If the debugger and the gdb server establish a connection, the execution of the -program will be stopped at the entrypoint function, and lldb should print -several lines of the source code around the entrypoint function signature. From -this point on, normal lldb commands can be used to control execution of the -program being debugged. - -### Debugging in an IDE - -To debug onchain programs in Visual Studio IDE, install the CodeLLDB extension. -Open CodeLLDB Extension Settings. In Advanced settings change the value of -`Lldb: Library` field to the path of `liblldb.so` (or liblldb.dylib on macOS). -For example on Linux a possible path to Solana customized lldb can be -`/home//.cache/solana/v1.33/platform-tools/llvm/lib/liblldb.so.` where -`` is your Linux system username. This can also be added directly to -`~/.config/Code/User/settings.json` file, e.g. - -```json -{ - "lldb.library": "/home//.cache/solana/v1.35/platform-tools/llvm/lib/liblldb.so" -} -``` - -In `.vscode` subdirectory of your on-chain project, create two files - -First file is `tasks.json` with the following content - -```json -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "type": "shell", - "command": "cargo build-sbf --debug", - "problemMatcher": [], - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "label": "solana-debugger", - "type": "shell", - "command": "solana-ledger-tool program run -l test-ledger -e debugger ${workspaceFolder}/target/deploy/helloworld.so" - } - ] -} -``` - -The first task is to build the on-chain program using cargo-build-sbf utility. -The second task is to run `solana-ledger-tool program run` in debugger mode. - -Another file is `launch.json` with the following content - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "custom", - "name": "Debug", - "targetCreateCommands": [ - "target create ${workspaceFolder}/target/deploy/helloworld.debug" - ], - "processCreateCommands": ["gdb-remote 127.0.0.1:9001"] - } - ] -} -``` - -This file specifies how to run debugger and to connect it to the gdb server -implemented by `solana-ledger-tool`. - -To start debugging a program, first build it by running the build task. The next -step is to run `solana-debugger` task. The tasks specified in `tasks.json` file -are started from `Terminal >> Run Task...` menu of VSCode. When -`solana-ledger-tool` is running and listening from incoming connections, it's -time to start the debugger. Launch it from VSCode `Run and Debug` menu. If -everything is set up correctly, VSCode will start a debugging session and the -program execution should stop on the entrance into the `entrypoint` function. diff --git a/docs/programs/deploying.md b/docs/programs/deploying.md index 78aa89fc9..934ec1925 100644 --- a/docs/programs/deploying.md +++ b/docs/programs/deploying.md @@ -4,6 +4,7 @@ description: "Deploying onchain programs can be done using the Solana CLI using the Upgradable BPF loader to upload the compiled byte-code to the Solana blockchain." +sidebarSortOrder: 4 --- Solana onchain programs (otherwise known as "smart contracts") are stored in @@ -140,19 +141,6 @@ on developers who deploy their own programs since [program accounts](/docs/core/accounts.md#custom-programs) are among the largest we typically see on Solana. -#### Example of how much data is used for programs - -As a data point of the number of accounts and potential data stored on-chain, -below is the distribution of the largest accounts (at least 100KB) at slot -`103,089,804` on `mainnet-beta` by assigned on-chain program: - -1. **Serum Dex v3**: 1798 accounts -2. **Metaplex Candy Machine**: 1089 accounts -3. **Serum Dex v2**: 864 accounts -4. **Upgradeable BPF Program Loader**: 824 accounts -5. **BPF Program Loader v2**: 191 accounts -6. **BPF Program Loader v1**: 150 accounts - ### Reclaiming buffer accounts Buffer accounts are used by the Upgradeable BPF loader to temporarily store @@ -165,9 +153,6 @@ account, developers might retry their deployment with a new buffer and not realize that they stored a good chunk of SOL in a forgotten buffer account from an earlier deploy. -> As of slot `103,089,804` on `mainnet-beta` there are 276 abandoned buffer -> accounts that could be reclaimed! - Developers can check if they own any abandoned buffer accounts by using the Solana CLI: diff --git a/docs/programs/examples.md b/docs/programs/examples.md index bd70d765d..1d0c969b9 100644 --- a/docs/programs/examples.md +++ b/docs/programs/examples.md @@ -1,5 +1,4 @@ --- -date: 2024-04-26T00:00:00Z title: "Program Examples" description: "A list of Solana program examples in different languages and frameworks, @@ -24,10 +23,11 @@ keywords: - blockchain tutorial - web3 developer - anchor +sidebarSortOrder: 2 --- The -"[Solana Program Examples](https://github.com/solana-developers/program-examples)" +[Solana Program Examples](https://github.com/solana-developers/program-examples) repository on GitHub offers several subfolders, each containing code examples for different Solana programming paradigms and languages, designed to help developers learn and experiment with Solana blockchain development. @@ -35,9 +35,8 @@ developers learn and experiment with Solana blockchain development. You can find the examples in the `solana-developers/program-examples` together with README files that explain you how to run the different examples. Most examples are self-contained and are available in native Rust (ie, with no -framework), [Anchor](https://www.anchor-lang.com/docs/installation), -[Seahorse](https://seahorse-lang.org/) and it also contains a list of examples -that we would love to +framework) and [Anchor](https://www.anchor-lang.com/docs/installation). It also +contains a list of examples that we would love to [see as contributions](https://github.com/solana-developers/program-examples?tab=readme-ov-file#examples-wed-love-to-see). Within the repo you will find the following subfolder, each with assorted example programs within them: @@ -56,22 +55,22 @@ Contains a series of examples that demonstrate the foundational steps for building Solana programs using native Rust libraries. These examples are designed to help developers understand the core concepts of Solana programming. -| Example Name | Description | Language | -| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------- | -| [Account Data](https://github.com/solana-developers/program-examples/tree/main/basics/account-data) | Saving an address with name, house number, street and city in an account. | Native, Anchor | -| [Checking Accounts](https://github.com/solana-developers/program-examples/tree/main/basics/checking-accounts) | Security lessons that shows how to do account checks | Native, Anchor | -| [Close Account](https://github.com/solana-developers/program-examples/tree/main/basics/close-account) | Show you how to close accounts to get its rent back. | Native, Anchor | -| [Counter](https://github.com/solana-developers/program-examples/tree/main/basics/counter) | A simple counter program in all the different architectures. | Native, Anchor, Seahorse, mpl-stack | -| [Create Account](https://github.com/solana-developers/program-examples/tree/main/basics/create-account) | How to create a system account within a program. | Native, Anchor | -| [Cross Program Invocation](https://github.com/solana-developers/program-examples/tree/main/basics/cross-program-invocation) | Using a hand and lever analogy this shows you how to call another program from within a program. | Native, Anchor | -| [hello solana](https://github.com/solana-developers/program-examples/tree/main/basics/hello-solana) | Hello world example which just prints hello world in the transaction logs. | Native, Anchor | -| [Pda Rent payer](https://github.com/solana-developers/program-examples/tree/main/basics/pda-rent-payer) | Shows you how you can use the lamports from a PDA to pay for a new account. | Native, Anchor | -| [Processing Instructions](https://github.com/solana-developers/program-examples/tree/main/basics/processing-instructions) | Shows you how to handle instruction data string and u32. | Native, Anchor | -| [Program Derived Addresses](https://github.com/solana-developers/program-examples/tree/main/basics/program-derived-addresses) | Shows how to use seeds to refer to a PDA and save data in it. | Native, Anchor | -| [Realloc](https://github.com/solana-developers/program-examples/tree/main/basics/realloc) | Shows you how to increase and decrease the size of an existing account. | Native, Anchor | -| [Rent](https://github.com/solana-developers/program-examples/tree/main/basics/rent) | Here you will learn how to calculate rent requirements within a program. | Native, Anchor | -| [Repository Layout](https://github.com/solana-developers/program-examples/tree/main/basics/repository-layout) | Recommendations on how to structure your program layout. | Native, Anchor | -| [Transfer SOL](https://github.com/solana-developers/program-examples/tree/main/basics/transfer-sol) | Different methods of transferring SOL for system accounts and PDAs. | Native, Anchor, Seahorse | +| Example Name | Description | Language | +| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------- | +| [Account Data](https://github.com/solana-developers/program-examples/tree/main/basics/account-data) | Saving an address with name, house number, street and city in an account. | Native, Anchor | +| [Checking Accounts](https://github.com/solana-developers/program-examples/tree/main/basics/checking-accounts) | Security lessons that shows how to do account checks | Native, Anchor | +| [Close Account](https://github.com/solana-developers/program-examples/tree/main/basics/close-account) | Show you how to close accounts to get its rent back. | Native, Anchor | +| [Counter](https://github.com/solana-developers/program-examples/tree/main/basics/counter) | A simple counter program in all the different architectures. | Native, Anchor, mpl-stack | +| [Create Account](https://github.com/solana-developers/program-examples/tree/main/basics/create-account) | How to create a system account within a program. | Native, Anchor | +| [Cross Program Invocation](https://github.com/solana-developers/program-examples/tree/main/basics/cross-program-invocation) | Using a hand and lever analogy this shows you how to call another program from within a program. | Native, Anchor | +| [hello solana](https://github.com/solana-developers/program-examples/tree/main/basics/hello-solana) | Hello world example which just prints hello world in the transaction logs. | Native, Anchor | +| [Pda Rent payer](https://github.com/solana-developers/program-examples/tree/main/basics/pda-rent-payer) | Shows you how you can use the lamports from a PDA to pay for a new account. | Native, Anchor | +| [Processing Instructions](https://github.com/solana-developers/program-examples/tree/main/basics/processing-instructions) | Shows you how to handle instruction data string and u32. | Native, Anchor | +| [Program Derived Addresses](https://github.com/solana-developers/program-examples/tree/main/basics/program-derived-addresses) | Shows how to use seeds to refer to a PDA and save data in it. | Native, Anchor | +| [Realloc](https://github.com/solana-developers/program-examples/tree/main/basics/realloc) | Shows you how to increase and decrease the size of an existing account. | Native, Anchor | +| [Rent](https://github.com/solana-developers/program-examples/tree/main/basics/rent) | Here you will learn how to calculate rent requirements within a program. | Native, Anchor | +| [Repository Layout](https://github.com/solana-developers/program-examples/tree/main/basics/repository-layout) | Recommendations on how to structure your program layout. | Native, Anchor | +| [Transfer SOL](https://github.com/solana-developers/program-examples/tree/main/basics/transfer-sol) | Different methods of transferring SOL for system accounts and PDAs. | Native, Anchor, Seahorse | ## Compression @@ -89,9 +88,9 @@ focused on compressed NFTs (cNFTs). Oracles allow to use off chain data in programs. -| Example Name | Description | Language | -| ------------------------------------------------------------------------------------ | --------------------------------------------------------------- | ---------------- | -| [Pyth](https://github.com/solana-developers/program-examples/tree/main/oracles/pyth) | Pyth makes price data of tokens available in on chain programs. | Anchor, Seahorse | +| Example Name | Description | Language | +| ------------------------------------------------------------------------------------ | --------------------------------------------------------------- | -------- | +| [Pyth](https://github.com/solana-developers/program-examples/tree/main/oracles/pyth) | Pyth makes price data of tokens available in on chain programs. | Anchor | ## Tokens @@ -99,15 +98,15 @@ Most tokens on Solana use the Solana Program Library (SPL) token standard. Here you can find many examples on how to mint, transfer, burn tokens and even how to interact with them in programs. -| Example Name | Description | Language | -| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------ | -| [Create Token](https://github.com/solana-developers/program-examples/tree/main/tokens/create-token) | How to create a token and add metaplex metadata to it. | Anchor, Native | -| [NFT Minter](https://github.com/solana-developers/program-examples/tree/main/tokens/nft-minter) | Minting only one amount of a token and then removing the mint authority. | Anchor, Native | -| [PDA Mint Authority](https://github.com/solana-developers/program-examples/tree/main/tokens/pda-mint-authority) | Shows you how to change the mint authority of a mint, to mint tokens from within a program. | Anchor, Native | -| [SPL Token Minter](https://github.com/solana-developers/program-examples/tree/main/tokens/spl-token-minter) | Explains how to use Associated Token Accounts to be able to keep track of token accounts. | Anchor, Native | -| [Token Swap](https://github.com/solana-developers/program-examples/tree/main/tokens/token-swap) | Extensive example that shows you how to build a AMM (automated market maker) pool for SPL tokens. | Anchor | -| [Transfer Tokens](https://github.com/solana-developers/program-examples/tree/main/tokens/transfer-tokens) | Shows how to transfer SPL token using CPIs into the token program. | Anchor, Native, Seahorse | -| [Token-2022](https://github.com/solana-developers/program-examples/tree/main/tokens/token-2022) | See Token 2022 (Token extensions). | Anchor, Native | +| Example Name | Description | Language | +| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------- | +| [Create Token](https://github.com/solana-developers/program-examples/tree/main/tokens/create-token) | How to create a token and add metaplex metadata to it. | Anchor, Native | +| [NFT Minter](https://github.com/solana-developers/program-examples/tree/main/tokens/nft-minter) | Minting only one amount of a token and then removing the mint authority. | Anchor, Native | +| [PDA Mint Authority](https://github.com/solana-developers/program-examples/tree/main/tokens/pda-mint-authority) | Shows you how to change the mint authority of a mint, to mint tokens from within a program. | Anchor, Native | +| [SPL Token Minter](https://github.com/solana-developers/program-examples/tree/main/tokens/spl-token-minter) | Explains how to use Associated Token Accounts to be able to keep track of token accounts. | Anchor, Native | +| [Token Swap](https://github.com/solana-developers/program-examples/tree/main/tokens/token-swap) | Extensive example that shows you how to build a AMM (automated market maker) pool for SPL tokens. | Anchor | +| [Transfer Tokens](https://github.com/solana-developers/program-examples/tree/main/tokens/transfer-tokens) | Shows how to transfer SPL token using CPIs into the token program. | Anchor, Native | +| [Token-2022](https://github.com/solana-developers/program-examples/tree/main/tokens/token-2022) | See Token 2022 (Token extensions). | Anchor, Native | ## Token 2022 (Token Extensions) diff --git a/docs/programs/faq.md b/docs/programs/faq.md deleted file mode 100644 index a5dd87b8c..000000000 --- a/docs/programs/faq.md +++ /dev/null @@ -1,225 +0,0 @@ ---- -title: "FAQ" ---- - -When writing or interacting with Solana programs, there are common questions or -challenges that often come up. Below are resources to help answer these -questions. - -If not addressed here, ask on -[StackExchange](https://solana.stackexchange.com/questions/ask?tags=solana-program) -with the `solana-program` tag. - -## Limitations - -Developing programs on the Solana blockchain have some inherent limitation -associated with them. Below is a list of common limitation that you may run -into. - -See [limitations of developing programs](/docs/programs/limitations.md) for more -details - -## Berkeley Packet Filter (BPF) - -Solana onchain programs are compiled via the -[LLVM compiler infrastructure](https://llvm.org/) to an -[Executable and Linkable Format (ELF)](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) -containing a variation of the -[Berkeley Packet Filter (BPF)](https://en.wikipedia.org/wiki/Berkeley_Packet_Filter) -bytecode. - -Because Solana uses the LLVM compiler infrastructure, a program may be written -in any programming language that can target the LLVM's BPF backend. - -BPF provides an efficient -[instruction set](https://github.com/iovisor/bpf-docs/blob/master/eBPF.md) that -can be executed in an interpreted virtual machine or as efficient just-in-time -compiled native instructions. - -## Memory map - -The virtual address memory map used by Solana SBF programs is fixed and laid out -as follows - -- Program code starts at 0x100000000 -- Stack data starts at 0x200000000 -- Heap data starts at 0x300000000 -- Program input parameters start at 0x400000000 - -The above virtual addresses are start addresses but programs are given access to -a subset of the memory map. The program will panic if it attempts to read or -write to a virtual address that it was not granted access to, and an -`AccessViolation` error will be returned that contains the address and size of -the attempted violation. - -## InvalidAccountData - -This program error can happen for a lot of reasons. Usually, it's caused by -passing an account to the program that the program is not expecting, either in -the wrong position in the instruction or an account not compatible with the -instruction being executed. - -An implementation of a program might also cause this error when performing a -cross-program instruction and forgetting to provide the account for the program -that you are calling. - -## InvalidInstructionData - -This program error can occur while trying to deserialize the instruction, check -that the structure passed in matches exactly the instruction. There may be some -padding between fields. If the program implements the Rust `Pack` trait then try -packing and unpacking the instruction type `T` to determine the exact encoding -the program expects. - -## MissingRequiredSignature - -Some instructions require the account to be a signer; this error is returned if -an account is expected to be signed but is not. - -An implementation of a program might also cause this error when performing a -cross-program invocation that requires a signed program address, but the passed -signer seeds passed to [`invoke_signed`](/docs/core/cpi.md) don't match the -signer seeds used to create the program address -[`create_program_address`](/docs/core/pda.md#createprogramaddress). - -## `rand` Rust dependency causes compilation failure - -See -[Rust Project Dependencies](/docs/programs/lang-rust.md#project-dependencies) - -## Rust restrictions - -See [Rust restrictions](/docs/programs/lang-rust.md#restrictions) - -## Stack - -SBF uses stack frames instead of a variable stack pointer. Each stack frame is -4KB in size. - -If a program violates that stack frame size, the compiler will report the -overrun as a warning. - -For example: - -```text -Error: Function _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E Stack offset of -30728 exceeded max offset of -4096 by 26632 bytes, please minimize large stack variables -``` - -The message identifies which symbol is exceeding its stack frame, but the name -might be mangled if it is a Rust or C++ symbol. - -> To demangle a Rust symbol use [rustfilt](https://github.com/luser/rustfilt). - -The above warning came from a Rust program, so the demangled symbol name is: - -```shell -rustfilt _ZN16curve25519_dalek7edwards21EdwardsBasepointTable6create17h178b3d2411f7f082E -curve25519_dalek::edwards::EdwardsBasepointTable::create -``` - -To demangle a C++ symbol use `c++filt` from binutils. - -The reason a warning is reported rather than an error is because some dependent -crates may include functionality that violates the stack frame restrictions even -if the program doesn't use that functionality. If the program violates the stack -size at runtime, an `AccessViolation` error will be reported. - -SBF stack frames occupy a virtual address range starting at `0x200000000`. - -## Heap size - -Programs have access to a runtime heap either directly in C or via the Rust -`alloc` APIs. To facilitate fast allocations, a simple 32KB bump heap is -utilized. The heap does not support `free` or `realloc` so use it wisely. - -Internally, programs have access to the 32KB memory region starting at virtual -address 0x300000000 and may implement a custom heap based on the program's -specific needs. - -- [Rust program heap usage](/docs/programs/lang-rust.md#heap) -- [C program heap usage](/docs/programs/lang-c.md#heap) - -## Loaders - -Programs are deployed with and executed by runtime loaders, currently there are -two supported loaders -[BPF Loader](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader.rs#L17) -and -[BPF loader deprecated](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader_deprecated.rs#L14) - -Loaders may support different application binary interfaces so developers must -write their programs for and deploy them to the same loader. If a program -written for one loader is deployed to a different one the result is usually a -`AccessViolation` error due to mismatched deserialization of the program's input -parameters. - -For all practical purposes program should always be written to target the latest -BPF loader and the latest loader is the default for the command-line interface -and the javascript APIs. - -For language specific information about implementing a program for a particular -loader see: - -- [Rust program entrypoints](/docs/programs/lang-rust.md#program-entrypoint) -- [C program entrypoints](/docs/programs/lang-c.md#program-entrypoint) - -### Deployment - -SBF program deployment is the process of uploading a BPF shared object into a -program account's data and marking the account executable. A client breaks the -SBF shared object into smaller pieces and sends them as the instruction data of -[`Write`](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/sdk/program/src/loader_instruction.rs#L13) -instructions to the loader where loader writes that data into the program's -account data. Once all the pieces are received the client sends a -[`Finalize`](https://github.com/solana-labs/solana/blob/bc7133d7526a041d1aaee807b80922baa89b6f90/sdk/program/src/loader_instruction.rs#L30) -instruction to the loader, the loader then validates that the SBF data is valid -and marks the program account as _executable_. Once the program account is -marked executable, subsequent transactions may issue instructions for that -program to process. - -When an instruction is directed at an executable SBF program the loader -configures the program's execution environment, serializes the program's input -parameters, calls the program's entrypoint, and reports any errors encountered. - -For further information, see [deploying programs](/docs/programs/deploying.md). - -### Input Parameter Serialization - -SBF loaders serialize the program input parameters into a byte array that is -then passed to the program's entrypoint, where the program is responsible for -deserializing it on-chain. One of the changes between the deprecated loader and -the current loader is that the input parameters are serialized in a way that -results in various parameters falling on aligned offsets within the aligned byte -array. This allows deserialization implementations to directly reference the -byte array and provide aligned pointers to the program. - -For language specific information about serialization see: - -- [Rust program parameter deserialization](/docs/programs/lang-rust.md#parameter-deserialization) -- [C program parameter deserialization](/docs/programs/lang-c.md#parameter-deserialization) - -The latest loader serializes the program input parameters as follows (all -encoding is little endian): - -- 8 bytes unsigned number of accounts -- For each account - - 1 byte indicating if this is a duplicate account, if not a duplicate then - the value is 0xff, otherwise the value is the index of the account it is a - duplicate of. - - If duplicate: 7 bytes of padding - - If not duplicate: - - 1 byte boolean, true if account is a signer - - 1 byte boolean, true if account is writable - - 1 byte boolean, true if account is executable - - 4 bytes of padding - - 32 bytes of the account public key - - 32 bytes of the account's owner public key - - 8 bytes unsigned number of lamports owned by the account - - 8 bytes unsigned number of bytes of account data - - x bytes of account data - - 10k bytes of padding, used for realloc - - enough padding to align the offset to 8 bytes. - - 8 bytes rent epoch -- 8 bytes of unsigned number of instruction data -- x bytes of instruction data -- 32 bytes of the program id diff --git a/docs/programs/index.md b/docs/programs/index.md index 6a09d28ff..4189752bd 100644 --- a/docs/programs/index.md +++ b/docs/programs/index.md @@ -1,5 +1,5 @@ --- title: Developing Programs +sidebarSortOrder: 2 metaOnly: true -sidebarSortOrder: 4 --- diff --git a/docs/programs/lang-c.md b/docs/programs/lang-c.md deleted file mode 100644 index 213cdce4c..000000000 --- a/docs/programs/lang-c.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -title: "Developing with C" ---- - -Solana supports writing onchain programs using the C and C++ programming -languages. - -## Project Layout - -C projects are laid out as follows: - -```text -/src/ -/makefile -``` - -The `makefile` should contain the following: - -```shell -OUT_DIR := -include ~/.local/share/solana/install/active_release/bin/sdk/sbf/c/sbf.mk -``` - -The sbf-sdk may not be in the exact place specified above but if you setup your -environment per [How to Build](#how-to-build) then it should be. - -## How to Build - -First setup the environment: - -- Install the latest Rust stable from https://rustup.rs -- Install the latest [Solana command-line tools](/docs/intro/installation.md) - -Then build using make: - -```shell -make -C -``` - -## How to Test - -Solana uses the [Criterion](https://github.com/Snaipe/Criterion) test framework -and tests are executed each time the program is built -[How to Build](#how-to-build). - -To add tests, create a new file next to your source file named -`test_.c` and populate it with criterion test cases. See the -[Criterion docs](https://criterion.readthedocs.io/en/master) for information on -how to write a test case. - -## Program Entrypoint - -Programs export a known entrypoint symbol which the Solana runtime looks up and -calls when invoking a program. Solana supports multiple versions of the SBF -loader and the entrypoints may vary between them. Programs must be written for -and deployed to the same loader. For more details see the -[FAQ section on Loaders](/docs/programs/faq.md#loaders). - -Currently there are two supported loaders -[SBF Loader](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader.rs#L17) -and -[SBF loader deprecated](https://github.com/solana-labs/solana/blob/7ddf10e602d2ed87a9e3737aa8c32f1db9f909d8/sdk/program/src/bpf_loader_deprecated.rs#L14). - -They both have the same raw entrypoint definition, the following is the raw -symbol that the runtime looks up and calls: - -```c -extern uint64_t entrypoint(const uint8_t *input) -``` - -This entrypoint takes a generic byte array which contains the serialized program -parameters (program id, accounts, instruction data, etc...). To deserialize the -parameters each loader contains its own [helper function](#serialization). - -### Serialization - -Each loader provides a helper function that deserializes the program's input -parameters into C types: - -- [SBF Loader deserialization](https://github.com/solana-labs/solana/blob/d2ee9db2143859fa5dc26b15ee6da9c25cc0429c/sdk/bpf/c/inc/solana_sdk.h#L304) -- [SBF Loader deprecated deserialization](https://github.com/solana-labs/solana/blob/8415c22b593f164020adc7afe782e8041d756ddf/sdk/bpf/c/inc/deserialize_deprecated.h#L25) - -Some programs may want to perform deserialization themselves, and they can by -providing their own implementation of the [raw entrypoint](#program-entrypoint). -Take note that the provided deserialization functions retain references back to -the serialized byte array for variables that the program is allowed to modify -(lamports, account data). The reason for this is that upon return the loader -will read those modifications so they may be committed. If a program implements -their own deserialization function they need to ensure that any modifications -the program wishes to commit must be written back into the input byte array. - -Details on how the loader serializes the program inputs can be found in the -[Input Parameter Serialization](https://solana.com/docs/programs/faq#input-parameter-serialization) -docs. - -## Data Types - -The loader's deserialization helper function populates the -[SolParameters](https://github.com/solana-labs/solana/blob/8415c22b593f164020adc7afe782e8041d756ddf/sdk/sbf/c/inc/solana_sdk.h#L276) -structure: - -```c -/** - * Structure that the program's entrypoint input data is deserialized into. - */ -typedef struct { - SolAccountInfo* ka; /** Pointer to an array of SolAccountInfo, must already - point to an array of SolAccountInfos */ - uint64_t ka_num; /** Number of SolAccountInfo entries in `ka` */ - const uint8_t *data; /** pointer to the instruction data */ - uint64_t data_len; /** Length in bytes of the instruction data */ - const SolPubkey *program_id; /** program_id of the currently executing program */ -} SolParameters; -``` - -'ka' is an ordered array of the accounts referenced by the instruction and -represented as a -[SolAccountInfo](https://github.com/solana-labs/solana/blob/8415c22b593f164020adc7afe782e8041d756ddf/sdk/sbf/c/inc/solana_sdk.h#L173) -structures. An account's place in the array signifies its meaning, for example, -when transferring lamports an instruction may define the first account as the -source and the second as the destination. - -The members of the `SolAccountInfo` structure are read-only except for -`lamports` and `data`. Both may be modified by the program in accordance with -the "runtime enforcement policy". When an instruction reference the same account -multiple times there may be duplicate `SolAccountInfo` entries in the array but -they both point back to the original input byte array. A program should handle -these cases delicately to avoid overlapping read/writes to the same buffer. If a -program implements their own deserialization function care should be taken to -handle duplicate accounts appropriately. - -`data` is the general purpose byte array from the -[instruction's instruction data](/docs/core/transactions.md#instruction) being -processed. - -`program_id` is the public key of the currently executing program. - -## Heap - -C programs can allocate memory via the system call -[`calloc`](https://github.com/solana-labs/solana/blob/c3d2d2134c93001566e1e56f691582f379b5ae55/sdk/sbf/c/inc/solana_sdk.h#L245) -or implement their own heap on top of the 32KB heap region starting at virtual -address x300000000. The heap region is also used by `calloc` so if a program -implements their own heap it should not also call `calloc`. - -## Logging - -The runtime provides two system calls that take data and log it to the program -logs. - -- [`sol_log(const char*)`](https://github.com/solana-labs/solana/blob/d2ee9db2143859fa5dc26b15ee6da9c25cc0429c/sdk/sbf/c/inc/solana_sdk.h#L128) -- [`sol_log_64(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t)`](https://github.com/solana-labs/solana/blob/d2ee9db2143859fa5dc26b15ee6da9c25cc0429c/sdk/sbf/c/inc/solana_sdk.h#L134) - -The [debugging](/docs/programs/debugging.md#logging) section has more -information about working with program logs. - -## Compute Budget - -Use the system call `sol_remaining_compute_units()` to return a `u64` indicating -the number of compute units remaining for this transaction. - -Use the system call -[`sol_log_compute_units()`](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/sbf/c/inc/solana_sdk.h#L140) -to log a message containing the remaining number of compute units the program -may consume before execution is halted - -See the [Compute Budget](/docs/core/fees.md#compute-budget) documentation for -more information. - -## ELF Dump - -The SBF shared object internals can be dumped to a text file to gain more -insight into a program's composition and what it may be doing at runtime. The -dump will contain both the ELF information as well as a list of all the symbols -and the instructions that implement them. Some of the SBF loader's error log -messages will reference specific instruction numbers where the error occurred. -These references can be looked up in the ELF dump to identify the offending -instruction and its context. - -To create a dump file: - -```shell -cd -make dump_ -``` - -## Examples - -The -[Solana Program Library github](https://github.com/solana-labs/solana-program-library/tree/master/examples/c) -repo contains a collection of C examples diff --git a/docs/programs/lang-rust.md b/docs/programs/lang-rust.md deleted file mode 100644 index 9a6a8cefa..000000000 --- a/docs/programs/lang-rust.md +++ /dev/null @@ -1,399 +0,0 @@ ---- -title: "Developing with Rust" ---- - -Solana supports writing onchain programs using the -[Rust](https://www.rust-lang.org/) programming language. - - - -To quickly get started with Solana development and build your first Rust -program, take a look at these detailed quick start guides: - -- [Build and deploy your first Solana program using only your browser](/content/guides/getstarted/hello-world-in-your-browser.md). - No installation needed. -- [Setup your local environment](/docs/intro/installation) and use the local - test validator. - - - -## Project Layout - -Solana Rust programs follow the typical -[Rust project layout](https://doc.rust-lang.org/cargo/guide/project-layout.html): - -```text -/inc/ -/src/ -/Cargo.toml -``` - -Solana Rust programs may depend directly on each other in order to gain access -to instruction helpers when making -[cross-program invocations](/docs/core/cpi.md). When doing so it's important to -not pull in the dependent program's entrypoint symbols because they may conflict -with the program's own. To avoid this, programs should define an `no-entrypoint` -feature in `Cargo.toml` and use to exclude the entrypoint. - -- [Define the feature](https://github.com/solana-labs/solana-program-library/blob/fca9836a2c8e18fc7e3595287484e9acd60a8f64/token/program/Cargo.toml#L12) -- [Exclude the entrypoint](https://github.com/solana-labs/solana-program-library/blob/fca9836a2c8e18fc7e3595287484e9acd60a8f64/token/program/src/lib.rs#L12) - -Then when other programs include this program as a dependency, they should do so -using the `no-entrypoint` feature. - -- [Include without entrypoint](https://github.com/solana-labs/solana-program-library/blob/fca9836a2c8e18fc7e3595287484e9acd60a8f64/token-swap/program/Cargo.toml#L22) - -## Project Dependencies - -At a minimum, Solana Rust programs must pull in the -[`solana-program`](https://crates.io/crates/solana-program) crate. - -Solana SBF programs have some [restrictions](#restrictions) that may prevent the -inclusion of some crates as dependencies or require special handling. - -For example: - -- Crates that require the architecture be a subset of the ones supported by the - official toolchain. There is no workaround for this unless that crate is - forked and SBF added to that those architecture checks. -- Crates may depend on `rand` which is not supported in Solana's deterministic - program environment. To include a `rand` dependent crate refer to - [Depending on Rand](#depending-on-rand). -- Crates may overflow the stack even if the stack overflowing code isn't - included in the program itself. For more information refer to - [Stack](/docs/programs/faq.md#stack). - -## How to Build - -First setup the environment: - -- Install the latest Rust stable from https://rustup.rs/ -- Install the latest [Solana command-line tools](/docs/intro/installation.md) - -The normal cargo build is available for building programs against your host -machine which can be used for unit testing: - -```shell -cargo build -``` - -To build a specific program, such as SPL Token, for the Solana SBF target which -can be deployed to the cluster: - -```shell -cd -cargo build-bpf -``` - -## How to Test - -Solana programs can be unit tested via the traditional `cargo test` mechanism by -exercising program functions directly. - -To help facilitate testing in an environment that more closely matches a live -cluster, developers can use the -[`program-test`](https://crates.io/crates/solana-program-test) crate. The -`program-test` crate starts up a local instance of the runtime and allows tests -to send multiple transactions while keeping state for the duration of the test. - -For more information the -[test in sysvar example](https://github.com/solana-labs/solana-program-library/blob/master/examples/rust/sysvar/tests/functional.rs) -shows how an instruction containing sysvar account is sent and processed by the -program. - -## Program Entrypoint - -Programs export a known entrypoint symbol which the Solana runtime looks up and -calls when invoking a program. Solana supports multiple versions of the BPF -loader and the entrypoints may vary between them. Programs must be written for -and deployed to the same loader. For more details see the -[FAQ section on Loaders](/docs/programs/faq.md#loaders). - -Currently there are two supported loaders -[BPF Loader](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/bpf_loader.rs#L17) -and -[BPF loader deprecated](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/bpf_loader_deprecated.rs#L14) - -They both have the same raw entrypoint definition, the following is the raw -symbol that the runtime looks up and calls: - -```rust -#[no_mangle] -pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64; -``` - -This entrypoint takes a generic byte array which contains the serialized program -parameters (program id, accounts, instruction data, etc...). To deserialize the -parameters each loader contains its own wrapper macro that exports the raw -entrypoint, deserializes the parameters, calls a user defined instruction -processing function, and returns the results. - -You can find the entrypoint macros here: - -- [BPF Loader's entrypoint macro](https://github.com/solana-labs/solana/blob/9b1199cdb1b391b00d510ed7fc4866bdf6ee4eb3/sdk/program/src/entrypoint.rs#L42) -- [BPF Loader deprecated's entrypoint macro](https://github.com/solana-labs/solana/blob/9b1199cdb1b391b00d510ed7fc4866bdf6ee4eb3/sdk/program/src/entrypoint_deprecated.rs#L38) - -The program defined instruction processing function that the entrypoint macros -call must be of this form: - -```rust -pub type ProcessInstruction = - fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult; -``` - -### Parameter Deserialization - -Each loader provides a helper function that deserializes the program's input -parameters into Rust types. The entrypoint macros automatically calls the -deserialization helper: - -- [BPF Loader deserialization](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/entrypoint.rs#L146) -- [BPF Loader deprecated deserialization](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/entrypoint_deprecated.rs#L57) - -Some programs may want to perform deserialization themselves and they can by -providing their own implementation of the [raw entrypoint](#program-entrypoint). -Take note that the provided deserialization functions retain references back to -the serialized byte array for variables that the program is allowed to modify -(lamports, account data). The reason for this is that upon return the loader -will read those modifications so they may be committed. If a program implements -their own deserialization function they need to ensure that any modifications -the program wishes to commit be written back into the input byte array. - -Details on how the loader serializes the program inputs can be found in the -[Input Parameter Serialization](/docs/programs/faq.md#input-parameter-serialization) -docs. - -### Data Types - -The loader's entrypoint macros call the program defined instruction processor -function with the following parameters: - -```rust -program_id: &Pubkey, -accounts: &[AccountInfo], -instruction_data: &[u8] -``` - -The program id is the public key of the currently executing program. - -The accounts is an ordered slice of the accounts referenced by the instruction -and represented as an -[AccountInfo](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/account_info.rs#L12) -structures. An account's place in the array signifies its meaning, for example, -when transferring lamports an instruction may define the first account as the -source and the second as the destination. - -The members of the `AccountInfo` structure are read-only except for `lamports` -and `data`. Both may be modified by the program in accordance with the "runtime -enforcement policy". Both of these members are protected by the Rust `RefCell` -construct, so they must be borrowed to read or write to them. The reason for -this is they both point back to the original input byte array, but there may be -multiple entries in the accounts slice that point to the same account. Using -`RefCell` ensures that the program does not accidentally perform overlapping -read/writes to the same underlying data via multiple `AccountInfo` structures. -If a program implements their own deserialization function care should be taken -to handle duplicate accounts appropriately. - -The instruction data is the general purpose byte array from the -[instruction's instruction data](/docs/core/transactions.md#instruction) being -processed. - -## Heap - -Rust programs implement the heap directly by defining a custom -[`global_allocator`](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/entrypoint.rs#L72) - -Programs may implement their own `global_allocator` based on its specific needs. -Refer to the [custom heap example](#examples) for more information. - -## Restrictions - -On-chain Rust programs support most of Rust's libstd, libcore, and liballoc, as -well as many 3rd party crates. - -There are some limitations since these programs run in a resource-constrained, -single-threaded environment, as well as being deterministic: - -- No access to - - `rand` - - `std::fs` - - `std::net` - - `std::future` - - `std::process` - - `std::sync` - - `std::task` - - `std::thread` - - `std::time` -- Limited access to: - - `std::hash` - - `std::os` -- Bincode is extremely computationally expensive in both cycles and call depth - and should be avoided -- String formatting should be avoided since it is also computationally - expensive. -- No support for `println!`, `print!`, the Solana [logging helpers](#logging) - should be used instead. -- The runtime enforces a limit on the number of instructions a program can - execute during the processing of one instruction. See - [computation budget](/docs/core/fees.md#compute-budget) for more information. - -## Depending on Rand - -Programs are constrained to run deterministically, so random numbers are not -available. Sometimes a program may depend on a crate that depends itself on -`rand` even if the program does not use any of the random number functionality. -If a program depends on `rand`, the compilation will fail because there is no -`get-random` support for Solana. The error will typically look like this: - -```shell -error: target is not supported, for more information see: https://docs.rs/getrandom/#unsupported-targets - --> /Users/jack/.cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.1.14/src/lib.rs:257:9 - | -257 | / compile_error!("\ -258 | | target is not supported, for more information see: \ -259 | | https://docs.rs/getrandom/#unsupported-targets\ -260 | | "); - | |___________^ -``` - -To work around this dependency issue, add the following dependency to the -program's `Cargo.toml`: - -```rust -getrandom = { version = "0.1.14", features = ["dummy"] } -``` - -or if the dependency is on getrandom v0.2 add: - -```rust -getrandom = { version = "0.2.2", features = ["custom"] } -``` - -## Logging - -Rust's `println!` macro is computationally expensive and not supported. Instead -the helper macro -[`msg!`](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/log.rs#L33) -is provided. - -`msg!` has two forms: - -```rust -msg!("A string"); -``` - -or - -```rust -msg!(0_64, 1_64, 2_64, 3_64, 4_64); -``` - -Both forms output the results to the program logs. If a program so wishes they -can emulate `println!` by using `format!`: - -```rust -msg!("Some variable: {:?}", variable); -``` - -The [debugging](/docs/programs/debugging.md#logging) section has more -information about working with program logs the [Rust examples](#examples) -contains a logging example. - -## Panicking - -Rust's `panic!`, `assert!`, and internal panic results are printed to the -[program logs](/docs/programs/debugging.md#logging) by default. - -```shell -INFO solana_runtime::message_processor] Finalized account CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ -INFO solana_runtime::message_processor] Call SBF program CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ -INFO solana_runtime::message_processor] Program log: Panicked at: 'assertion failed: `(left == right)` - left: `1`, - right: `2`', rust/panic/src/lib.rs:22:5 -INFO solana_runtime::message_processor] SBF program consumed 5453 of 200000 units -INFO solana_runtime::message_processor] SBF program CGLhHSuWsp1gT4B7MY2KACqp9RUwQRhcUFfVSuxpSajZ failed: BPF program panicked -``` - -### Custom Panic Handler - -Programs can override the default panic handler by providing their own -implementation. - -First define the `custom-panic` feature in the program's `Cargo.toml` - -```rust -[features] -default = ["custom-panic"] -custom-panic = [] -``` - -Then provide a custom implementation of the panic handler: - -```rust -#[cfg(all(feature = "custom-panic", target_os = "solana"))] -#[no_mangle] -fn custom_panic(info: &core::panic::PanicInfo<'_>) { - solana_program::msg!("program custom panic enabled"); - solana_program::msg!("{}", info); -} -``` - -In the above snippit, the default implementation is shown, but developers may -replace that with something that better suits their needs. - -One of the side effects of supporting full panic messages by default is that -programs incur the cost of pulling in more of Rust's `libstd` implementation -into program's shared object. Typical programs will already be pulling in a fair -amount of `libstd` and may not notice much of an increase in the shared object -size. But programs that explicitly attempt to be very small by avoiding `libstd` -may take a significant impact (~25kb). To eliminate that impact, programs can -provide their own custom panic handler with an empty implementation. - -```rust -#[cfg(all(feature = "custom-panic", target_os = "solana"))] -#[no_mangle] -fn custom_panic(info: &core::panic::PanicInfo<'_>) { - // Do nothing to save space -} -``` - -## Compute Budget - -Use the system call `sol_remaining_compute_units()` to return a `u64` indicating -the number of compute units remaining for this transaction. - -Use the system call -[`sol_log_compute_units()`](https://github.com/solana-labs/solana/blob/d9b0fc0e3eec67dfe4a97d9298b15969b2804fab/sdk/program/src/log.rs#L141) -to log a message containing the remaining number of compute units the program -may consume before execution is halted - -See the [Compute Budget](/docs/core/fees.md#compute-budget) documentation for -more information. - -## ELF Dump - -The SBF shared object internals can be dumped to a text file to gain more -insight into a program's composition and what it may be doing at runtime. The -dump will contain both the ELF information as well as a list of all the symbols -and the instructions that implement them. Some of the BPF loader's error log -messages will reference specific instruction numbers where the error occurred. -These references can be looked up in the ELF dump to identify the offending -instruction and its context. - -To create a dump file: - -```shell -cd -cargo build-bpf --dump -``` - -## Examples - -The -[Solana Program Library GitHub](https://github.com/solana-labs/solana-program-library/tree/master/examples/rust) -repo contains a collection of Rust examples. - -The -[Solana Developers Program Examples GitHub](https://github.com/solana-developers/program-examples) -repo also contains a collection of beginner to intermediate Rust program -examples. diff --git a/docs/programs/limitations.md b/docs/programs/limitations.md deleted file mode 100644 index 1a4469669..000000000 --- a/docs/programs/limitations.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: "Limitations" ---- - -Developing programs on the Solana blockchain have some inherent limitation -associated with them. Below is a list of common limitation that you may run -into. - -## Rust libraries - -Since Rust based onchain programs must run be deterministic while running in a -resource-constrained, single-threaded environment, they have some limitations on -various libraries. - -See -[Developing with Rust - Restrictions](/docs/programs/lang-rust.md#restrictions) -for a detailed breakdown these restrictions and limitations. - -## Compute budget - -To prevent abuse of the blockchain's computational resources, each transaction -is allocated a [compute budget](/docs/terminology.md#compute-budget). Exceeding -this compute budget will result in the transaction failing. - -See the [computational constraints](/docs/core/fees.md#compute-budget) -documentation for more specific details. - -## Call stack depth - `CallDepthExceeded` error - -Solana programs are constrained to run quickly, and to facilitate this, the -program's call stack is limited to a max depth of **64 frames**. - -When a program exceeds the allowed call stack depth limit, it will receive the -`CallDepthExceeded` error. - -## CPI call depth - `CallDepth` error - -Cross-program invocations allow programs to invoke other programs directly, but -the depth is constrained currently to `4`. - -When a program exceeds the allowed -[cross-program invocation call depth](/docs/core/cpi.md), it will receive a -`CallDepth` error - -## Float Rust types support - -Programs support a limited subset of Rust's float operations. If a program -attempts to use a float operation that is not supported, the runtime will report -an unresolved symbol error. - -Float operations are performed via software libraries, specifically LLVM's float -built-ins. Due to the software emulated, they consume more compute units than -integer operations. In general, fixed point operations are recommended where -possible. - -The -[Solana Program Library math](https://github.com/solana-labs/solana-program-library/tree/master/libraries/math) -tests will report the performance of some math operations. To run the test, sync -the repo and run: - -```shell -cargo test-sbf -- --nocapture --test-threads=1 -``` - -Recent results show the float operations take more instructions compared to -integers equivalents. Fixed point implementations may vary but will also be less -than the float equivalents: - -```text - u64 f32 -Multiply 8 176 -Divide 9 219 -``` - -## Static writable data - -Program shared objects do not support writable shared data. Programs are shared -between multiple parallel executions using the same shared read-only code and -data. This means that developers should not include any static writable or -global variables in programs. In the future a copy-on-write mechanism could be -added to support writable data. - -## Signed division - -The SBF instruction set does not support -[signed division](https://www.kernel.org/doc/html/latest/bpf/bpf_design_QA.Html#q-why-there-is-no-bpf-sdiv-for-signed-divide-operation). -Adding a signed division instruction is a consideration. diff --git a/docs/programs/overview.md b/docs/programs/overview.md deleted file mode 100644 index 3e937a2c1..000000000 --- a/docs/programs/overview.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Overview of Developing On-chain Programs -sidebarLabel: Overview -sidebarSortOrder: 0 -altRoutes: - - /docs/programs ---- - -Developers can write and deploy their own programs to the Solana blockchain. -This process can be broadly summarized into a few key steps. - - - -To quickly get started with Solana development and build your first Rust -program, take a look at these detailed quick start guides: - -- [Build and deploy your first Solana program using only your browser](/content/guides/getstarted/hello-world-in-your-browser.md). - No installation needed. -- [Setup your local environment](/docs/intro/installation) and use the local - test validator. - - - -## On-chain program development lifecycle - -1. Setup your development environment -2. Write your program -3. Compile the program -4. Generate the program's public address -5. Deploy the program - -### 1. Setup your development environment - -The most robust way of getting started with Solana development, is -[installing the Solana CLI](/docs/intro/installation.md) tools on your local -computer. This will allow you to have the most powerful development environment. - -Some developers may also opt for using -[Solana Playground](https://beta.solpg.io/), a browser based IDE. It will let -you write, build, and deploy onchain programs. All from your browser. No -installation needed. - -### 2. Write your program - -Writing Solana programs is most commonly done so using the Rust language. These -Rust programs are effectively the same as creating a traditional -[Rust library](https://doc.rust-lang.org/rust-by-example/crates/lib.html). - -> You can read more about other [supported languages](#support-languages) below. - -### 3. Compile the program - -Once the program is written, it must be complied down to -[Berkley Packet Filter](/docs/programs/faq.md#berkeley-packet-filter-bpf) -byte-code that will then be deployed to the blockchain. - -### 4. Generate the program's public address - -Using the [Solana CLI](/docs/intro/installation.md), the developer will generate -a new unique [Keypair](/docs/terminology.md#keypair) for the new program. The -public address (aka [Pubkey](/docs/terminology.md#public-key-pubkey)) from this -Keypair will be used on-chain as the program's public address (aka -[`programId`](/docs/terminology.md#program-id)). - -### 5. Deploying the program - -Then again using the CLI, the compiled program can be deployed to the selected -blockchain cluster by creating many transactions containing the program's -byte-code. Due to the transaction memory size limitations, each transaction -effectively sends small chunks of the program to the blockchain in a rapid-fire -manner. - -Once the entire program has been sent to the blockchain, a final transaction is -sent to write all of the buffered byte-code to the program's data account. This -either mark the new program as `executable`, or complete the process to upgrade -an existing program (if it already existed). - -## Support languages - -Solana programs are typically written in the -[Rust language](/docs/programs/lang-rust.md), but -[C/C++](/docs/programs/lang-c.md) are also supported. - -There are also various community driven efforts to enable writing on-chain -programs using other languages, including: - -- Python via [Seahorse](https://seahorse.dev/) (that acts as a wrapper the Rust - based Anchor framework) - -## Example programs - -You can also explore the [Program Examples](/docs/programs/examples.md) for -examples of onchain programs. - -## Limitations - -As you dive deeper into program development, it is important to understand some -of the important limitations associated with onchain programs. - -Read more details on the [Limitations](/docs/programs/limitations.md) page - -## Frequently asked questions - -Discover many of the [frequently asked questions](/docs/programs/faq.md) other -developers have about writing/understanding Solana programs. diff --git a/docs/programs/testing.md b/docs/programs/testing.md index 32d3bcdc3..2af73ebe5 100644 --- a/docs/programs/testing.md +++ b/docs/programs/testing.md @@ -1,6 +1,7 @@ --- title: "Testing with NodeJS" description: "Testing native solana programs written with rust using NodeJS" +sidebarSortOrder: 5 --- When developing programs on Solana, ensuring their correctness and reliability @@ -232,16 +233,3 @@ This is how the output looks like after running the tests for ℹ todo 0 ℹ duration_ms 63.52616 ``` - -## Next Steps - -- Checkout more testing examples from the - [Program Examples](/docs/programs/examples.md) -- You can also - use [anchor-bankrun](https://kevinheavey.github.io/solana-bankrun/tutorial/#anchor-integration) to - write tests in NodeJS for Anchor programs -- [Writing and testing your Solana programs using Rust](https://solana.com/docs/programs/lang-rust#how-to-test) - is possible with - [solana_program_test](https://docs.rs/solana-program-test/1.18.14/solana_program_test/) -- You can also write test with python for Solana programs written in Rust with - [solders.bankrun](https://kevinheavey.github.io/solders/api_reference/bankrun.html) diff --git a/docs/terminology.md b/docs/terminology.md index 2ce27df49..5f94c07bd 100644 --- a/docs/terminology.md +++ b/docs/terminology.md @@ -82,7 +82,7 @@ a block chain. ## BPF loader The Solana program that owns and loads -[BPF](/docs/programs/faq.md#berkeley-packet-filter-bpf) +[BPF](/docs/core/programs#berkeley-packet-filter-bpf) [onchain programs](#onchain-program), allowing the program to interface with the runtime. diff --git a/package.json b/package.json index f09b9b1f3..0e8656f1a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "crowdin:download": "crowdin download && npm run prettier:i18n", "crowdin:upload": "crowdin upload sources", "browser-sync": "browser-sync start --proxy \"localhost:3000\" --files \"**/*.md\"", - "dev:sync": "yarn dev & (sleep 3 && yarn browser-sync)" + "dev:sync": "yarn dev & (sleep 5 && yarn browser-sync)" }, "dependencies": { "@crowdin/cli": "^3.18.0", From 8706897262678925710c4cd230fbeb3bd25b3516 Mon Sep 17 00:00:00 2001 From: Akojede Olorundara Date: Fri, 4 Oct 2024 05:03:58 +0100 Subject: [PATCH 29/54] Updated Intro to solana mobile (#478) * chore: updated lesson summary * improved lesson overview * improved intro to solana mobile * updated use cases * grammar restructure * updating in compliance to contribution guideline * updating in compliance to contribution guide * more descriptive variable names, handled errors, avoided repitition * fixed title * updated conclusion and included useful links * improved setup section, removed unnecesary pragraphs * minor update to ConnectionProvider function * improved some functions in .tsx files, added comments where necessary * improved basic-solana-mobile-connect.png * improved basic-solana-mobile-flow.png * improved basic-solana-mobile-transact.png * fixed content styling * replaced cryprocurrency with blockchain transactions * cryptocurrency to blockchain transactions * cleanup * improved intro to beginner-friendly standard * improved clarity * more restructuring, building a more readable flow, especially for rust beginners * minor fixes, resolved conflict * prettier:fix * prettier:fix * prettier:fix * Update content/courses/mobile/intro-to-solana-mobile.md removed redundant comment Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md grammatical fix Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md changed "units" to "lessons" for uniformity Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md improved structure for easier readability Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md removed quoted and improved tone Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md changed grammar structure to improve clarity Co-authored-by: Mike MacCana * requested changes: removed redundant sections, typo fixes, improved clarity, used further contribution guidelines * removed backticks in titles, resolved merge conflicts * fixed broken links * revert changes in rust-acro.md * prettier fix * revert image changes * changed Solana community forum link from discord to stack exchange * added official link to install node * fix formatting * Delete content/courses/program-optimization/developer-content.code-workspace * fix links --------- Co-authored-by: Akojede Olorundara Co-authored-by: Mike MacCana --- .../courses/mobile/intro-to-solana-mobile.md | 929 +++++++++--------- 1 file changed, 461 insertions(+), 468 deletions(-) diff --git a/content/courses/mobile/intro-to-solana-mobile.md b/content/courses/mobile/intro-to-solana-mobile.md index cf6a9002b..68a5791ca 100644 --- a/content/courses/mobile/intro-to-solana-mobile.md +++ b/content/courses/mobile/intro-to-solana-mobile.md @@ -1,50 +1,65 @@ --- title: Introduction to Solana Mobile objectives: - - Explain the benefits of creating mobile-first dApp experiences + - Explain the benefits of creating mobile-first App experiences - Explain the high-level Mobile Wallet Adapter (MWA) flow - Explain the high-level differences between React and React Native - - Create a simple Android Solana dApp using React Native + - Create a simple Android Solana App using React Native description: "Learn how to build native mobile apps using blockchain functionality" --- ## Summary -- The Solana Mobile Wallet Adapter (MWA) creates a web socket connection between - mobile apps and mobile wallets, allowing native mobile apps to submit - transactions for signing -- The simplest way to get started creating Solana mobile applications is with - Solana Mobile's - [React Native packages](https://docs.solanamobile.com/react-native/setup) +- The **Solana Mobile Wallet Adapter** (**MWA**) allows mobile apps to submit + transactions for signing via a WebSocket connection to mobile wallets. +- The easiest way to start building Solana mobile applications is by using + Solana Mobile’s + [React Native packages](https://docs.solanamobile.com/react-native/setup) - `@solana-mobile/mobile-wallet-adapter-protocol` and `@solana-mobile/mobile-wallet-adapter-protocol-web3js` -- React Native is very similar to React with a few mobile quirks -## Lesson +## Lesson Overview -Solana Mobile Stack (SMS) is designed to help developers create mobile dApps -with a seamless UX. It consists of the -[Mobile Wallet Adapter (MWA)](https://docs.solanamobile.com/getting-started/overview#mobile-wallet-adapter), +In these lessons, we will develop mobile apps that interact with the Solana +network, this opens up a whole new paradigm of blockchain use cases and +behaviors. The **Solana Mobile Stack** (**SMS**) is designed to help developers +seamlessly create mobile apps. It includes the +[Mobile Wallet Adapter (MWA)](https://docs.solanamobile.com/getting-started/overview#mobile-wallet-adapter) +, a Solana Mobile SDK that uses React Native, [Seed Vault](https://docs.solanamobile.com/getting-started/overview#seed-vault), and the -[Solana dApp Store](https://docs.solanamobile.com/getting-started/overview#solana-dapp-store). - -Most relevant to your development journey is the Mobile Wallet Adapter (MWA). -The simplest way to get started is to use the Mobile Wallet Adapter with React -Native to create a simple Android app. This lesson assumes you're familiar with -React and Solana programming. If that's not the case, -[start our course from the beginning](/content/courses/intro-to-solana/intro-to-cryptography) -and come back here when you feel ready! - -### Intro To Solana Mobile - -In these units, we'll develop mobile apps that interact with the Solana network. -This opens up a whole new paradigm of crypto use cases and behaviors. +[Solana app Store](https://docs.solanamobile.com/getting-started/overview#solana-app-store). +These resources simplify mobile development with a similar experience but with +mobile-specific features. + +This lesson focuses on using React Native to create a simple Android app that +integrates with the Solana network. If you're not familiar with programming in +React or Solana, we recommend starting with our +[Intro to Solana lesson](https://github.com/solana-foundation/developer-content/tree/main/content/courses/intro-to-solana) +and returning when you're ready. If you are, let's dive in! + +## Intro to Solana Mobile + +Native mobile wallets hold your private keys and use them to sign and send +transactions just like web extension wallets. However native mobile wallets use +the +[Mobile Wallet Adapter](https://github.com/solana-mobile/mobile-wallet-adapter) +(MWA) standard instead of the +[Wallet Adapter](https://github.com/anza-xyz/wallet-adapter) to ensure any apps +can work with any wallet. + +We will dig into the specifics of the MWA in a +[later lesson](/content/courses/mobile/mwa-deep-dive), but it effectively opens +a WebSocket between applications to facilitate communication. That way a +separate app can provide the wallet app with the transaction to be signed and +sent, and the wallet app can respond with appropriate status updates. -#### Solana Mobile Use Cases +### Mobile Use Cases with Solana -Here are a few examples of what Solana mobile development can unlock: +Before development, it is important to understand the current landscape of Web3 +mobile development to foresee potential blockers and opportunities. Here are a +few examples of what Solana mobile development can unlock: **Mobile Banking and Trading (DeFi)** @@ -66,55 +81,22 @@ SMS can enable a new wave of mobile e-commerce shoppers to pay directly from their favorite Solana wallet. Imagine a world where you can use your Solana wallet as seamlessly as you can use Apple Pay. -To summarize, mobile crypto opens up many doors. Let's dive in and learn how we -can be part of it: - -#### How Solana development differs between native mobile apps and web +In summary, mobile blockchain transactions can open many opportunities. Let's +start building! -Solana wallet interaction differs slightly on mobile compared to the web. The -core wallet functionality is the same: the wallet holds your private keys and -uses them to sign and send transactions. To avoid having different interfaces -between wallets, developers abstracted that functionality into the Solana Wallet -Adapter standard. This remains the standard on the web. The mobile counterpart -is the Mobile Wallet Adapter (MWA). +### Supported Operating Systems -The differences between the two standards are due to the different construction -of web vs mobile wallets. Web wallets are just browser extensions that inject -wallet adapter functions into the `window` object of your webpage. This gives -your site access to them. Mobile wallets, however, are native applications on a -mobile operating system. There's no way to surface functions from one native -application to another. The Mobile Wallet Adapter exists to enable any app, -written in any language, to connect to a native wallet app. +Currently, the MWA only supports Android. On Android, a WebSocket connection can +persist between apps, even when the wallet app is in the background. -We'll dig into the specifics of the Mobile Wallet Adapter in a -[later lesson](/content/courses/mobile/mwa-deep-dive), but it effectively opens -a WebSocket between applications to facilitate communication. That way a -separate app can provide the wallet app with the transaction to be signed and -sent, and the wallet app can respond with appropriate status updates. - -#### Supported Operating Systems - -At the time of writing, Android is the only mobile OS supported by the Mobile -Wallet Adapter. - -On Android, a WebSocket connection can persist between apps, even when the -wallet app is in the background. - -On iOS, the lifetime of a connection between apps is purposefully limited by the -operating system. Specifically, iOS will quickly suspend connections when an app -is pushed to the background. This kills the MWA WebSocket connection. This is an -inherent design difference between iOS and Android (probably made to preserve -battery, network usage, etc). - -However, this doesn't mean that Solana dApps can't run on iOS at all. You can -still create a Mobile Web App using the -[standard wallet adapter](https://github.com/solana-labs/wallet-adapter) -library. Your users can then install a mobile-friendly wallet like -the [Glow Wallet](https://glow.app/). +On iOS, the OS quickly suspends websocket connections when an app is +backgrounded, so the standard +[Wallet Adapter](https://github.com/solana-labs/wallet-adapter) library is used +instead. The remainder of this lesson will focus on developing Android apps with the MWA. -#### Supported Frameworks +### Supported Frameworks Solana Mobile supports a number of different frameworks. Officially supported are React Native and native Android, with community SDKs for Flutter, Unity, and @@ -132,297 +114,315 @@ Unreal Engine. - [Unity](https://docs.solanamobile.com/unity/unity_sdk) - [Unreal Engine](https://docs.solanamobile.com/unreal/unreal_sdk) -To keep the development experience as close as possible to other lessons, we'll -be working exclusively with React Native. - -### From React to React Native - -React Native takes the React web framework and applies it to mobile -applications. However, while React and React Native feel very similar, there are -differences. The best way to understand these differences is to experience them -while coding. But, to give you a head start here is a list of some differences -to keep in mind: - -- React Native compiles down to native iOS and Android applications while React - compiles down to a collection of web pages. -- In React, you use JSX to program with HTML and CSS. With React Native, you use - similar syntax to manipulate native UI components. It's more like using a UI - library like Chakra or Tailwind UI. Instead of `
`, `

`, and `` - you'll be using ``, ``, and ``. -- Interactions are different. Instead of `onClick`, you'll use `onPress` and - other gestures. -- Many standard React and Node packages may not be compatible with React Native. - Fortunately, there are React Native counterparts to the most popular libraries - and you can often use polyfills to make Node packages available. If you're not - familiar with polyfills, take a look at the - [MDN docs](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill). In - short, polyfills actively replace Node-native libraries to make them work - anywhere Node is not running. -- Setting up a development environment in React Native can be challenging. This - will require setting up Android Studio to compile to Android and XCode for - iOS. React Native has a - [really good guide](https://reactnative.dev/docs/environment-setup?guide=native) - for this. -- For regular development and testing, you'll use a physical mobile device or an - emulator to run your code. This relies on a tool called Metro that comes - pre-installed. React Native's setup guide also covers this. -- React Native gives you access to the phone's hardware that React can't - provide. This includes things like the phone's camera, accelerometer, and - more. -- React Native introduces new config files and build folders. For example, the - `ios` and `android` directories contain platform-specific information. - Additionally, there are config files like `Gemfile` and `metro.config.js`. - Generally, leave all configurations alone and just worry about writing your - code, the starting point for which will be in `App.tsx`. - -There is a learning curve, but if you know React you're not nearly as far from -being able to develop mobile apps as you think! It may feel jarring to start, -but after a few hours of React Native development, you'll start to feel much -more comfortable. You'll likely feel much more confident even after -[this lesson's lab](#lab). - -### Creating a Solana dApp with React Native - -Solana React Native dApps are virtually identical to React dApps. The primary +To keep the development experience as close as possible to other lessons, we +will be working exclusively with React Native. + +## From React to React Native + +React Native is very similar to React but designed for mobile. Here are some key +points to note: + +- React Native compiles down to native Android and iOS apps while React compiles + down to a collection of web pages. +- Instead of using web elements like `

`, you will use mobile-native + elements like ``. +- React Native allows access to mobile hardware, such as the camera and + accelerometer, which React web apps cannot access. +- Many standard React and Node packages may not be compatible with React Native + and setting up React Native can be challenging. Fortunately, the + [React Native Docs](https://reactnative.dev/docs/environment-setup?guide=native) + contains everything you may need. +- For development, you will need to set up + [Android Studio](https://developer.android.com/studio/intro/) for Android apps + and an emulator or physical device for testing. + + +**NOTE:** There is a learning curve, but if you know React you're not nearly as far from being able to develop mobile apps as you think! It may feel jarring to start, but after a few hours of React Native development, you will start to feel much more comfortable. We have included a [Lab](#lab) section below to help you. + + +## Creating a React Native App on Solana + +Solana React Native apps are virtually identical to React apps. The primary difference is in the wallet interaction. Instead of the wallet being available -in the browser, your dApp will create an MWA session with the wallet app of your +in the browser, your app will create an MWA session with the wallet app of your choosing using a WebSocket. Fortunately, this is abstracted for you in the MWA -library. The only difference you'll need to know is anytime you need to make a -call to the wallet you'll be using the `transact` function, which we'll talk -about soon. +library. The only difference is that anytime you need to make a call to the +wallet, the `transact` function will be used, more details on this function in +later parts of this lesson. -![dApp Flow](/public/assets/courses/unboxed/basic-solana-mobile-flow.png) +![App Flow](/public/assets/courses/unboxed/basic-solana-mobile-flow.png) -#### Reading data +## Reading Data -Reading data from a Solana cluster in React Native is the exact same as in -React. You use the `useConnection` hook to grab the `Connection` object. Using -that, you can get account info. Since reading is free, we don't need to actually -connect to the wallet. +Reading data from a Solana cluster in React Native works the same way as in +React. You can use the `useConnection` hook to access the `connection` object, +which is responsible for interacting with the Solana network. -```tsx -const account = await connection.getAccountInfo(account); +In Solana, an account refers to any object stored on-chain, and is typically +referenced by a +[public key](https://solana.com/docs/terminology#public-key-pubkey). + +Here’s an example of how you can read an account information using the +`getAccountInfo` method: + +```javascript +const { connection } = useConnection(); +const publicKey = new PublicKey("your-wallet-public-key-here"); // Replace with a valid public key +const account = await connection.getAccountInfo(publicKey); ``` -If you need a refresher on this, check out our -[lesson on reading data from the blockchain](/content/courses/intro-to-solana/intro-to-reading-data). +> **NOTE:** If you need a refresher, refer to our +> [Intro to Reading Data lesson](/content/courses/intro-to-solana/intro-to-reading-data). -#### Connecting to a wallet +## Connecting to a Wallet -Writing data to the blockchain has to happen through a transaction. Transactions -have to be signed by one or more private keys and sent to an RPC provider. This -virtually always happens through a wallet application. +When writing data to the blockchain, it must be done through a **transaction**. +Transactions need to be signed by one or more secret keys (previously referred +to as private keys) and sent to an +[RPC provider](https://academy.subquery.network/subquery_network/node_operators/rpc_providers/introduction.html) +for processing. In almost all cases, this interaction is facilitated through a +wallet application. -Typical wallet interaction happens by calling out to a browser extension. On -mobile, you use a WebSocket to start an MWA session. Specifically, you use -Android intents where the dApp broadcasts its intent with the `solana-wallet://` -scheme. +### Web vs. Mobile Wallet Interactions +The websocket that connects the app and the wallet is managed using the MWA, and +initiated using **Android intents**, with the dApp broadcasting its intent using +the `solana-wallet://` scheme. ![Connecting](/public/assets/courses/unboxed/basic-solana-mobile-connect.png) -When the wallet app receives this intent, it opens a connection with the dApp -that initiated the session. Your dApp sends this intent using the `transact` -function: +When the wallet app receives the intent broadcast, it opens a WebSocket +connection with the app that initiated the session. The app initiates this +connection using the `transact` function, as shown below: ```tsx transact(async (wallet: Web3MobileWallet) => { - // Wallet Action code here -} + // Your wallet action code goes here +}); ``` -This will give you access to the `Web3MobileWallet` object. You can then use -this to send transactions to the wallet. Again, when you want to access the -wallet, it has to be through the function `transact` function's callback. +This function provides access to the `Web3MobileWallet` object, allowing you to +perform actions such as signing transactions or interacting with wallet data. +Remember, all wallet interactions must occur inside the callback of the +`transact` function. -#### Signing and sending transactions +### Signing and sending transactions -Sending a transaction happens inside the `transact` callback. The flow is as -follows: +The overall flow for signing and sending a transaction is as follows: -1. Establish a session with a wallet using `transact` which will have a callback - of `async (wallet: Web3MobileWallet) => {...}`. -2. Inside the callback, request authorization with the `wallet.authorize` or - `wallet.reauthorize` method depending on the state of the wallet. -3. Sign the transaction with `wallet.signTransactions` or sign and send with - `wallet.signAndSendTransactions`. +- Use the `transact` function to establish a session with the wallet. This + function takes an asynchronous callback: + `async (wallet: Web3MobileWallet) => {...}`. +- Inside the callback, request wallet authorization using `wallet.authorize()` + or `wallet.reauthorize()`, depending on the wallet's state (whether it has an + active session or requires reauthorization). +- Once the wallet is authorized, you can either: + - Sign the transaction using `wallet.signTransactions()`, or + - Sign and send the transaction directly using + `wallet.signAndSendTransactions()`. ![Transacting](/public/assets/courses/unboxed/basic-solana-mobile-transact.png) +To manage the wallet's authorization state, consider creating a +`useAuthorization()` hook. This hook can streamline the process of handling +authorization within your app, especially if you have multiple interactions with +the wallet. -You may want to create a `useAuthorization()` hook to -manage the wallet's authorization state. We'll practice this in the -[Lab](#lab). +> We will explore the use of this hook and practice managing the wallet's state +> in more detail during the lab exercises. Here is an example of sending a transaction using MWA: ```tsx +//import required dependencies if any + const { authorizeSession } = useAuthorization(); const { connection } = useConnection(); -const sendTransactions = (transaction: Transaction) => { - transact(async (wallet: Web3MobileWallet) => { - const latestBlockhashResult = await connection.getLatestBlockhash(); - const authResult = await authorizeSession(wallet); - - const updatedTransaction = new Transaction({ - ...transaction, - ...latestBlockhashResult, - feePayer: authResult.publicKey, - }); +const sendTransactions = async (transaction: Transaction) => { + try { + // Start a session with the wallet + await transact(async (wallet: Web3MobileWallet) => { + // Get the latest blockhash for the transaction + const { blockhash, lastValidBlockHeight } = + await connection.getLatestBlockhash(); + + // Authorize the wallet session + const authResult = await authorizeSession(wallet); + + // Create an updated transaction with the latest blockhash and feePayer + const updatedTransaction = new Transaction({ + recentBlockhash: blockhash, + feePayer: authResult.publicKey, + }).add(transaction); + + // Sign and send the transaction via the wallet + const signatures = await wallet.signAndSendTransactions({ + transactions: [updatedTransaction], + }); - const signature = await wallet.signAndSendTransactions({ - transactions: [transaction], + console.log(`Transaction successful! Signature: ${signatures[0]}`); }); - }); + } catch (error) { + console.error("Error sending transaction:", error); + throw new Error("Transaction failed"); + } }; ``` -#### Debugging - -Since two applications are involved in sending transactions, debugging can be -tricky. Specifically, you won't be able to see the wallet's debug logs the way -you can see your dApps logs. - -Fortunately, -[Logcat on Android Studio](https://developer.android.com/studio/debug/logcat) -makes it possible to see logs from all applications on your device. - -If you prefer not to use Logcat, the other method you could try is to only use -the wallet to sign transactions, and then send them in your code. This allows -you to better debug the transaction if you're running into problems. - -#### Releasing - -Deploying mobile applications can be difficult on its own. It's often even more -difficult when it's a crypto app. There are two main reasons for this: customer -safety and financial incentives. - -First, most of the mobile app marketplaces have policies restricting blockchain -involvement. Crypto is new enough that it's a regulatory wildcard. Platforms -feel they're protecting users by being strict with blockchain-related apps. - -Second, if you use crypto for "purchases" in-app, you'll be seen as -circumnavigating the platform's fee (anywhere from 15-30%). This is explicitly -against app store policies as the platform is trying to protect its revenue -stream. - -These are hurdles for sure, but there's hope. Here are some things to keep in -mind for each marketplace: - -- **App Store (iOS) -** We only talked about Android today for the technical MWA - reason. However, their policies are also some of the most strict and make it - hard for Solana dApps to exist. For now, Apple has some pretty strict - anti-crypto policies. Wallets seem to be fine, but they'll flag and likely - reject anything that seems like a purchase using crypto. -- **Google Play (Android) -** Google is generally more relaxed, but there are - still a few things to be aware of. As of this writing in November ‘23, Google - is rolling out - [new crypto policies](https://www.theverge.com/2023/7/12/23792720/android-google-play-blockchain-crypto-nft-apps) - to make it more clear what they will and will not allow. Take a look. -- **Steam -** Does not allow crypto games at all - > “built on blockchain technology that issue or allow the exchange of - > cryptocurrencies or NFTs.” -- **Download Sites / Your Site -** Depending on the target platform, you can - make your dApp available for download on your own site. However, most users - are wary of downloading mobile applications from websites. -- **dApp Store (Solana) -** Solana saw the issues with mobile dApp distribution - on other platform app stores and decided to make their own. As part of the SMS - stack, they created the - [Solana dApp Store](https://docs.solanamobile.com/getting-started/overview#solana-dapp-store). - -### Conclusion - -Getting started with mobile Solana development is fairly straightforward thanks -to SMS. While React Native is slightly different than React, the code you have -to write is more similar than different. The primary difference is that the -portion of your code that interacts with wallets will exist within the -`transact` callback. Remember to look at our other lessons if you need a -refresher on Solana development more broadly. - -## Lab - -Let's practice this together by building a simple Android mobile counter dApp -with React Native. The app will interact with the Anchor counter program that we -made in the -[Intro to client-side Anchor development](https://www.soldev.app/course/intro-to-anchor-frontend) -lesson. This dApp simply displays a counter and allows users to increment the -count through a Solana program. In this app, we'll be able to see the current -count, connect our wallet, and increment the count. We'll be doing this all on -Devnet and will be compiling only for Android. - -This program already exists and is already deployed on Devnet. Feel free to -check out the -[deployed program's code](https://github.com/Unboxed-Software/anchor-ping-frontend/tree/solution-decrement) -if you want more context. - -We'll write this application in vanilla React Native without a starting -template. Solana Mobile provides a -[React Native template](https://docs.solanamobile.com/react-native/react-native-scaffold) -that shortcuts some of the boilerplate, but there's no better way to learn than -to do something from scratch. - -#### 0. Prerequisites - -React Native allows us to write mobile applications using similar patterns as -React. However, under the hood, our React code needs to be compiled down to -languages and frameworks that work with the device's native OS. This requires a -few prerequisite setup items: - -1. [Set up a React Native dev environment](https://reactnative.dev/docs/environment-setup?guide=native#creating-a-new-application). - Go through the - [**_entire article_**](https://reactnative.dev/docs/environment-setup?guide=native#creating-a-new-application), - using Android as the target OS. For convenience, we've typed out the - high-level steps below. Keep in mind that the source article might change - from the time of writing to when you're reading this. The source article is - your source of truth here. - - 1. Install dependencies - 2. Install Android Studio - 3. Configure **ANDROID_HOME** environment variable - 4. Create a new sample project (this is only used to set up the emulator) - - 1. If you run into the error `✖ Copying template`, add the `--npm` flag - at the end - - ```bash - npx react-native@latest init AwesomeProject - ✔ Downloading template - ✖ Copying template - - npx react-native@latest init AwesomeProject --npm - ✔ Downloading template - ✔ Copying template - ``` - - 5. Run and compile the sample project on your emulator - -2. Install and run the Solana fake wallet - - 1. Install the repo - - ```bash - git clone https://github.com/solana-mobile/mobile-wallet-adapter.git - ``` - - 2. In Android - Studio, `Open project > Navigate to the cloned directory > Select mobile-wallet-adapter/android` - 3. After Android Studio finishes loading the project, select `fakewallet` in - the build/run configuration dropdown in the top right - - ![Fake Wallet](/public/assets/courses/unboxed/basic-solana-mobile-fake-wallet.png) - - 4. For debugging, you'll want to use `Logcat`. Now that your fake wallet is - running on the emulator, go to `View -> Tool Windows -> Logcat`. This will - open up a console logging out what's happening with fake wallet. - -3. (Optional) Install other - [Solana wallets](https://solana.com/ecosystem/explore?categories=wallet) on - the Google Play store. - -Lastly, if you run into Java versioning issues - you'll want to be on Java -version 11. To check what you're currently running type `java --version` in your -terminal. - -#### 1. Plan out the App's Structure +## Debugging + +Debugging can be challenging when working with Solana mobile transactions, as +two separate applications are involved: your app and the mobile wallet. Unlike +typical single-application setups, you won't have direct access to the wallet’s +logs, which makes tracking issues more complex. + +However, Android Studio’s +[Logcat](https://developer.android.com/studio/debug/logcat) provides a useful +solution - enabling you to view logs from all applications running on your +device including the wallet. By leveraging Logcat, you can monitor the +interaction between your app and the wallet, helping you identify any issues +that arise during transaction signing and submission. + +If Logcat is not your preferred tool, an alternative approach is to use the +wallet solely for signing transactions, while handling the actual transaction +submission in your app’s code. This method allows for greater control over +debugging, as you can inspect the transaction flow more thoroughly on the client +side. + +## Deploying for Solana Mobile + +Deploying mobile applications can be challenging, and the complexity increases +when dealing with blockchain-based apps. Two primary factors contribute to this +difficulty: customer safety and financial incentives. + +### Customer Safety and Regulatory Uncertainty: + +Most mobile app marketplaces, such as the Apple App Store and Google Play Store, +have policies that restrict blockchain-related apps. Since blockchain is still a +relatively new and evolving technology, platforms are cautious about regulatory +compliance. They often adopt strict guidelines to protect users from potential +risks associated with blockchain apps. + +### In-App Purchases and Platform Fees: + +Another significant challenge arises when using blockchain transactions for +in-app purchases. Many platforms impose a transaction fee on purchases made +within their apps (ranging from 15% to 30%). Payment via the blockchain is often +seen as a way to bypass these fees, which is explicitly prohibited by most app +stores. These platforms prioritize protecting their revenue streams and +therefore enforce strict policies against apps that facilitate blockchain +payments for in-app purchases. + +> While traditional app stores impose strict policies around blockchain +> transactions to protect their revenue and comply with regulations, alternative +> distribution methods like the Solana app Store offers developers a more +> flexible platform for deploying Solana-based mobile applications. This +> decentralized approach bypasses many of the restrictions seen in centralized +> app marketplaces, allowing apps to thrive in a more blockchain-friendly +> ecosystem. + +## Conclusion + +Getting started with Solana mobile development is more accessible than ever, +thanks to the Solana Mobile Stack (SMS). Although React Native introduces some +differences compared to React, much of the code you will write remains familiar, +particularly when it comes to structuring the UI and handling state. The main +distinction lies in how you interact with wallets, which requires using the +`transact` callback to establish wallet sessions, sign transactions, and +communicate with Solana’s blockchain. + +As you continue building Solana mobile apps, it's essential to keep learning and +refining your skills. Be sure to explore additional resources like: + +- [The official Solana Developer Docs](https://solana.com/docs) for in-depth + guides on Solana’s core libraries and best practices. +- [Solana Stack Exchange](https://solana.stackexchange.com/) forum for + troubleshooting, sharing insights, and staying updated on the latest ecosystem + changes. + +Mastering mobile Solana development will open up new opportunities in +decentralized finance (DeFi), gaming, and e-commerce, allowing you to build +cutting-edge applications with a seamless user experience. Stay curious and +experiment with different tools to push the boundaries of what you can achieve +with mobile apps. Let's put our knowledge to test by building a counting app +with React Native for Android OS! + +## Lab: Building a Mobile Counter app with React Native + +This app will display a counter and allow users to make increments via a +transaction on the Solana blockchain. The app will also connect to a wallet for +signing transactions. + +We will use the **Anchor framework** to interact with the on-chain counter +program. The client side has already been developed in one of our previous +lessons called +[Intro to client-side Anchor development](https://solana.com/developers/courses/onchain-development/intro-to-anchor-frontend), +feel free to check out its code for more context. + +To ensure you fully understand the core concepts, we will write this application +in vanilla React Native without a starting template. While Solana Mobile offers +templates that handle some boilerplate, building from scratch provides a much +deeper understanding. + +### Getting Started + +To get started, you will need to properly set up a React Native development +environment if you didn't already. This +[article](https://reactnative.dev/docs/set-up-your-environment) shows you how. +Remember that this step is not required if you are using a +[Framework](https://reactnative.dev/architecture/glossary#react-native-framework). + +Ensure you have [Node.js](https://nodejs.org/en/download) installed on your +system. These will manage your JavaScript packages. Install Android Studio: + +Android Studio is required to run the Android emulator and to compile your React +Native app for Android devices. Configure the ANDROID_HOME Environment Variable: + +> **NOTE:** You will need to configure the `ANDROID_HOME` environment variable +> so that your terminal can recognize Android’s SDK tools. This step is critical +> for running and building your app on Android. + +## Project Setup + +Create a Sample Project for the Emulator Setup to ensure your Android +environment is set up correctly. In your terminal, run the code below within +your preferred directory to scaffold a new React Native project, where +`SampleProject` is your preferred project name. You can open the project in +Android Studio and ensure it runs correctly on the Android emulator. + +```bash + npx react-native init SampleProject --npm +``` + +### Cloning and Running MWA + +1. Clone the repo in `SampleProject` + + ```bash + git clone https://github.com/solana-mobile/mobile-wallet-adapter.git + ``` + +2. In Android Studio, _Open project > Navigate to the cloned directory > Select + mobile-wallet-adapter/android_ +3. After Android Studio finishes loading the project, select `fakewallet` in + the build/run configuration dropdown in the top right + + ![Fake Wallet](/public/assets/courses/unboxed/basic-solana-mobile-fake-wallet.png) + +4. For easier debugging, use **Logcat**. Check the + [Logcat installation guide](https://developer.android.com/studio/debug/logcat) + if you are interested. +5. Now that your fake wallet is running on the emulator, go to _View -> Tool + Windows -> Logcat_. This will open up a console logging out what’s happening + with fake wallet. + +6. (Optional) Install other + [Solana wallets](https://play.google.com/store/search?q=solana%20wallet&c=apps) + on the Google Play store. + +Lastly, we recommend installing _java version 11_ to avoid dependency errors. To +know what version you have installed, run `java --version` in your terminal. + +### 1. Plan out the App Structure Before we do any coding, let's conceptualize the outline of the app. Again, this app will connect to and interact with the counter program we've already deployed @@ -437,7 +437,7 @@ to Devnet. To do this, we'll need the following: There will be more files and considerations, but these are the most important files we'll be creating and working with. -#### 2. Create the App +### 2. Create the App Now that we've got some of the basic setup and structure down, let's scaffold a new app with the following command: @@ -457,35 +457,36 @@ npm run android ``` This should open and run the app in your Android emulator. If you run into -problems, check to make sure you've accomplished everything in the -[prerequisites section](#0-prerequisites). +problems, check to make sure you’ve accomplished everything in the +[_Getting Started_](#getting-started) section. -#### 3. Install Dependencies +### 3. Install Dependencies -We'll need to add in our Solana dependencies. +We will need to import our Solana dependencies. [The Solana Mobile docs provide a nice list of packages](https://docs.solanamobile.com/react-native/setup) and explanations for why we need them: - `@solana-mobile/mobile-wallet-adapter-protocol`: A React Native/Javascript API enabling interaction with MWA-compatible wallets - `@solana-mobile/mobile-wallet-adapter-protocol-web3js`: A convenience wrapper - to use common primitives - from [@solana/web3.js](https://github.com/solana-labs/solana-web3.js), such - as `Transaction` and `Uint8Array` + to use common primitives from + [@solana/web3.js](https://github.com/solana-labs/solana-web3.js), such as + `Transaction` and `Uint8Array` - `@solana/web3.js`: Solana Web Library for interacting with the Solana network - through the [JSON RPC API](/docs/rpc/http/index.mdx) -- `react-native-get-random-values` Secure random number generator polyfill - for `web3.js` underlying Crypto library on React Native -- `buffer`: Buffer polyfill; also needed for `web3.js` on React Native + through th + [JSON RPC API](https://github.com/solana-foundation/developer-content/blob/main/docs/rpc/http/index.mdx) +- `@react-native-get-random-values` Secure random number generator polyfill for +- `web3.js` underlying library on React Native +- `buffer`: Buffer polyfill; also needed for `web3.js` on React Native -In addition to this list, we'll add two more packages: +In addition to this list, we will add two more packages: - `@coral-xyz/anchor`: The Anchor TS client. - `assert`: A polyfill that lets Anchor do its thing. - `text-encoding-polyfill`: A polyfill needed to create the `Program` object -If you're not familiar: polyfills actively replace Node-native libraries to make -them work anywhere Node is not running. We'll finish our polyfill setup shortly. +If you’re not familiar: polyfills provide Node-native libraries to make them +work anywhere Node is not running. We will finish our polyfill setup shortly. For now, install dependencies using the following command: ```bash @@ -500,7 +501,7 @@ npm install \ text-encoding-polyfill ``` -#### 4. Create ConnectionProvider.tsx +### 4. Create ConnectionProvider.tsx file Let's start adding our Solana functionality. Create a new folder called `components` and within it, a file called `ConnectionProvider.tsx`. This @@ -526,12 +527,11 @@ const ConnectionContext = createContext( {} as ConnectionContextState, ); -export function ConnectionProvider(props: ConnectionProviderProps) { - const { - children, - endpoint, - config = { commitment: "confirmed" }, - } = { ...props }; +export function ConnectionProvider({ + children, + endpoint, + config = { commitment: "confirmed" }, +}: ConnectionProviderProps) { const connection = useMemo( () => new Connection(endpoint, config), [config, endpoint], @@ -548,11 +548,11 @@ export const useConnection = (): ConnectionContextState => useContext(ConnectionContext); ``` -#### 5. Create AuthProvider.tsx +### 5. Create AuthProvider.tsx file -The next Solana provision we'll need is the auth provider. This is one of the -main differences between mobile and web development. What we're implementing -here is roughly equivalent to the `WalletProvider` that we're used to in web +The next Solana provision we will need is the **auth provider**. This is one of +the main differences between mobile and web development. What we’re implementing +here is roughly equivalent to the `WalletProvider` that we’re used to in web apps. However, since we're using Android and its natively installed wallets, the flow to connect and utilize them is a bit different. Most notably, we need to follow the MWA protocol. @@ -570,20 +570,20 @@ We do this by providing the following in our `AuthProvider`: - `deauthorizeSession(wallet)`: Deauthorizes the `wallet`. - `onChangeAccount`: Acts as a handler when `selectedAccount` is changed. -We're also going to throw in some utility methods: +We are also going to throw in some utility methods: - `getPublicKeyFromAddress(base64Address)`: Creates a new Public Key object from the Base64 address given from the `wallet` object - `getAuthorizationFromAuthResult`: Handles the authorization result, extracts relevant data from the result, and returns the `Authorization` context object -We'll expose all of this through a `useAuthorization` hook. +We will expose all of this through a `useAuthorization` hook. -Since this provider is the same across virtually all apps, we're going to give -you the full implementation that you can copy/paste. We'll dig into the details -of MWA in a future lesson. +Since this provider is the same across all apps, we are going to give you the +full implementation that you can copy and paste. We will dig into the details of +MWA in a future lesson. -Create the file `AuthProvider.tsx` in the `components` and paste in the +Create the file `AuthProvider.tsx` in the `components` folder and paste in the following: ```tsx @@ -601,25 +601,19 @@ import { toUint8Array } from "js-base64"; import { useState, useCallback, useMemo, ReactNode } from "react"; import React from "react"; -export const AuthUtils = { +const AuthUtils = { getAuthorizationFromAuthResult: ( authResult: AuthorizationResult, previousAccount?: Account, ): Authorization => { - let selectedAccount: Account; - if ( - //no wallet selected yet - previousAccount === null || - //the selected wallet is no longer authorized + const selectedAccount = + previousAccount === undefined || !authResult.accounts.some( ({ address }) => address === previousAccount.address, ) - ) { - const firstAccount = authResult.accounts[0]; - selectedAccount = AuthUtils.getAccountFromAuthorizedAccount(firstAccount); - } else { - selectedAccount = previousAccount; - } + ? AuthUtils.getAccountFromAuthorizedAccount(authResult.accounts[0]) + : previousAccount; + return { accounts: authResult.accounts.map( AuthUtils.getAccountFromAuthorizedAccount, @@ -631,19 +625,13 @@ export const AuthUtils = { getAccountFromAuthorizedAccount: ( authAccount: AuthorizedAccount, - ): Account => { - return { - ...authAccount, - publicKey: AuthUtils.getPublicKeyFromAddress(authAccount.address), - }; - }, - - getPublicKeyFromAddress: (address: Base64EncodedAddress) => { - return new PublicKey(toUint8Array(address)); - }, + ): Account => ({ + ...authAccount, + publicKey: new PublicKey(toUint8Array(authAccount.address)), + }), }; -export type Account = Readonly<{ +type Account = Readonly<{ address: Base64EncodedAddress; label?: string; publicKey: PublicKey; @@ -655,11 +643,11 @@ type Authorization = Readonly<{ selectedAccount: Account; }>; -export const AppIdentity = { +const APP_IDENTITY = { name: "Solana Counter Incrementor", }; -export type AuthorizationProviderContext = { +type AuthorizationProviderContext = { accounts: Account[] | null; authorizeSession: (wallet: AuthorizeAPI & ReauthorizeAPI) => Promise; deauthorizeSession: (wallet: DeauthorizeAPI) => void; @@ -669,25 +657,24 @@ export type AuthorizationProviderContext = { const AuthorizationContext = React.createContext({ accounts: null, - authorizeSession: (_wallet: AuthorizeAPI & ReauthorizeAPI) => { + authorizeSession: () => { throw new Error("Provider not initialized"); }, - deauthorizeSession: (_wallet: DeauthorizeAPI) => { + deauthorizeSession: () => { throw new Error("Provider not initialized"); }, - onChangeAccount: (_nextSelectedAccount: Account) => { + onChangeAccount: () => { throw new Error("Provider not initialized"); }, selectedAccount: null, }); -export type AuthProviderProps = { +type AuthProviderProps = { children: ReactNode; cluster: Cluster; }; -export function AuthorizationProvider(props: AuthProviderProps) { - const { children, cluster } = { ...props }; +function AuthorizationProvider({ children, cluster }: AuthProviderProps) { const [authorization, setAuthorization] = useState( null, ); @@ -699,55 +686,47 @@ export function AuthorizationProvider(props: AuthProviderProps) { authorization?.selectedAccount, ); setAuthorization(nextAuthorization); - return nextAuthorization; }, - [authorization, setAuthorization], + [authorization], ); const authorizeSession = useCallback( async (wallet: AuthorizeAPI & ReauthorizeAPI) => { - const authorizationResult = await (authorization - ? wallet.reauthorize({ + const authorizationResult = authorization + ? await wallet.reauthorize({ auth_token: authorization.authToken, - identity: AppIdentity, + identity: APP_IDENTITY, }) - : wallet.authorize({ cluster, identity: AppIdentity })); + : await wallet.authorize({ cluster, identity: APP_IDENTITY }); return (await handleAuthorizationResult(authorizationResult)) .selectedAccount; }, - [authorization, handleAuthorizationResult], + [authorization, cluster, handleAuthorizationResult], ); const deauthorizeSession = useCallback( async (wallet: DeauthorizeAPI) => { - if (authorization?.authToken === null) { - return; + if (authorization?.authToken) { + await wallet.deauthorize({ auth_token: authorization.authToken }); + setAuthorization(null); } - - await wallet.deauthorize({ auth_token: authorization.authToken }); - setAuthorization(null); }, - [authorization, setAuthorization], + [authorization], ); - const onChangeAccount = useCallback( - (nextAccount: Account) => { - setAuthorization(currentAuthorization => { - if ( - //check if the account is no longer authorized - !currentAuthorization?.accounts.some( - ({ address }) => address === nextAccount.address, - ) - ) { - throw new Error(`${nextAccount.address} is no longer authorized`); - } - + const onChangeAccount = useCallback((nextAccount: Account) => { + setAuthorization(currentAuthorization => { + if ( + currentAuthorization?.accounts.some( + ({ address }) => address === nextAccount.address, + ) + ) { return { ...currentAuthorization, selectedAccount: nextAccount }; - }); - }, - [setAuthorization], - ); + } + throw new Error(`${nextAccount.address} is no longer authorized`); + }); + }, []); const value = useMemo( () => ({ @@ -767,21 +746,28 @@ export function AuthorizationProvider(props: AuthProviderProps) { ); } -export const useAuthorization = () => React.useContext(AuthorizationContext); +const useAuthorization = () => React.useContext(AuthorizationContext); + +export { + AuthorizationProvider, + useAuthorization, + type Account, + type AuthProviderProps, + type AuthorizationProviderContext, +}; ``` -#### 6. Create ProgramProvider.tsx +### 6. Create ProgramProvider.tsx file The last provider we need is our program provider. This will expose the counter program we want to interact with. -Since we're using the Anchor TS client to interact with our program, we need the -program's IDL. Start by creating a root-level folder called `models`, then -create a new file `anchor-counter.ts`. Paste the contents of the -[Anchor Counter IDL](/public/assets/courses/unboxed/counter-rn-idl.ts) into this -new file. +Since we are using the Anchor TS client to interact with our program, we need +the program's IDL. Start by creating a root-level folder called `models`, then +create a new file `anchor-counter.ts`. Paste the contents of the Anchor Counter +IDL into this new file. -Next, create the file `ProgramProvider.tsx` inside of `components`. Inside we'll +Next, create the file `ProgramProvider.tsx` inside of components. Inside we will create the program provider to surface our program and the counter PDA: ```tsx @@ -820,8 +806,7 @@ export type ProgramProviderProps = { children: ReactNode; }; -export function ProgramProvider(props: ProgramProviderProps) { - const { children } = props; +export function ProgramProvider({ children }: ProgramProviderProps) { const { connection } = useConnection(); const [program, setProgram] = useState | null>(null); const [counterAddress, setCounterAddress] = useState(null); @@ -831,6 +816,11 @@ export function ProgramProvider(props: ProgramProviderProps) { "ALeaCzuJpZpoCgTxMjJbNjREVqSwuvYFRZUfc151AKHU", ); + // MockWallet is a placeholder wallet used for initializing the AnchorProvider. + // In a mobile app, we don't need a real wallet here because the actual signing + // will be done by the user's mobile wallet app. This mock wallet allows us to + // set up the provider without a real wallet instance. + const MockWallet = { signTransaction: () => Promise.reject(), signAllTransactions: () => Promise.reject(), @@ -875,7 +865,7 @@ export function ProgramProvider(props: ProgramProviderProps) { export const useProgram = () => useContext(ProgramContext); ``` -#### 7. Modify App.tsx +### 7. Modify App.tsx file Now that we have all our providers, let's wrap our app with them. We're going to re-write the default `App.tsx` with the following changes: @@ -907,11 +897,14 @@ export default function App() { const endpoint = clusterApiUrl(cluster); return ( + // ConnectionProvider: Manages the connection to the Solana network + // AuthorizationProvider: Handles wallet authorization + // ProgramProvider: Provides access to the Solana program @@ -921,7 +914,7 @@ export default function App() { } ``` -#### 8. Create MainScreen.tsx +### 8. Create MainScreen.tsx file Now, let's put everything together to create our UI. Create a new folder called `screens` and a new file called `MainScreen.tsx` inside of it. In this file, we @@ -935,47 +928,45 @@ to CSS. In `screens/MainScreen.tsx` paste the following: ```tsx +import React from "react"; import { StatusBar, StyleSheet, View } from "react-native"; import { CounterView } from "../components/CounterView"; import { CounterButton } from "../components/CounterButton"; -import React from "react"; -const mainScreenStyles = StyleSheet.create({ +export function MainScreen() { + return ( + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ container: { height: "100%", width: "100%", backgroundColor: "lightgray", }, - - incrementButtonContainer: { position: "absolute", right: "5%", bottom: "3%" }, + incrementButtonContainer: { + position: "absolute", + right: "5%", + bottom: "3%", + }, counterContainer: { alignContent: "center", alignItems: "center", justifyContent: "center", }, }); - -export function MainScreen() { - return ( - - - - - - - - - - ); -} ``` -#### 9. Create CounterView.tsx +### 9. Create CounterView.tsx file The `CounterView` is the first of our two program-specific files. `CounterView`'s only job is to fetch and listen for updates on our `Counter` @@ -1045,7 +1036,7 @@ export function CounterView() { } ``` -#### 10. Create CounterButton.tsx +### 10. Create CounterButton.tsx file Finally, we have our last component, the `CounterButton`. This floating action button will do the following in a new function `incrementCounter`: @@ -1181,7 +1172,7 @@ export function CounterButton() { } ``` -#### 11. Build and Run +### 11. Build and Run Now it's time to test that everything works! Build and run with the following command: @@ -1200,7 +1191,7 @@ test your app: If you run into problems, here are some examples of what they could be and how to fix them: -- Application does not build → Exit Metro with ctrl+c and try again +- Application does not build → Exit Metro with _Ctrl+C_ and try again - Nothing happens when you press the `CounterButton` → Make sure you have Solana wallet installed ( like the fake wallet we installed in Prerequisites ) - You get stuck in a forever loop while calling `increment` → This is likely due @@ -1208,22 +1199,24 @@ to fix them: `CounterButton` and manually send some Devnet sol to your wallet's address (printed in the console) -That's it! You've made your first Solana Mobile dApp. If you get stuck, feel -free to check out the -[full solution code](https://github.com/Unboxed-Software/solana-react-native-counter) +That's it! You've made your first Solana Mobile app. If you get stuck, feel free +to check out the +[full solution code](https://github.com/solana-developers/react-native-counter) on the `main` branch of the repository. ## Challenge -Your challenge today is to take our app and add a decrement function. Simply add -another button and call the `decrement` function on our program. This -instruction already exists on the program and its IDL, so you simply need to -write client code to call it. +Your next challenge is to expand the app by adding a `decrement` function. You +need to create another button that will call the `decrement` method on the +Solana program. The logic for the decrement function already exists in the +program’s **IDL** (**Interface Description Language**), so your task is to write +the client-side code that interacts with it. -After you give it a try on your own, feel free to take a look at the -[solution code on the `solution` branch](https://github.com/Unboxed-Software/solana-react-native-counter/tree/solution). +Once you've completed this, you can check your solution against the solution +code available on the +[solution branch](https://github.com/solana-developers/react-native-counter). - -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=c15928ce-8302-4437-9b1b-9aa1d65af864)! + +If you’ve successfully completed the lab, push your code to GitHub and share +your feedback on this lesson through this [form](https://form.typeform.com/to/IPH0UGz7#answers-lesson=c15928ce-8302-4437-9b1b-9aa1d65af864) From 32952e14aa4403f4cb729307523a4cce8822b9c4 Mon Sep 17 00:00:00 2001 From: Britt Cyr Date: Fri, 4 Oct 2024 00:22:59 -0400 Subject: [PATCH 30/54] fix basis points comment (#550) * fix basis points comment * fmt --- content/courses/token-extensions/transfer-fee.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/courses/token-extensions/transfer-fee.md b/content/courses/token-extensions/transfer-fee.md index 16cd7ff93..f7d574865 100644 --- a/content/courses/token-extensions/transfer-fee.md +++ b/content/courses/token-extensions/transfer-fee.md @@ -159,7 +159,7 @@ There are a couple of notes when transferring tokens with the `transfer fee` extension. First, the recipient is the one who "pays" for the fee. If I send 100 tokens -with basis points of 50 (5%), the recipient will receive 95 tokens (five +with basis points of 500 (5%), the recipient will receive 95 tokens (five withheld) Second, the fee is calculated not by the tokens sent, but the smallest unit of @@ -167,9 +167,9 @@ said token. In Solana programming, we always specify amounts to be transferred, minted or burned in their smallest unit. To send one SOL to someone, we actually send `1 * 10 ^ 9` lamports. Another way to look at it is if you wanted to send one US dollar, you're actually sending 100 pennies. Let's make this dollar a -token with a 50 basis points (5%) transfer fee. Sending one dollar, would result -in a five cent fee. Now let's say we have a max fee of 10 cents, this will -always be the highest fee, even if we send $10,000. +token with a 500 basis points (5%) transfer fee. Sending one dollar, would +result in a five cent fee. Now let's say we have a max fee of 10 cents, this +will always be the highest fee, even if we send $10,000. The calculation can be summed up like this: From 02ec29c39a6f67695028c12d092b06264737766e Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Fri, 4 Oct 2024 09:54:14 +0530 Subject: [PATCH 31/54] Native onchain development - Updated cross program invocation lesson (#493) * Updated content and code snippets as per guidelines * Updated content as per comments * Updated initialize token mint intsructions * Update cross-program-invocations.md --- .../cross-program-invocations.md | 642 ++++++++++++------ 1 file changed, 416 insertions(+), 226 deletions(-) diff --git a/content/courses/native-onchain-development/cross-program-invocations.md b/content/courses/native-onchain-development/cross-program-invocations.md index dff3df609..693c95bcd 100644 --- a/content/courses/native-onchain-development/cross-program-invocations.md +++ b/content/courses/native-onchain-development/cross-program-invocations.md @@ -5,74 +5,84 @@ objectives: - Describe how to construct and use CPIs - Explain how a program provides a signature for a PDA - Avoid common pitfalls and troubleshoot common errors associated with CPIs -description: "How to invoke functions in other Solana programs." +description: "Learn how to invoke functions in other Solana programs." --- ## Summary -- A **Cross-Program Invocation (CPI)** is a call from one program to another, - targeting a specific instruction on the program called -- CPIs are made using the commands `invoke` or `invoke_signed`, the latter being - how programs provide signatures for PDAs that they own -- CPIs make programs in the Solana ecosystem completely interoperable because - all public instructions of a program can be invoked by another program via a - CPI -- Because we have no control over the accounts and data submitted to a program, - it's important to verify all of the parameters passed into a CPI to ensure - program security +- A **Cross-Program Invocation (CPI)** is when one program calls another, + targeting a specific instruction in the called program. +- CPIs are performed using the commands `invoke` or `invoke_signed`, with the + latter enabling programs to sign on behalf of Program Derived Addresses (PDAs) + they own. +- CPIs enable Solana programs to be fully interoperable, allowing any + instruction handler to be invoked by another program via a CPI. +- CPIs are commonly used. For example, if your program transfers tokens, it will + perform a CPI to the Token or Token Extensions programs to execute the + transfer. +- Since the calling program in a CPI does not have control over the accounts or + data passed to the invoked program, it's crucial for the invoked program to + verify all parameters. This ensures that malicious or incorrect data doesn't + compromise program security. ## Lesson ### What is a CPI? -A Cross-Program Invocation (CPI) is a direct call from one program into another. -Just as any client can call any program using the JSON RPC, any program can call -any other program directly. The only requirement for invoking an instruction on -another program from within your program is that you construct the instruction -correctly. You can make CPIs to native programs, other programs you've created, -and third party programs. CPIs essentially turn the entire Solana ecosystem into -one giant API that is at your disposal as a developer. +A **Cross-Program Invocation (CPI)** is when one program directly calls another +program's instruction, similar to how a client makes calls to programs using the +JSON RPC API. In a CPI, your program can call native programs, third-party +programs, or programs you've created. CPIs allow for seamless interaction +between programs, effectively making the entire Solana ecosystem one large API +for developers. -CPIs have a similar make up to instructions that you are used to creating client -side. There are some intricacies and differences depending on if you are using -`invoke` or `invoke_signed`. We'll be covering both of these later in this -lesson. +To invoke an instruction on another program, you need to construct the +instruction correctly. The process of creating a CPI is similar to creating +instructions on the client side, but there are important distinctions when using +`invoke` or `invoke_signed`. We'll dive into both methods later in this lesson. -### How to make a CPI +### Making a Cross-Program Invocation (CPI) -CPIs are made using the -[`invoke`](https://docs.rs/solana-program/1.10.19/solana_program/program/fn.invoke.html) +To make a CPI, use either the +[`invoke`](https://docs.rs/solana-program/latest/solana_program/program/fn.invoke.html) or -[`invoke_signed`](https://docs.rs/solana-program/1.10.19/solana_program/program/fn.invoke_signed.html) -function from the `solana_program` crate. You use `invoke` to essentially pass -through the original transaction signature that was passed into your program. -You use `invoke_signed` to have your program "sign" for its PDAs. +[`invoke_signed`](https://docs.rs/solana-program/latest/solana_program/program/fn.invoke_signed.html) +functions from the `solana_program` crate. + +- Use `invoke` to pass through the original transaction signature that was + submitted to your program. +- Use `invoke_signed` when your program needs to "sign" for its Program Derived + Addresses (PDAs). ```rust -// Used when there are not signatures for PDAs needed +// Used when no signatures are required for PDAs pub fn invoke( instruction: &Instruction, account_infos: &[AccountInfo<'_>] ) -> ProgramResult -// Used when a program must provide a 'signature' for a PDA, hence the signer_seeds parameter +// Used when a program must provide a 'signature' for a PDA, utilizing the signer_seeds parameter pub fn invoke_signed( instruction: &Instruction, account_infos: &[AccountInfo<'_>], + // An array of signing PDAs, each with an array of seeds, which are an array of `u8` bytes. signers_seeds: &[&[&[u8]]] ) -> ProgramResult ``` -CPIs extend the privileges of the caller to the callee. If the instruction the -callee program is processing contains an account that was marked as a signer or -writable when originally passed into the caller program, then it will be -considered a signer or writable account in the invoked program as well. +When you make a Cross-Program Invocation (CPI), the privileges of the invoking +program are extended to the invoked program. If the invoking program's +instruction handler had accounts marked as a signer or writable when calling the +invoked program, those accounts retain their signer or writable status in the +invoked program. + + -It's important to note that you as the developer decide which accounts to pass -into the CPI. You can think of a CPI as building another instruction from -scratch with only information that was passed into your program. +As the developer, you have full control over which accounts are passed into the +CPI. You can think of constructing a CPI as building a new instruction from +scratch, but only with the data that was passed into your program. -#### CPI with `invoke` +#### CPI with invoke function ```rust invoke( @@ -85,13 +95,13 @@ invoke( )?; ``` -- `program_id` - the public key of the program you are going to invoke -- `account` - a list of account metadata as a vector. You need to include every - account that the invoked program will read or write -- `data` - a byte buffer representing the data being passed to the callee - program as a vector +- `program_id` - The public key of the program you're invoking. +- `account` - A list of account metadata as a vector. Include every account the + invoked program will read or write. +- `data` - A byte buffer representing the data passed to the invoked program as + a vector. -The `Instruction` type has the following definition: +The `Instruction` struct has the following definition: ```rust pub struct Instruction { @@ -101,23 +111,21 @@ pub struct Instruction { } ``` -Depending on the program you're making the call to, there may be a crate -available with helper functions for creating the `Instruction` object. Many -individuals and organizations create publicly available crates alongside their -programs that expose these sorts of functions to simplify calling their -programs. This is similar to the Typescript libraries we've used in this course -(e.g. [@solana/web3.js](https://solana-labs.github.io/solana-web3.js/), -[@solana/spl-token](https://solana-labs.github.io/solana-program-library/token/js/)). -For example, in this lesson's lab we'll be using the `spl_token` crate to create -minting instructions. In all other cases, you'll need to create the -`Instruction` instance from scratch. +Depending on the program you're calling, there may be a crate available with +helper functions for creating the `Instruction` object. Many individuals and +organizations provide publicly available crates alongside their programs that +expose these functions, simplifying program interaction. + +For example, in this lesson's lab, we'll be using the `spl_token` crate to +create minting instructions. In cases where no such crate is available, you'll +need to manually create the `Instruction` instance. -While the `program_id` field is fairly straightforward, the `accounts` and -`data` fields require some explanation. +While the `program_id` field is straightforward, the `accounts` and `data` +fields require further explanation. -Both the `accounts` and `data` fields are of type `Vec`, or vector. You can use +Both the `accounts` and `data` fields are of type `Vec` (vector). You can use the [`vec`](https://doc.rust-lang.org/std/macro.vec.html) macro to construct a -vector using array notation, like so: +vector using array notation, as shown below: ```rust let v = vec![1, 2, 3]; @@ -151,14 +159,16 @@ vec![ ] ``` -The final field of the instruction object is the data, as a byte buffer of -course. You can create a byte buffer in Rust using the `vec` macro again, which -has an implemented function allowing you to create a vector of certain length. -Once you have initialized an empty vector, you would construct the byte buffer -similar to how you would client-side. Determine the data required by the callee -program and the serialization format used and write your code to match. Feel -free to read up on some of the -[features of the `vec` macro available to you here](https://doc.rust-lang.org/alloc/vec/struct.Vec.html#). +The final field of the `Instruction` object is the data, represented as a byte +buffer. In Rust, you can create this buffer by using +[`Vec::with_capacity()`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.with_capacity) +to allocate space, and then populate the vector by pushing values or extending +it with slices. This allows you to construct the byte buffer incrementally, +similar to how you would on the client side. + +Determine the data required by the invoked program and the serialization format +used, then write your code to match. Feel free to read up on some of the +[features of the `vec` macro](https://doc.rust-lang.org/alloc/vec/struct.Vec.html#). ```rust let mut vec = Vec::with_capacity(3); @@ -177,19 +187,18 @@ input, iterates over the slice, clones each element, and then appends it to the In addition to the instruction, both `invoke` and `invoke_signed` also require a list of `account_info` objects. Just like the list of `AccountMeta` objects you -added to the instruction, you must include all of the accounts that the program -you're calling will read or write. +added to the instruction, you must include all the accounts that the program +you're invoking will read or write. By the time you make a CPI in your program, you should have already grabbed all the `account_info` objects that were passed into your program and stored them in variables. You'll construct your list of `account_info` objects for the CPI by -choosing which of these accounts to copy and send along. +selecting which of these accounts to copy and send along. -You can copy each `account_info` object that you need to pass into the CPI using -the +You can copy each `account_info` object you need to pass into the CPI using the [`Clone`](https://docs.rs/solana-program/1.10.19/solana_program/account_info/struct.AccountInfo.html#impl-Clone) -trait that is implemented on the `account_info` struct in the `solana_program` -crate. This `Clone` trait returns a copy of the +trait implemented on the `account_info` struct in the `solana_program` crate. +This `Clone` trait returns a copy of the [`account_info`](https://docs.rs/solana-program/1.10.19/solana_program/account_info/struct.AccountInfo.html) instance. @@ -197,7 +206,7 @@ instance. &[first_account.clone(), second_account.clone(), third_account.clone()] ``` -#### CPI with `invoke` +#### CPI with invoke With both the instruction and the list of accounts created, you can perform a call to `invoke`. @@ -214,21 +223,22 @@ invoke( ``` There's no need to include a signature because the Solana runtime passes along -the original signature passed into your program. Remember, `invoke` won't work -if a signature is required on behalf of a PDA. For that, you'll need to use +the original signature provided to your program. Remember, `invoke` won't work +if a signature is required on behalf of a PDA. In that case, you'll need to use `invoke_signed`. -#### CPI with `invoke_signed` +#### CPI with invoke_signed -Using `invoke_signed` is a little different just because there is an additional -field that requires the seeds used to derive any PDAs that must sign the -transaction. You may recall from previous lessons that PDAs do not lie on the -Ed25519 curve and, therefore, do not have a corresponding secret key. You've -been told that programs can provide signatures for their PDAs, but have not -learned how that actually happens - until now. Programs provide signatures for -their PDAs with the `invoke_signed` function. The first two fields of -`invoke_signed` are the same as `invoke`, but there is an additional -`signers_seeds` field that comes into play here. +Using `invoke_signed` is slightly different because there is an additional field +that requires the seeds used to derive any PDAs that must sign the transaction. +You may recall from previous lessons that PDAs do not lie on the Ed25519 curve +and, therefore, do not have a corresponding secret key. You've learned that +programs can provide signatures for their PDAs, but haven't yet learned how this +works—until now. Programs provide signatures for their PDAs with the +`invoke_signed` function. + +The first two fields of `invoke_signed` are the same as `invoke`, but an +additional `signers_seeds` field is required here. ```rust invoke_signed( @@ -246,38 +256,37 @@ runtime to verify that the PDA belongs to the calling program is for the calling program to supply the seeds used to generate the address in the `signers_seeds` field. -The Solana runtime will internally -call [`create_program_address`](https://docs.rs/solana-program/1.4.4/solana_program/pubkey/struct.Pubkey.html#method.create_program_address) -using the seeds provided and the `program_id` of the calling program. It can +The Solana runtime will internally call +[`create_program_address`](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html#method.create_program_address) +using the seeds provided and the `program_id` of the calling program. It will then compare the result against the addresses supplied in the instruction. If -any of the addresses match, then the runtime knows that indeed the program -associated with this address is the caller and thus is authorized to be a -signer. +any of the addresses match, the runtime knows that the program associated with +the address is the invoking and is authorized to be a signer. -### Best Practices and common pitfalls +### Best practices and common pitfalls #### Security checks -There are some common mistakes and things to remember when utilizing CPIs that -are important to your program's security and robustness. The first thing to -remember is that, as we know by now, we have no control over what information is -passed into our programs. For this reason, it's important to always verify the -`program_id`, accounts, and data passed into the CPI. Without these security -checks, someone could submit a transaction that invokes an instruction on a -completely different program than was expected, which is not ideal. - -Fortunately, there are inherent checks on the validity of any PDAs that are -marked as signers within the `invoke_signed` function. All other accounts and -`instruction_data` should be verified somewhere in your program code before -making the CPI. It's also important to make sure you're targeting the intended -instruction on the program you are invoking. The easiest way to do this is to -read the source code of the program you will be invoking just as you would if -you were constructing an instruction from the client side. +There are some common mistakes and important things to remember when utilizing +CPIs to ensure your program's security and robustness. First, keep in mind that +we have no control over the information passed into our programs. Therefore, +it's crucial to always verify the `program_id`, accounts, and data passed into +the CPI. Without these security checks, someone could submit a transaction that +invokes an instruction on a completely different program than expected, which is +a significant security risk. + +Fortunately, the `invoke_signed` function performs inherent checks on the +validity of any PDAs marked as signers. However, all other accounts and +`instruction_data` should be verified in your program code before making the +CPI. It's also important to ensure that you're targeting the intended +instruction in the program you're invoking. The simplest way to do this is to +review the source code of the program you're invoking, just as you would when +constructing an instruction from the client side. #### Common errors -There are some common errors you might receive when executing a CPI, they -usually mean you are constructing the CPI with incorrect information. For +There are common errors you might encounter when executing a CPI, which usually +indicate that you're constructing the CPI with incorrect information. For example, you may come across an error message similar to this: ```text @@ -285,104 +294,108 @@ EF1M4SPfKcchb6scq297y8FPCaLvj5kGjwMzjTM68wjA's signer privilege escalated Program returned error: "Cross-program invocation with unauthorized signer or writable account" ``` -This message is a little misleading, because “signer privilege escalated” does -not seem like a problem but, in reality, it means that you are incorrectly -signing for the address in the message. If you are using `invoke_signed` and -receive this error, then it likely means that the seeds you are providing are -incorrect. You can also find +This message can be misleading because "signer privilege escalated" might not +initially seem like an issue, but it actually means you are incorrectly signing +for the address in the message. If you're using `invoke_signed` and receive this +error, it's likely that the seeds you're providing are incorrect. You can check [an example transaction that failed with this error](https://explorer.solana.com/tx/3mxbShkerH9ZV1rMmvDfaAhLhJJqrmMjcsWzanjkARjBQurhf4dounrDCUkGunH1p9M4jEwef9parueyHVw6r2Et?cluster=devnet). -Another similar error is thrown when an account that's written to isn't marked -as `writable` inside the `AccountMeta` struct. +Another similar error occurs when an account is written to isn't marked as +`writable` in the `AccountMeta` struct. ```text 2qoeXa9fo8xVHzd2h9mVcueh6oK3zmAiJxCTySM5rbLZ's writable privilege escalated Program returned error: "Cross-program invocation with unauthorized signer or writable account" ``` + + Remember, any account whose data may be mutated by the program during execution -must be specified as writable. During execution, writing to an account that was -not specified as writable will cause the transaction to fail. Writing to an -account that is not owned by the program will cause the transaction to fail. Any -account whose lamport balance may be mutated by the program during execution -must be specified as writable. During execution, mutating the lamports of an -account that was not specified as writable will cause the transaction to fail. -While subtracting lamports from an account not owned by the program will cause -the transaction to fail, adding lamports to any account is allowed, as long is -it is mutable. +must be specified as `writable`. During execution, attempting to write to an +account that was not marked as `writable` will cause the transaction to fail. +Similarly, writing to an account not owned by the program will also cause the +transaction to fail. + +Any account whose lamport balance may be mutated by the program during execution +must also be specified as `writable`. Mutating the lamports of an account that +was not marked as `writable` will cause the transaction to fail. While +subtracting lamports from an account not owned by the program will cause the +transaction to fail, adding lamports to any account is allowed, as long as it is +mutable. To see this in action, view this [transaction in the explorer](https://explorer.solana.com/tx/ExB9YQJiSzTZDBqx4itPaa4TpT8VK4Adk7GU5pSoGEzNz9fa7PPZsUxssHGrBbJRnCvhoKgLCWnAycFB7VYDbBg?cluster=devnet). + ### Why CPIs matter? -CPIs are a very important feature of the Solana ecosystem and they make all -programs deployed interoperable with each other. With CPIs there is no need to -re-invent the wheel when it comes to development. This creates the opportunity -for building new protocols and applications on top of what's already been built, -just like building blocks or Lego bricks. It's important to remember that CPIs -are a two-way street and the same is true for any programs that you deploy! If -you build something cool and useful, developers have the ability to build on top -of what you've done or just plug your protocol into whatever it is that they are -building. Composability is a big part of what makes crypto so unique and CPIs -are what makes this possible on Solana. +Cross-Program Invocations (CPIs) are a crucial feature of the Solana ecosystem +because they make all deployed programs interoperable. With CPIs, there's no +need to reinvent the wheel during development, as they enable new protocols and +applications to be built on top of existing ones, much like building blocks or +Lego bricks. CPIs make composability possible, allowing developers to integrate +or build on top of your programs. If you build something cool and useful, other +developers can leverage your protocol in their projects. Composability is one of +the unique aspects of Web3, and CPIs enable this on Solana. Another important aspect of CPIs is that they allow programs to sign for their -PDAs. As you have probably noticed by now, PDAs are used very frequently in -Solana development because they allow programs to control specific addresses in -such a way that no external user can generate transactions with valid signatures -for those addresses. This can be _very_ useful for many applications in Web3 -(e.g. DeFi, NFTs, etc.) Without CPIs, PDAs would not be nearly as useful because -there would be no way for a program to sign transactions involving them - -essentially turning them black holes (once something is sent to a PDA, there -would be no way to get it back out w/o CPIs!) +PDAs. As you've likely noticed, PDAs are frequently used in Solana development +because they allow programs to control specific addresses in a way that prevents +external users from generating valid transactions with signatures for those +addresses. This feature is _extremely_ useful in many Web3 applications, such as +DeFi and NFTs. Without CPIs, PDAs would be far less useful since programs +wouldn't be able to sign transactions involving them—effectively turning them +into black holes where assets sent to a PDA couldn't be retrieved without CPIs! ## Lab -Now let's get some hands on experience with CPIs by making some additions to the -Movie Review program again. If you're dropping into this lesson without having -gone through prior lessons, the Movie Review program allows users to submit -movie reviews and have them stored in PDA accounts. +Now let's get some hands-on experience with CPIs by making some additions to the +Movie Review program. If you're dropping into this lesson without going through +the prior ones, the Movie Review program allows users to submit movie reviews, +which are stored in PDA accounts. -Last lesson, we added the ability to leave comments on other movie reviews using -PDAs. In this lesson, we're going to work on having the program mint tokens to -the reviewer or commenter anytime a review or comment is submitted. +In the +[program derived addresses lesson](/content/courses/native-onchain-development/program-derived-addresses.md), +we added the ability to leave comments on movie reviews using PDAs. In this +lesson, we'll work on having the program mint tokens to reviewers or commenters +whenever a review or comment is submitted. -To implement this, we'll have to invoke the SPL Token Program's `MintTo` -instruction using a CPI. If you need a refresher on tokens, token mints, and -minting new tokens, have a look at the -[Token Program lesson](/content/courses/tokens/token-program) before moving -forward with this lab. +To implement this, we'll invoke the SPL Token Program's `MintTo` instruction +using a CPI. If you need a refresher on tokens, token mints, and minting new +tokens, check out the +[Token Program lesson](/content/courses/tokens-and-nfts/token-program.md) before +moving forward with this lab. -#### 1. Get starter code and add dependencies +### 1. Get starter code and add dependencies -To get started, we will be using the final state of the Movie Review program -from the previous PDA lesson. So, if you just completed that lesson then you're -all set and ready to go. If you are just jumping in here, no worries, you can -[download the starter code here](https://github.com/Unboxed-Software/solana-movie-program/tree/solution-add-comments). -We'll be using the `solution-add-comments` branch as our starting point. +To get started, we'll be using the final state of the Movie Review program from +the +[previous PDA lesson](/content/courses/native-onchain-development/program-derived-addresses.md). +If you just completed that lesson, you're all set and ready to go. If you're +jumping in at this point, no worries! You can download the +[starter code from the `solution-add-comments` branch](https://github.com/solana-developers/movie-program/tree/solution-add-comments). -#### 2. Add dependencies to `Cargo.toml` +### 2. Add dependencies to Cargo.toml Before we get started we need to add two new dependencies to the `Cargo.toml` file underneath `[dependencies]`. We'll be using the `spl-token` and `spl-associated-token-account` crates in addition to the existing dependencies. -```text -spl-token = { version="~3.2.0", features = [ "no-entrypoint" ] } -spl-associated-token-account = { version="=1.0.5", features = [ "no-entrypoint" ] } +```toml +spl-token = { version="6.0.0", features = [ "no-entrypoint" ] } +spl-associated-token-account = { version="5.0.1", features = [ "no-entrypoint" ] } ``` After adding the above, run `cargo check` in your console to have cargo resolve your dependencies and ensure that you are ready to continue. Depending on your setup you may need to modify crate versions before moving on. -#### 3. Add necessary accounts to `add_movie_review` +### 3. Add necessary accounts to add_movie_review Because we want users to be minted tokens upon creating a review, it makes sense to add minting logic inside the `add_movie_review` function. Since we'll be -minting tokens, the `add_movie_review` instruction requires a few new accounts -to be passed in: +minting tokens, the `add_movie_review` instruction handler requires a few new +accounts to be passed in: - `token_mint` - the mint address of the token - `mint_auth` - address of the authority of the token mint @@ -393,7 +406,7 @@ to be passed in: We'll start by adding these new accounts to the area of the function that iterates through the passed in accounts: -```rust +```rust filename="processor.rs" // Inside add_movie_review msg!("Adding movie review..."); msg!("Title: {}", title); @@ -404,7 +417,6 @@ let account_info_iter = &mut accounts.iter(); let initializer = next_account_info(account_info_iter)?; let pda_account = next_account_info(account_info_iter)?; -let pda_counter = next_account_info(account_info_iter)?; let token_mint = next_account_info(account_info_iter)?; let mint_auth = next_account_info(account_info_iter)?; let user_ata = next_account_info(account_info_iter)?; @@ -416,12 +428,12 @@ There is no additional `instruction_data` required for the new functionality, so no changes need to be made to how data is deserialized. The only additional information that's needed is the extra accounts. -#### 4. Mint tokens to the reviewer in `add_movie_review` +### 4. Mint tokens to the reviewer in add_movie_review Before we dive into the minting logic, let's import the address of the Token program and the constant `LAMPORTS_PER_SOL` at the top of the file. -```rust +```rust filename="processor.rs" // Inside processor.rs use solana_program::native_token::LAMPORTS_PER_SOL; use spl_associated_token_account::get_associated_token_address; @@ -445,9 +457,9 @@ Let's go ahead and derive the token mint and mint authority addresses using the `find_program_address` function with the seeds “token_mint” and "token_auth," respectively. -```rust -// Mint tokens here -msg!("deriving mint authority"); +```rust filename="processor.rs" +// Mint tokens for adding a review +msg!("Deriving mint authority"); let (mint_pda, _mint_bump) = Pubkey::find_program_address(&[b"token_mint"], program_id); let (mint_auth_pda, mint_auth_bump) = Pubkey::find_program_address(&[b"token_auth"], program_id); @@ -456,7 +468,7 @@ let (mint_auth_pda, mint_auth_bump) = Next, we'll perform security checks against each of the new accounts passed into the program. Always remember to verify accounts! -```rust +```rust filename="processor.rs" if *token_mint.key != mint_pda { msg!("Incorrect token mint"); return Err(ReviewError::IncorrectAccountError.into()); @@ -468,7 +480,7 @@ if *mint_auth.key != mint_auth_pda { } if *user_ata.key != get_associated_token_address(initializer.key, token_mint.key) { - msg!("Incorrect token mint"); + msg!("Incorrect associated token account for initializer"); return Err(ReviewError::IncorrectAccountError.into()); } @@ -485,7 +497,7 @@ because it means we don't have to manually build the entire instruction from scratch. Rather, we can simply pass in the arguments required by the function. Here's the function signature: -```rust +```rust filename="processor.rs" // Inside the token program, returns an Instruction object pub fn mint_to( token_program_id: &Pubkey, @@ -501,7 +513,7 @@ Then we provide copies of the `token_mint`, `user_ata`, and `mint_auth` accounts. And, most relevant to this lesson, we provide the seeds used to find the `token_mint` address, including the bump seed. -```rust +```rust filename="processor.rs" msg!("Minting 10 tokens to User associated token account"); invoke_signed( // Instruction @@ -511,7 +523,7 @@ invoke_signed( user_ata.key, mint_auth.key, &[], - 10*LAMPORTS_PER_SOL, + 10 * LAMPORTS_PER_SOL, )?, // Account_infos &[token_mint.clone(), user_ata.clone(), mint_auth.clone()], @@ -532,18 +544,18 @@ If any of the addresses match the derived address, the runtime knows that the matching account is a PDA of this program and that the program is signing this transaction for this account. -At this point, the `add_movie_review` instruction should be fully functional and -will mint ten tokens to the reviewer when a review is created. +At this point, the `add_movie_review` instruction handler should be fully +functional and will mint ten tokens to the reviewer when a review is created. -#### 5. Repeat for `add_comment` +### 5. Repeat for add_comment Our updates to the `add_comment` function will be almost identical to what we did for the `add_movie_review` function above. The only difference is that we'll -change the amount of tokens minted for a comment from ten to five so that adding -reviews are weighted above commenting. First, update the accounts with the same +change the number of tokens minted for comment from ten to five so that adding +reviews is weighted above commenting. First, update the accounts with the same four additional accounts as in the `add_movie_review` function. -```rust +```rust filename="processor.rs" // Inside add_comment let account_info_iter = &mut accounts.iter(); @@ -562,9 +574,9 @@ Next, move to the bottom of the `add_comment` function just before the `Ok(())`. Then derive the token mint and mint authority accounts. Remember, both are PDAs derived from seeds "token_mint" and "token_authority" respectively. -```rust +```rust filename="processor.rs" // Mint tokens here -msg!("deriving mint authority"); +msg!("Deriving mint authority"); let (mint_pda, _mint_bump) = Pubkey::find_program_address(&[b"token_mint"], program_id); let (mint_auth_pda, mint_auth_bump) = Pubkey::find_program_address(&[b"token_auth"], program_id); @@ -572,7 +584,7 @@ let (mint_auth_pda, mint_auth_bump) = Next, verify that each of the new accounts is the correct account. -```rust +```rust filename="processor.rs" if *token_mint.key != mint_pda { msg!("Incorrect token mint"); return Err(ReviewError::IncorrectAccountError.into()); @@ -584,7 +596,7 @@ if *mint_auth.key != mint_auth_pda { } if *user_ata.key != get_associated_token_address(commenter.key, token_mint.key) { - msg!("Incorrect token mint"); + msg!("Incorrect associated token account for commenter"); return Err(ReviewError::IncorrectAccountError.into()); } @@ -597,7 +609,7 @@ if *token_program.key != TOKEN_PROGRAM_ID { Finally, use `invoke_signed` to send the `mint_to` instruction to the Token program, sending five tokens to the commenter. -```rust +```rust filename="processor.rs" msg!("Minting 5 tokens to User associated token account"); invoke_signed( // Instruction @@ -618,7 +630,7 @@ invoke_signed( Ok(()) ``` -#### 6. Set up the token mint +### 6. Set up the token mint We've written all the code needed to mint tokens to reviewers and commenters, but all of it assumes that there is a token mint at the PDA derived with the @@ -631,7 +643,7 @@ concepts associated with PDAs and CPIs multiple times, we're going to walk through this bit with less explanation than the prior steps. Start by adding a fourth instruction variant to the `MovieInstruction` enum in `instruction.rs`. -```rust +```rust filename="instruction.rs" pub enum MovieInstruction { AddMovieReview { title: String, @@ -651,40 +663,44 @@ pub enum MovieInstruction { ``` Be sure to add it to the `match` statement in the `unpack` function in the same -file under the variant `3`. +file under the discriminator `3`. -```rust +```rust filename="instruction.rs" impl MovieInstruction { pub fn unpack(input: &[u8]) -> Result { - let (&variant, rest) = input + let (&discriminator, rest) = input .split_first() .ok_or(ProgramError::InvalidInstructionData)?; - Ok(match variant { + + match discriminator { 0 => { - let payload = MovieReviewPayload::try_from_slice(rest).unwrap(); - Self::AddMovieReview { + let payload = MovieReviewPayload::try_from_slice(rest) + .map_err(|_| ProgramError::InvalidInstructionData)?; + Ok(Self::AddMovieReview { title: payload.title, rating: payload.rating, description: payload.description, - } + }) } 1 => { - let payload = MovieReviewPayload::try_from_slice(rest).unwrap(); - Self::UpdateMovieReview { + let payload = MovieReviewPayload::try_from_slice(rest) + .map_err(|_| ProgramError::InvalidInstructionData)?; + Ok(Self::UpdateMovieReview { title: payload.title, rating: payload.rating, description: payload.description, - } + }) } 2 => { - let payload = CommentPayload::try_from_slice(rest).unwrap(); - Self::AddComment { + let payload = CommentPayload::try_from_slice(rest) + .map_err(|_| ProgramError::InvalidInstructionData)?; + Ok(Self::AddComment { comment: payload.comment, - } + }) } - 3 => Self::InitializeMint, + 3 => Ok(Self::InitializeMint), _ => return Err(ProgramError::InvalidInstructionData), - }) + } } } ``` @@ -693,7 +709,7 @@ In the `process_instruction` function in the `processor.rs` file, add the new instruction to the `match` statement and call a function `initialize_token_mint`. -```rust +```rust filename="processor.rs" pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], @@ -723,9 +739,9 @@ mint account, and then initialize the token mint. We won't explain all of this in detail, but it's worth reading through the code, especially given that the creation and initialization of the token mint both involve CPIs. Again, if you need a refresher on tokens and mints, have a look at the -[Token Program lesson](/content/courses/tokens/token-program). +[Token Program lesson](/content/courses/tokens-and-nfts/token-program.md). -```rust +```rust filename="processor.rs" pub fn initialize_token_mint(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let account_info_iter = &mut accounts.iter(); @@ -797,19 +813,193 @@ pub fn initialize_token_mint(program_id: &Pubkey, accounts: &[AccountInfo]) -> P } ``` -#### 7. Build and deploy +### 7. Build and Deploy Now we're ready to build and deploy our program! You can build the program by -running `cargo build-bpf` and then running the command that is returned, it -should look something like `solana program deploy `. +running `cargo build-sbf`. -Before you can start testing whether or not adding a review or comment sends you -tokens, you need to initialize the program's token mint. You can use -[this script](https://github.com/Unboxed-Software/solana-movie-token-client) to -do that. Once you'd cloned that repository, replace the `PROGRAM_ID` in -`index.ts` with your program's ID. Then run `npm install` and then `npm start`. -The script assumes you're deploying to Devnet. If you're deploying locally, then -make sure to tailor the script accordingly. +```sh +cargo build-sbf +``` + +Then deploy the program by running the `solana program deploy` command. + +```sh +solana program deploy target/deploy/.so +``` + +Upon successful deployment, you'll receive a Program ID. For example: + +```sh +Program Id: AzKatnACpNwQxWRs2YyPovsGhgsYVBiTmC3TL4t72eJW +``` + +If you encounter an "insufficient funds" error during deployment, you may need +to add SOL to your deployment wallet. Use the Solana CLI to request an airdrop: + +```sh +solana airdrop 2 +``` + +After receiving the airdrop, attempt the deployment again. + + + +Ensure your Solana CLI is configured for the correct network (`Localnet`, +`devnet`, `testnet`, or `mainnet-beta`) before deploying or requesting airdrops. + + +If you encounter the following error during program deployment, it indicates +that your program size needs to be extended: + +```sh +Error: Deploying program failed: RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: account data too small for instruction [3 log messages ] +``` + +To resolve this, if you're using Solana CLI version 1.18 or later, run the +following command: + +```sh +solana program extend PROGRAM_ID 20000 -u d -k KEYPAIR_FILE_PATH +``` + +Replace `PROGRAM_ID` and `KEYPAIR_FILE_PATH` with your own values. For example: + +```sh + solana program extend HMDRWmYvL2A9xVKZG8iA1ozxi4gMKiHQz9mFkURKrG4 20000 -u d -k ~/.config/solana/id.json +``` + + + +Ensure you are passing the correct Solana's JSON RPC or moniker URL parameter in +the command. + +```bash +-u, --url URL for Solana's JSON RPC or moniker (or their first letter): [mainnet-beta, testnet, devnet, localhost] +``` + + + +Before testing whether adding a review or comment sends tokens, you need to +initialize the program's token mint. + +First, create and initialize an empty NPM project, then change into the project +directory: + +```bash +mkdir movie-token-client +cd movie-token-client +npm init -y +``` + +Install all the required dependencies. + +```bash +npm i @solana/web3.js @solana-developers/helpers@2.5.2 + +npm i --save-dev esrun +``` + +Create a new file named `initialize-review-token-mint.ts`: + +```bash +touch initialize-review-token-mint.ts +``` + +Copy the code below into the newly created file. + +```typescript filename="initialize-review-token-mint.ts" +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { + Connection, + LAMPORTS_PER_SOL, + PublicKey, + Transaction, + TransactionInstruction, + sendAndConfirmTransaction, + SystemProgram, + SYSVAR_RENT_PUBKEY, +} from "@solana/web3.js"; +import { + initializeKeypair, + airdropIfRequired, + getExplorerLink, +} from "@solana-developers/helpers"; + +const PROGRAM_ID = new PublicKey( + "AzKatnACpNwQxWRs2YyPovsGhgsYVBiTmC3TL4t72eJW", +); + +const LOCALHOST_RPC_URL = "http://localhost:8899"; +const AIRDROP_AMOUNT = 2 * LAMPORTS_PER_SOL; +const MINIMUM_BALANCE_FOR_RENT_EXEMPTION = 1 * LAMPORTS_PER_SOL; + +const connection = new Connection(LOCALHOST_RPC_URL); +const userKeypair = await initializeKeypair(connection); + +await airdropIfRequired( + connection, + userKeypair.publicKey, + AIRDROP_AMOUNT, + MINIMUM_BALANCE_FOR_RENT_EXEMPTION, +); + +const [tokenMintPDA] = PublicKey.findProgramAddressSync( + [Buffer.from("token_mint")], + PROGRAM_ID, +); + +const [tokenAuthPDA] = PublicKey.findProgramAddressSync( + [Buffer.from("token_auth")], + PROGRAM_ID, +); + +const INITIALIZE_MINT_INSTRUCTION = 3; + +const initializeMintInstruction = new TransactionInstruction({ + keys: [ + { pubkey: userKeypair.publicKey, isSigner: true, isWritable: false }, + { pubkey: tokenMintPDA, isSigner: false, isWritable: true }, + { pubkey: tokenAuthPDA, isSigner: false, isWritable: false }, + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, + { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, + { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, + ], + programId: PROGRAM_ID, + data: Buffer.from([INITIALIZE_MINT_INSTRUCTION]), +}); + +const transaction = new Transaction().add(initializeMintInstruction); + +try { + const transactionSignature = await sendAndConfirmTransaction( + connection, + transaction, + [userKeypair], + ); + const explorerLink = getExplorerLink("transaction", transactionSignature); + + console.log(`Transaction submitted: ${explorerLink}`); +} catch (error) { + if (error instanceof Error) { + throw new Error( + `Failed to initialize program token mint: ${error.message}`, + ); + } else { + throw new Error("An unknown error occurred"); + } +} +``` + +Replace `PROGRAM_ID` in `initialize-review-token-mint.ts` with your program ID. +Then run the file with: + +```bash +npx esrun initialize-review-token-mint.ts +``` + +Your token mint will now be initialized. The script assumes you're deploying to +localnet. If you're deploying to devnet, update the script accordingly. Once you've initialized your token mint, you can use the [Movie Review frontend](https://github.com/Unboxed-Software/solana-movie-frontend/tree/solution-add-tokens) @@ -821,9 +1011,8 @@ add a comment, you should receive 5 tokens. They won't have a fancy name or image since we didn't add any metadata to the token, but you get the idea. If you need more time with the concepts from this lesson or got stuck along the -way, feel free to -[take a look at the solution code](https://github.com/Unboxed-Software/solana-movie-program/tree/solution-add-tokens). -Note that the solution to this lab is on the `solution-add-tokens` branch. +way, feel free to take a look at the +[solution code in `solution-add-tokens` branch](https://github.com/solana-developers/movie-program/tree/solution-add-tokens). ## Challenge @@ -841,6 +1030,7 @@ that is possible and you now have the skills and knowledge to go and build something like it on your own! + Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=ade5d386-809f-42c2-80eb-a6c04c471f53)! From 1e885157007dbf3827155f5307e5e9179e22dd7c Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 3 Oct 2024 21:27:54 -0700 Subject: [PATCH 32/54] onchain-development intro-to-anchor updated (#349) * onchain intro-to-onchain updated * some refactors * minor refactors * minor refactors * minor refactors * Delete content/courses/onchain-development/temp.rs * 'instruction handler' and 'instruction' terminology usage fixed --------- Co-authored-by: Mike MacCana --- .../onchain-development/intro-to-anchor.md | 245 ++++++++++++------ 1 file changed, 163 insertions(+), 82 deletions(-) diff --git a/content/courses/onchain-development/intro-to-anchor.md b/content/courses/onchain-development/intro-to-anchor.md index efda0cd27..69a4cb110 100644 --- a/content/courses/onchain-development/intro-to-anchor.md +++ b/content/courses/onchain-development/intro-to-anchor.md @@ -1,7 +1,7 @@ --- title: Intro to Anchor development objectives: - - Use the Anchor framework to build a basic program + - Use the Anchor framework to build a basic Solana program - Describe the basic structure of an Anchor program - Explain how to implement basic account validation and security checks with Anchor @@ -10,8 +10,9 @@ description: "Create your first Solana onchain program in Anchor." ## Summary -- **Programs** on Solana have **instruction handlers** that execute instruction - logic. +- **Programs** on Solana have **instruction handlers**, which are functions that + take arguments from incoming instructions. They are the entry point for any + operation in a program. - **Rust** is the most common language for building Solana programs. The **Anchor** framework takes care of common grunt work - like reading data from incoming instructions, and checking the right accounts are provided - so you @@ -19,15 +20,26 @@ description: "Create your first Solana onchain program in Anchor." ## Lesson -Solana's ability to run arbitrary executable code is part of what makes it so -powerful. Solana programs, similar to "smart contracts" in other blockchain -environments, are quite literally the backbone of the Solana ecosystem. And the -collection of programs grows daily as developers and creators dream up and -deploy new programs. +Before we begin, make sure you have Anchor installed. You can follow this lesson +on [local-setup](/content/onchain-development/local-setup.md). + +Solana's capacity to execute arbitrary code is a key part of its power. Solana +programs, (sometimes called "smart contracts"), are the very foundation of the +Solana ecosystem. And as developers and creators continuously conceive and +deploy new programs, the collection of Solana programs continues to expand +daily. + +Every popular Solana exchange, borrow-lend app, digital art auction house, perps +platform, and prediction market is a program. This lesson will give you a basic introduction to writing and deploying a Solana program using the Rust programming language and the Anchor framework. +> This and the further lessons in this course will give a good base to start +> building Solana programs with Anchor, however if you want to get more into +> Anchor, we would recommend checking out the +> [The Anchor Book](https://book.anchor-lang.com/). + ### What is Anchor? Anchor makes writing Solana programs easier, faster, and more secure, making it @@ -38,27 +50,32 @@ with writing a Solana program. ### Anchor program structure -Anchor uses macros and traits to generate boilerplate Rust code for you. These -provide a clear structure to your program so you can more easily reason about -your code. The main high-level macros and attributes are: +Anchor uses macros and traits to simplify Rust code for you. These provide a +clear structure to your program so you can focus more on its functionality. -- `declare_id` - a macro for declaring the program's onchain address +Some important macros provided by Anchor are: + +> From here on out, you'll see a lot of Rust. We assume that you are familiar +> with Rust, if not, we recommend you to check out +> [The Rust Book](https://doc.rust-lang.org/book/). + +- `declare_id!` - a macro for declaring the program’s onchain address - `#[program]` - an attribute macro used to denote the module containing the - program's instruction logic + program’s instruction handlers. - `Accounts` - a trait applied to structs representing the list of accounts - required for an instruction + required for an instruction. - `#[account]` - an attribute macro used to define custom account types for the - program + program. Let's talk about each of them before putting all the pieces together. ### Declare your program ID -The `declare_id` macro is used to specify the onchain address of the program -(i.e. the `programId`). When you build an Anchor program for the first time, the -framework will generate a new keypair. This becomes the default keypair used to -deploy the program unless specified otherwise. The corresponding public key -should be used as the `programId` specified in the `declare_id!` macro. +The `declare_id` macro sets the onchain address of the Anchor program (i.e. the +`programId`). When you create a new Anchor program, the framework generates a +default keypair. This keypair is used to deploy the program unless specified +otherwise. The public key of this keypair is used as the `programId` in the +`declare_id!` macro. ```rust declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); @@ -67,16 +84,14 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); ### Define instruction logic The `#[program]` attribute macro defines the module containing all of your -program's instructions. This is where you implement the business logic for each -instruction in your program. +program's instruction handlers. This is where you implement the business logic for each +operation in your program. Each public function in the module with the `#[program]` attribute will be -treated as a separate instruction. +treated as a separate instruction handler. -Each instruction function requires a parameter of type `Context` and can -optionally include additional function parameters representing instruction data. -Anchor will automatically handle instruction data deserialization so that you -can work with instruction data as Rust types. +Each instruction handler (function) requires a parameter of type `Context` and can include more parameters as needed. Anchor will automatically handle instruction data +deserialization so that you can work with instruction data as Rust types. ```rust #[program] @@ -90,13 +105,20 @@ mod program_module_name { } ``` -#### Instruction `Context` +- The `#[program]` attribute macro is used to denote the module containing the + program’s instruction logic. +- `use super::*;` is used to bring all the items from the parent module into + scope, which are needed to define the instruction logic. +- Next, there is the instruction handler function. This function just writes + some data (`instruction_data` in this case) to an account. + +### Instruction `Context` The `Context` type exposes instruction metadata and accounts to your instruction logic. ```rust -pub struct Context<'a, 'b, 'c, 'info, T> { +pub struct Context<'a, 'b, 'c, 'info, T: Bumps> { /// Currently executing program id. pub program_id: &'a Pubkey, /// Deserialized accounts. @@ -107,43 +129,55 @@ pub struct Context<'a, 'b, 'c, 'info, T> { /// Bump seeds found during constraint validation. This is provided as a /// convenience so that handlers don't have to recalculate bump seeds or /// pass them in as arguments. - pub bumps: BTreeMap, + /// Type is the bumps struct generated by #[derive(Accounts)] + pub bumps: T::Bumps, } ``` `Context` is a generic type where `T` defines the list of accounts an -instruction requires. When you use `Context`, you specify the concrete type of -`T` as a struct that adopts the `Accounts` trait (e.g. -`Context`). Through this context argument the -instruction can then access: +instruction handler requires. When you use `Context`, you specify the concrete type of +`T` as a struct that adopts the `Accounts` trait. + +The first argument of every instruction handler must be `Context`. `Context` +takes a generic of your `Accounts` struct, eg, if `AddMovieReview` was the +struct holding the accounts, the context for the `add_movie_review()` function +would be `Context`. + + + Yes, the Accounts struct is typically named the same thing as the instruction handler, just in TitleCase. Eg, the struct with the accounts for add_movie_review() is called AddMovieReview! + + +Through this context argument the instruction can then access: - The accounts passed into the instruction (`ctx.accounts`) - The program ID (`ctx.program_id`) of the executing program - The remaining accounts (`ctx.remaining_accounts`). The `remaining_accounts` is - a vector that contains all accounts that were passed into the instruction but + a vector that contains all accounts that were passed into the instruction handler but are not declared in the `Accounts` struct. - The bumps for any PDA accounts in the `Accounts` struct (`ctx.bumps`) +- The seeds for any PDA accounts in tha `Accounts` struct (`ctx.seeds`) + +> The design of Contexts can be different across different programs to serve +> their purpose; and the name of the context could be anything (not limited to +> Context) to better reflect it's usage. This example is to help understand how +> contexts work in Anchor. ### Define instruction accounts -The `Accounts` trait defines a data structure of validated accounts. Structs -adopting this trait define the list of accounts required for a given -instruction. These accounts are then exposed through an instruction's `Context` -so that manual account iteration and deserialization is no longer necessary. +The `Accounts` trait: -You typically apply the `Accounts` trait through the `derive` macro (e.g. -`#[derive(Accounts)]`). This implements an `Accounts` deserializer on the given -struct and removes the need to deserialize each account manually. +- Defines a structure of validated accounts for an instruction handler +- Makes accounts accessible through an instruction handler's `Context` +- Is typically applied using `#[derive(Accounts)]` +- Implements an `Accounts` deserializer on the struct +- Performs constraint checks for secure program execution -Implementations of the `Accounts` trait are responsible for performing all -requisite constraint checks to ensure the accounts meet the conditions required -for the program to run securely. Constraints are provided for each field using -the `#account(..)` attribute (more on that shortly). +Example: -For example, `instruction_one` requires a `Context` argument of type -`InstructionAccounts`. The `#[derive(Accounts)]` macro is used to implement the -`InstructionAccounts` struct which includes three accounts: `account_name`, -`user`, and `system_program`. +- `instruction_one` requires a `Context` +- `InstructionAccounts` struct is implemented with `#[derive(Accounts)]` +- It includes accounts like `account_name`, `user`, and `system_program` +- Constraints are specified using the `#account(..)` attribute ```rust #[program] @@ -156,25 +190,30 @@ mod program_module_name { } #[derive(Accounts)] -pub struct InstructionAccounts { - #[account(init, payer = user, space = 8 + 8)] +pub struct InstructionAccounts<'info> { + #[account( + init, + payer = user, + space = DISCRIMINATOR + AccountStruct::INIT_SPACE + )] pub account_name: Account<'info, AccountStruct>, + #[account(mut)] pub user: Signer<'info>, - pub system_program: Program<'info, System>, + pub system_program: Program<'info, System>, } ``` When `instruction_one` is invoked, the program: -- Checks that the accounts passed into the instruction match the account types +- Checks that the accounts passed into the instruction handler match the account types specified in the `InstructionAccounts` struct - Checks the accounts against any additional constraints specified -If any accounts passed into `instruction_one` fail the account validation or -security checks specified in the `InstructionAccounts` struct, then the -instruction fails before even reaching the program logic. +> If any accounts passed into `instruction_one` fail the account validation or +> security checks specified in the `InstructionAccounts` struct, then the +> instruction fails before even reaching the program logic. ### Account validation @@ -274,7 +313,11 @@ point be sure to look at the full Recall again the `account_name` field from the `InstructionAccounts` example. ```rust -#[account(init, payer = user, space = 8 + 8)] +#[account( + init, + payer = user, + space = DISCRIMINATOR + AccountStruct::INIT_SPACE +)] pub account_name: Account<'info, AccountStruct>, #[account(mut)] pub user: Signer<'info>, @@ -287,10 +330,14 @@ values: it (sets its account discriminator) - `payer` - specifies the payer for the account initialization to be the `user` account defined in the struct -- `space`- specifies that the space allocated for the account should be `8 + 8` - bytes. The first 8 bytes are for a discriminator that Anchor automatically - adds to identify the account type. The next 8 bytes allocate space for the - data stored on the account as defined in the `AccountStruct` type. +- `space`- the space allocated on the blockchain to store the account. + - `DISCRIMINATOR` is the first 8 bytes of an account, which Anchor uses to + save the type of the account. + - `AccountStruct::INIT_SPACE` is the total size of space required for all the + items in the `AccountStruct`. + - The very need of using this `space` constraint can be eliminated by using + `#[derive(InitSpace)]` macro. We'll see how to use that further in this + lesson. For `user` we use the `#[account(..)]` attribute to specify that the given account is mutable. The `user` account must be marked as mutable because @@ -344,15 +391,21 @@ As an example, let's look at `AccountStruct` used by the `account_name` of ```rust #[derive(Accounts)] pub struct InstructionAccounts { - #[account(init, payer = user, space = 8 + 8)] + #[account(init, + payer = user, + space = DISCRIMINATOR + AnchorStruct::INIT_SPACE + )] pub account_name: Account<'info, AccountStruct>, ... } #[account] +#[derive(InitSpace)] pub struct AccountStruct { data: u64 } + +const DISCRIMINATOR: usize = 8; ``` The `#[account]` attribute ensures that it can be used as an account in @@ -360,10 +413,14 @@ The `#[account]` attribute ensures that it can be used as an account in When the `account_name` account is initialized: -- The first 8 bytes is set as the `AccountStruct` discriminator +- The first 8 bytes is set as the `AccountStruct` discriminator using the + `DISCRIMINATOR` constant. - The data field of the account will match `AccountStruct` - The account owner is set as the `programId` from `declare_id` +> It is considered a good practice to use the `#[derive(InitSpace)]` macro which +> makes the code more readable and maintainable. + ### Bring it all together When you combine all of these Anchor types you end up with a complete program. @@ -393,21 +450,37 @@ mod program_module_name { // Validate incoming accounts for instructions #[derive(Accounts)] pub struct InstructionAccounts<'info> { - #[account(init, payer = user, space = 8 + 8)] + #[account(init, + payer = user, + space = DISCRIMINATOR + AccountStruct::INIT_SPACE + )] pub account_name: Account<'info, AccountStruct>, #[account(mut)] pub user: Signer<'info>, pub system_program: Program<'info, System>, - } // Define custom program account type #[account] +#[derive(InitSpace)] pub struct AccountStruct { data: u64 } + +const DISCRIMINATOR: usize = 8; ``` +#### Key takeaways: + +- The whole program structure can be broadly divided into three parts: + 1. Account constraints: define the accounts required for the instructions, as + well as rules to apply to them - e.g., whether they need to sign the + transaction, if they should be created on demand, how addresses for PDAs, + etc. + 2. Instruction handlers: implement program logic, as functions inside + the`#[program]` module. + 3. Accounts: define the format used for data accounts. + You are now ready to build your own Solana program using the Anchor framework! ## Lab @@ -444,7 +517,7 @@ Open the file `lib.rs` and look at `declare_id!`: declare_id!("BouTUP7a3MZLtXqMAm1NrkJSKwAjmid8abqiNjUyBJSr"); ``` -Run `anchor keys sync` +and then run... ```shell anchor keys sync @@ -478,7 +551,6 @@ declare_id!("your-private-key"); #[program] pub mod anchor_counter { use super::*; - } ``` @@ -489,13 +561,17 @@ type. The `Counter` struct defines one `count` field of type `u64`. This means that we can expect any new accounts initialized as a `Counter` type to have a matching data structure. The `#[account]` attribute also automatically sets the discriminator for a new account and sets the owner of the account as the -`programId` from the `declare_id!` macro. +`programId` from the `declare_id!` macro. We also use the `#[derive(InitSpace)]` +macro for convenient space allocation. ```rust #[account] +#[derive(InitSpace)] pub struct Counter { pub count: u64, } + +const DISCRIMINATOR: usize = 8; ``` #### 3. Implement `Context` type `Initialize` @@ -512,7 +588,10 @@ It'll need the following accounts: ```rust #[derive(Accounts)] pub struct Initialize<'info> { - #[account(init, payer = user, space = 8 + 8)] + #[account(init, + payer = user, + space = DISCRIMINATOR + Counter::INIT_SPACE + )] pub counter: Account<'info, Counter>, #[account(mut)] pub user: Signer<'info>, @@ -520,12 +599,11 @@ pub struct Initialize<'info> { } ``` -#### 4. Add the `initialize` instruction +#### 4. Add the `initialize` instruction handler Now that we have our `Counter` account and `Initialize` type , let's implement -the `initialize` instruction within `#[program]`. This instruction requires a -`Context` of type `Initialize` and takes no additional instruction data. In the -instruction logic, we are simply setting the `counter` account's `count` field +the `initialize` instruction handler within `#[program]`. This instruction handler requires a `Context` of type `Initialize` and takes no additional instruction data. +In the instruction logic, we are simply setting the `counter` account's `count` field to `0`. ```rust @@ -541,7 +619,7 @@ pub fn initialize(ctx: Context) -> Result<()> { #### 5. Implement `Context` type `Update` Now, using the `#[derive(Accounts)]` macro again, let's create the `Update` type -that lists the accounts that the `increment` instruction requires. It'll need +that lists the accounts that the `increment` instruction handler requires. It'll need the following accounts: - `counter` - an existing counter account to increment @@ -559,14 +637,11 @@ pub struct Update<'info> { } ``` -#### 6. Add `increment` instruction +#### 6. Add `increment` instruction handler -Lastly, within `#[program]`, let's implement an `increment` instruction to +Lastly, within `#[program]`, let's implement an `increment` instruction handler to increment the `count` once a `counter` account is initialized by the first -instruction. This instruction requires a `Context` of type `Update` (implemented -in the next step) and takes no additional instruction data. In the instruction -logic, we are simply incrementing an existing `counter` account's `count` field -by `1`. +instruction handler. This instruction handler requires a `Context` of type `Update` (implemented in the next step) and takes no additional instruction data. In the instruction logic, we are simply incrementing an existing `counter` account's `count` field by `1`. ```rust pub fn increment(ctx: Context) -> Result<()> { @@ -609,7 +684,10 @@ pub mod anchor_counter { #[derive(Accounts)] pub struct Initialize<'info> { - #[account(init, payer = user, space = 8 + 8)] + #[account(init, + payer = user, + space = DISCRIMINATOR + Counter::INIT_SPACE + )] pub counter: Account<'info, Counter>, #[account(mut)] pub user: Signer<'info>, @@ -624,9 +702,12 @@ pub struct Update<'info> { } #[account] +#[derive(InitSpace)] pub struct Counter { pub count: u64, } + +const DISCRIMINATOR: usize = 8; ``` Run `anchor build` to build the program. From 8de89f25a6ef149e010bd1c2ee12d9e321844478 Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Fri, 4 Oct 2024 11:02:15 +0530 Subject: [PATCH 33/54] Program Optimization - Updated Program Architecture lesson (#485) * Fixed links * Updated content and code snippets * Fixed grammar mistake * Fixed content and formatting as per comments * Added realloc related changes for account resizing * Updated content as per comment and realloc constraint --- .../program-architecture.md | 1577 ++++++++++------- 1 file changed, 985 insertions(+), 592 deletions(-) diff --git a/content/courses/program-optimization/program-architecture.md b/content/courses/program-optimization/program-architecture.md index 805888b71..e8682c8e5 100644 --- a/content/courses/program-optimization/program-architecture.md +++ b/content/courses/program-optimization/program-architecture.md @@ -13,7 +13,7 @@ description: "Design your Solana programs efficiently." - If your data accounts are too large for the Stack, wrap them in `Box` to allocate them to the Heap - Use Zero-Copy to deal with accounts that are too large for `Box` (< 10MB) -- The size and the order of fields in an account matter; put variable length +- The size and the order of fields in an account matter; put the variable length fields at the end - Solana can process in parallel, but you can still run into bottlenecks; be mindful of "shared" accounts that all users interacting with the program have @@ -31,28 +31,29 @@ with the code. And you, as the designer, need to think about: These questions are even more important when developing for a blockchain. Not only are resources more limited than in a typical computing environment, you're -also dealing with people's assets; code has a cost now. +also dealing with people's assets. We'll leave most of the asset handling discussion to -[security course lesson](/content/courses/program-security/security-intro), but -it's important to note the nature of resource limitations in Solana development. -There are, of course, limitations in a typical development environment, but -there are limitations unique to blockchain and Solana development such as how -much data can be stored in an account, the cost to store that data, and how many -compute units are available per transaction. You, the program designer, have to -be mindful of these limitations to create programs that are affordable, fast, -safe, and functional. Today we will be delving into some of the more advance -considerations that should be taken when creating Solana programs. +[security course lesson](/content/courses/program-security/security-intro.md), +but it's important to note the nature of resource limitations in Solana +development. There are, of course, limitations in a typical development +environment, but there are limitations unique to blockchain and Solana +development such as how much data can be stored in an account, the cost to store +that data, and how many compute units are available per transaction. You, the +program designer, have to be mindful of these limitations to create programs +that are affordable, fast, safe, and functional. Today we will be delving into +some of the more advanced considerations that should be taken when creating +Solana programs. ### Dealing With Large Accounts In modern application programming, we don't often have to think about the size -of the data structures we are using. You want to make a string? You can put a -4000 character limit on it if you want to avoid abuse, but it's probably not an +of the data structures we are using. Do you want to make a string? You can put a +4000-character limit on it if you want to avoid abuse, but it's probably not an issue. Want an integer? They're pretty much always 32-bit for convenience. -In high level languages, you are in the data-land-o-plenty! Now, in Solana land, -we pay per byte stored (rent) and have limits on heap, stack and account sizes. +In high-level languages, you are in the data-land-o-plenty! Now, in Solana land, +we pay per byte stored (rent) and have limits on heap, stack, and account sizes. We have to be a little more crafty with our bytes. There are two main concerns we are going to be looking at in this section: @@ -61,31 +62,32 @@ we are going to be looking at in this section: introduce you to the concept of data sizes here. 2. When operating on larger data, we run into - [Stack](https://solana.com/docs/onchain-programs/faq#stack) and - [Heap](https://solana.com/docs/onchain-programs/faq#heap-size) constraints - - to get around these, we'll look at using Box and Zero-Copy. + [Stack](https://solana.com/docs/programs/faq#stack) and + [Heap](https://solana.com/docs/programs/faq#heap-size) constraints - to get + around these, we'll look at using Box and Zero-Copy. #### Sizes -In Solana a transaction's fee payer pays for each byte stored onchain. We call -this [rent](https://solana.com/docs/core/fees). - -rent is a bit of a misnomer since it never actually gets -permanently taken. Once you deposit rent into the account, that data can stay -there forever or you can get refunded the rent if you close the account. Rent -used to be an actual thing, but now there's an enforced minimum rent exemption. -You can read about it in -[the Solana documentation](https://solana.com/docs/intro/rent). - -Rent etymology aside, putting data on the blockchain can be expensive. It's why -NFT attributes and associated files, like the image, are stored offchain. You -ultimately want to strike a balance that leaves your program highly functional -without becoming so expensive that your users don't want to pay to open the data -account. - -The first thing you need to know before you can start optimizing for space in -your program is the size of each of your structs. Below is a very helpful list -from the +In Solana, a transaction's fee payer pays for each byte stored onchain. This is +called [rent](https://solana.com/docs/core/fees#rent). + + + +Rent is a bit of a misnomer since it never gets permanently taken. Once you +deposit rent into the account, that data can stay there forever, or you can get +refunded the rent if you close the account. Previously, rent was paid in +intervals, similar to traditional rent, but now there's an enforced minimum +balance for rent exemption. You can read more about it in +[the Solana documentation](https://solana.com/docs/core/fees#rent-exempt). + + +Putting data on the blockchain can be expensive, which is why NFT attributes and +associated files, like images, are stored offchain. The goal is to strike a +balance between keeping your program highly functional and ensuring that users +aren't discouraged by the cost of storing data onchain. + +The first step in optimizing for space in your program is understanding the size +of your structs. Below is a helpful reference from the [Anchor Book](https://book.anchor-lang.com/anchor_references/space.html). @@ -110,7 +112,7 @@ from the Knowing these, start thinking about little optimizations you might take in a program. For example, if you have an integer field that will only ever reach 100, don't use a u64/i64, use a u8. Why? Because a u64 takes up 8 bytes, with a -max value of 2^64 or 1.84 \* 10^19. Thats a waste of space since you only need +max value of 2^64 or 1.84 \* 10^19. That's a waste of space since you only need to accommodate numbers up to 100. A single byte will give you a max value of 255 which, in this case, would be sufficient. Similarly, there's no reason to use i8 if you'll never have negative numbers. @@ -158,7 +160,9 @@ where that entire `SomeBigDataStruct` gets stored in memory and since 5000 bytes, or 5KB, is greater than the 4KB limit, it will throw a stack error. So how do we fix this? -The answer is the **`Box`** type! +The answer is the +[**`Box`**](https://docs.rs/anchor-lang/latest/anchor_lang/accounts/boxed/index.html) +type! ```rust #[account] @@ -175,14 +179,14 @@ pub struct SomeFunctionContext<'info> { In Anchor, **`Box`** is used to allocate the account to the Heap, not the Stack. Which is great since the Heap gives us 32KB to work with. The best part is you don't have to do anything different within the function. All you need to -do is add `Box<...>` around all of your big data accounts. +do is add `Box<…>` around all of your big data accounts. But Box is not perfect. You can still overflow the stack with sufficiently large accounts. We'll learn how to fix this in the next section. #### Zero Copy -Okay, so now you can deal with medium sized accounts using `Box`. But what if +Okay, so now you can deal with medium-sized accounts using `Box`. But what if you need to use really big accounts like the max size of 10MB? Take the following as an example: @@ -220,13 +224,13 @@ To understand what's happening here, take a look at the [rust Anchor documentation](https://docs.rs/anchor-lang/latest/anchor_lang/attr.account.html) > Other than being more efficient, the most salient benefit [`zero_copy`] -> provides is the ability to define account types larger than the max stack or -> heap size. When using borsh, the account has to be copied and deserialized -> into a new data structure and thus is constrained by stack and heap limits -> imposed by the BPF VM. With zero copy deserialization, all bytes from the -> account's backing `RefCell<&mut [u8]>` are simply re-interpreted as a -> reference to the data structure. No allocations or copies necessary. Hence the -> ability to get around stack and heap limitations. +> provides the ability to define account types larger than the max stack or heap +> size. When using borsh, the account has to be copied and deserialized into a +> new data structure and thus is constrained by stack and heap limits imposed by +> the BPF VM. With zero copy deserialization, all bytes from the account's +> backing `RefCell<&mut [u8]>` are simply re-interpreted as a reference to the +> data structure. No allocations or copies are necessary. Hence the ability to +> get around stack and heap limitations. Basically, your program never actually loads zero-copy account data into the stack or heap. It instead gets pointer access to the raw data. The @@ -244,7 +248,7 @@ pub struct ConceptZeroCopy<'info> { } ``` -Instead, your client has to create the large account and pay for it's rent in a +Instead, your client has to create a large account and pay for its rent in a separate instruction. ```typescript @@ -289,7 +293,7 @@ let some_really_big_data = &mut ctx.accounts.some_really_big_data.load_init()?; After you do that, then you can treat the account like normal! Go ahead and experiment with this in the code yourself to see everything in action! -For a better understanding on how this all works, Solana put together a really +For a better understanding of how this all works, Solana put together a really nice [video](https://www.youtube.com/watch?v=zs_yU0IuJxc&feature=youtu.be) and [code](https://github.com/solana-developers/anchor-zero-copy-example) explaining Box and Zero-Copy in vanilla Solana. @@ -297,8 +301,8 @@ Box and Zero-Copy in vanilla Solana. ### Dealing with Accounts Now that you know the nuts and bolts of space consideration on Solana, let's -look at some higher level considerations. In Solana, everything is an account, -so for the next couple sections we'll look at some account architecture +look at some higher-level considerations. In Solana, everything is an account, +so for the next couple sections, we'll look at some account architecture concepts. #### Data Order @@ -321,16 +325,17 @@ the location of `id` on the memory map. To make this more clear, observe what this account's data looks like onchain when `flags` has four items in the vector vs eight items. If you were to call `solana account ACCOUNT_KEY` you'd get a data dump like the following: +`solana account ACCOUNT_KEY` you'd get a data dump like the following: ```rust 0000: 74 e4 28 4e d9 ec 31 0a -> Account Discriminator (8) -0008: 04 00 00 00 11 22 33 44 -> Vec Size (4) | Data 4*(1) +0008: 04 00 00 00 11 22 33 44 -> Vec Size (4) | Data 4*(1) 0010: DE AD BE EF -> id (4) --- vs --- 0000: 74 e4 28 4e d9 ec 31 0a -> Account Discriminator (8) -0008: 08 00 00 00 11 22 33 44 -> Vec Size (8) | Data 4*(1) +0008: 08 00 00 00 11 22 33 44 -> Vec Size (8) | Data 4*(1) 0010: 55 66 77 88 DE AD BE EF -> Data 4*(1) | id (4) ``` @@ -344,11 +349,10 @@ the data in the `flags` field took up four more bytes. The main problem with this is lookup. When you query Solana, you use filters that look at the raw data of an account. These are called a `memcmp` filters, or memory compare filters. You give the filter an `offset` and `bytes`, and the -filter then looks directly at the memory, offsetting from the start by the -`offset` you provide, and compares the bytes in memory to the `bytes` you -provide. +filter then looks directly at the memory, offset from the start by the `offset` +you provide, and compares the bytes in memory to the `bytes` you provide. -For example, you know that the `flags` struct will always start at address +For example, you know that the `flags` struct will always start at the address 0x0008 since the first 8 bytes contain the account discriminator. Querying all accounts where the `flags` length is equal to four is possible because we _know_ that the four bytes at 0x0008 represent the length of the data in `flags`. Since @@ -368,12 +372,13 @@ const states = await program.account.badState.all([ However, if you wanted to query by the `id`, you wouldn't know what to put for the `offset` since the location of `id` is variable based on the length of `flags`. That doesn't seem very helpful. IDs are usually there to help with +`flags`. That doesn't seem very helpful. IDs are usually there to help with queries! The simple fix is to flip the order. ```rust -#[account] // Anchor hides the account disriminator +#[account] // Anchor hides the account discriminator pub struct GoodState { - pub id: u32 // 0xDEAD_BEEF + pub id: u32 // 0xDEAD_BEEF pub flags: Vec, // 0x11, 0x22, 0x33 ... } ``` @@ -383,83 +388,229 @@ accounts based on all the fields up to the first variable length field. To echo the beginning of this section: As a rule of thumb, keep all variable length structs at the end of the account. -#### For Future Use +#### Account Flexibility and Future-Proofing -In certain cases, consider adding extra, unused bytes to you accounts. These are -held in reserve for flexibility and backward compatibility. Take the following -example: +When developing Solana programs, it's crucial to design your account structures +with future upgrades and backward compatibility in mind. Solana offers powerful +features like account resizing and Anchor's `InitSpace` attribute to handle +these challenges efficiently. Let's explore a more dynamic and flexible approach +using a game state example: ```rust +use anchor_lang::prelude::*; + #[account] -pub struct GameState { +#[derive(InitSpace)] +pub struct GameState { // V1 + pub version: u8, pub health: u64, pub mana: u64, - pub event_log: Vec + pub experience: Option, + #[max_len(50)] + pub event_log: Vec } ``` -In this simple game state, a character has `health`, `mana`, and an event log. -If at some point you are making game improvements and want to add an -`experience` field, you'd hit a snag. The `experience` field should be a number -like a `u64`, which is simple enough to add. You can -[reallocate the account](/developers/courses/onchain-development/anchor-pdas) -and add space. - -However, to keep dynamic length fields, like `event_log`, at the end of the -struct, you would need to do some memory manipulation on all reallocated -accounts to move the location of `event_log`. This can be complicated and makes -querying accounts far more difficult. You'll end up in a state where -non-migrated accounts have `event_log` in one location and migrated accounts in -another. The old `GameState` without `experience` and the new `GameState` with -`experience` in it are no longer compatible. Old accounts won't serialize when -used where new accounts are expected. Queries will be far more difficult. You'll -likely need to create a migration system and ongoing logic to maintain backward -compatibility. Ultimately, it begins to seem like a bad idea. - -Fortunately, if you think ahead, you can add a `for_future_use` field that -reserves some bytes where you expect to need them most. +In this GameState, we have: + +- A `version` field to track account structure changes +- Basic character attributes (`health`, `mana`) +- An `experience` field as `Option` for backward compatibility +- An `event_log` with a specified maximum length + +Key advantages of this approach: + +1. **Automatic Space Calculation**: The `InitSpace` attribute automatically + calculates the required account space. +2. **Versioning**: The `version` field allows for easy identification of account + structure versions. +3. **Flexible Fields**: Using `Option` for new fields maintains compatibility + with older versions. +4. **Defined Limits**: The `max_len` attribute on `Vec` fields clearly + communicates size constraints. + +When you need to upgrade your account structure, such as increasing the length +of `event_log` or adding new fields, you can use a single upgrade instruction +with Anchor's `realloc` constraint: + +1. Update the `GameState` struct with new fields or increased `max_len` + attributes: + + ```rust + #[account] + #[derive(InitSpace)] + pub struct GameState { + pub version: u8, + pub health: u64, + pub mana: u64, + pub experience: Option, + #[max_len(100)] // Increased from 50 + pub event_log: Vec, + pub new_field: Option, // Added new field + } + ``` + +2. Use a single `UpgradeGameState` context for all upgrades with Anchor's + `realloc` constraint for `GameState`: + + ```rust + #[derive(Accounts)] + pub struct UpgradeGameState<'info> { + #[account( + mut, + realloc = GameState::INIT_SPACE, + realloc::payer = payer, + realloc::zero = false, + )] + pub game_state: Account<'info, GameState>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, + } + ``` + +3. Implement the upgrade logic in a single function: + + ```rust + pub fn upgrade_game_state(ctx: Context) -> Result<()> { + let game_state = &mut ctx.accounts.game_state; + + match game_state.version { + 1 => { + game_state.version = 2; + game_state.experience = Some(0); + msg!("Upgraded to version 2"); + }, + 2 => { + game_state.version = 3; + game_state.new_field = Some(0); + msg!("Upgraded to version 3"); + }, + _ => return Err(ErrorCode::AlreadyUpgraded.into()), + } + + Ok(()) + } + ``` + +The example to demonstrate this approach: ```rust +use anchor_lang::prelude::*; + #[account] -pub struct GameState { //V1 +#[derive(InitSpace)] +pub struct GameState { + pub version: u8, pub health: u64, pub mana: u64, - pub for_future_use: [u8; 128], - pub event_log: Vec + pub experience: Option, + #[max_len(100)] // Increased from 50 + pub event_log: Vec, + pub new_field: Option, } -``` -That way, when you go to add `experience` or something similar, it looks like -this and both the old and new accounts are compatible. +#[derive(Accounts)] +pub struct UpgradeGameState<'info> { + #[account( + mut, + realloc = GameState::INIT_SPACE, + realloc::payer = payer, + realloc::zero = false, + )] + pub game_state: Account<'info, GameState>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} -```rust -#[account] -pub struct GameState { //V2 - pub health: u64, - pub mana: u64, - pub experience: u64, - pub for_future_use: [u8; 120], - pub event_log: Vec +#[program] +pub mod your_program { + use super::*; + + // ... other instructions ... + + pub fn upgrade_game_state(ctx: Context) -> Result<()> { + let game_state = &mut ctx.accounts.game_state; + + match game_state.version { + 1 => { + game_state.version = 2; + game_state.experience = Some(0); + msg!("Upgraded to version 2"); + }, + 2 => { + game_state.version = 3; + game_state.new_field = Some(0); + msg!("Upgraded to version 3"); + }, + _ => return Err(ErrorCode::AlreadyUpgraded.into()), + } + + Ok(()) + } +} + +#[error_code] +pub enum ErrorCode { + #[msg("Account is already at the latest version")] + AlreadyUpgraded, } ``` -These extra bytes do add to the cost of using your program. However, it seems -well worth the benefit in most cases. +This approach: + +- Uses the Anchor's + [`realloc`](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html#normal-constraints) + constraint to automatically handle account resizing. +- The + [`InitSpace`](https://docs.rs/anchor-lang/latest/anchor_lang/derive.InitSpace.html) + derive macro automatically implements the `Space` trait for the `GameState` + struct. This trait includes the + [`INIT_SPACE`](https://docs.rs/anchor-lang/latest/anchor_lang/trait.Space.html#associatedconstant.INIT_SPACE) + associated constant , which calculates the total space required for the + account. +- Designates a payer for any additional rent with `realloc::payer = payer`. +- Keeps existing data with `realloc::zero = false`. + + + +Account data can be increased within a single call by up to +`solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE` bytes. -So as a general rule of thumb: anytime you think your account types have the -potential to change in a way that will require some kind of complex migration, -add in some `for_future_use` bytes. +Memory used to grow is already zero-initialized upon program entrypoint and +re-zeroing it wastes compute units. If within the same call a program reallocs +from larger to smaller and back to larger again the new space could contain +stale data. Pass `true` for `zero_init` in this case, otherwise compute units +will be wasted re-zero-initializing. + + + +While account resizing is powerful, use it judiciously. Consider the trade-offs +between frequent resizing and initial allocation based on your specific use case +and expected growth patterns. + +- Always ensure your account remains rent-exempt before resizing. +- The payer of the transaction is responsible for providing the additional + lamports. +- Consider the cost implications of frequent resizing in your program design. + + +In native Rust, you can resize accounts using the `realloc()` method. For more +details, refer to the +[account resizing program](/content/cookbook/programs/change-account-size.md). #### Data Optimization The idea here is to be aware of wasted bits. For example, if you have a field that represents the month of the year, don't use a `u64`. There will only ever +that represents the month of the year, don't use a `u64`. There will only ever be 12 months. Use a `u8`. Better yet, use a `u8` Enum and label the months. To get even more aggressive on bit savings, be careful with booleans. Look at the below struct composed of eight boolean flags. While a boolean _can_ be represented as a single bit, borsh deserialization will allocate an entire byte -to each of these fields. that means that eight booleans winds up being eight +to each of these fields. That means that eight booleans wind up being eight bytes instead of eight bits, an eight times increase in size. ```rust @@ -536,6 +687,10 @@ Depending on the seeding you can create all sorts of relationships: program. For example, if your program needs a lookup table, you could seed it with `seeds=[b"Lookup"]`. Just be careful to provide appropriate access restrictions. +- One-Per-Owner - Say you're creating a video game player account and you only + want one player account per wallet. Then you'd seed the account with + `seeds=[b"PLAYER", owner.key().as_ref()]`. This way, you'll always know where + to look for a wallet's player account **and** there can only ever be one of - One-Per-Owner - Say you're creating a video game player account and you only want one player account per wallet. Then you'd seed the account with `seeds=[b"PLAYER", owner.key().as_ref()]`. This way, you'll always know where @@ -583,7 +738,7 @@ seeds=[b"Podcast", channel_account.key().as_ref(), episode_number.to_be_bytes(). You can always find the channel account for a particular owner. And since the channel stores the number of episodes created, you always know the upper bound -of where to search for queries. Additionally you always know what index to +of where to search for queries. Additionally, you always know what index to create a new episode at: `index = episodes_created`. ```rust @@ -599,24 +754,24 @@ Podcast X: seeds=[b"Podcast", channel_account.key().as_ref(), X.to_be_bytes().as One of the main reasons to choose Solana for your blockchain environment is its parallel transaction execution. That is, Solana can run transactions in parallel as long as those transactions aren't trying to write data to the same account. -This improves program throughput out of the box, but with some proper planning +This improves program throughput out of the box, but with some proper planning, you can avoid concurrency issues and really boost your program's performance. #### Shared Accounts If you've been around crypto for a while, you may have experienced a big NFT -mint event. A new NFT project is coming out, everyone is really excited for it, -and then the candymachine goes live. It's a mad dash to click +mint event. A new NFT project is coming out, everyone is really excited about +it, and then the candymachine goes live. It's a mad dash to click `accept transaction` as fast as you can. If you were clever, you may have -written a bot to enter in the transactions faster that the website's UI could. -This mad rush to mint creates a lot of failed transactions. But why? Because -everyone is trying to write data to the same Candy Machine account. +written a bot to enter the transactions faster than the website's UI could. This +mad rush to mint creates a lot of failed transactions. But why? Because everyone +is trying to write data to the same Candy Machine account. Take a look at a simple example: Alice and Bob are trying to pay their friends Carol and Dean respectively. All -four accounts change, but neither depend on each other. Both transactions can -run at the same time. +four accounts change, but neither depends on other. Both transactions can run at +the same time. ```rust Alice -- pays --> Carol @@ -629,18 +784,18 @@ issues. ```rust Alice -- pays --> | - -- > Carol + -- > Carol Bob -- pays --- | ``` Since both of these transactions write to Carol's token account, only one of -them can go through at a time. Fortunately, Solana is wicked fast, so it'll +them can go through at a time. Fortunately, Solana is very fast, so it'll probably seem like they get paid at the same time. But what happens if more than just Alice and Bob try to pay Carol? ```rust Alice -- pays --> | - -- > Carol + -- > Carol x1000 -- pays --- | Bob -- pays --- | ``` @@ -675,7 +830,7 @@ pub struct DonationTally { } ``` -First let's look at the suboptimal solution. +First, let's look at the suboptimal solution. ```rust pub fn run_concept_shared_account_bottleneck(ctx: Context, lamports_to_donate: u64) -> Result<()> { @@ -708,7 +863,7 @@ pub fn run_concept_shared_account_bottleneck(ctx: ContextThis lab was created with Anchor version `0.28.0` in mind. -If there are problems compiling, please refer to the -[solution code](https://github.com/Unboxed-Software/anchor-rpg/tree/challenge-solution) -for the environment setup. + + +This lab was created with Anchor version `0.30.1` in mind. If there are problems +compiling, please refer to the +[solution code](https://github.com/solana-developers/anchor-rpg/tree/main) for +the environment setup. -Next, replace the program ID in `programs/rpg/lib.rs` and `Anchor.toml` with the -program ID shown when you run `anchor keys list`. +Next, run the command `anchor keys sync` that will automatically sync your +program ID. This command updates the program IDs in your program files +(including `Anchor.toml`) with the actual `pubkey` from the program keypair +file. -Finally, let's scaffold out the program in the `lib.rs` file. To make following -along easier, we're going to keep everything in one file. We'll augment this -with section comments for better organization and navigation. Copy the following +Finally, let's scaffold out the program in the `lib.rs` file. Copy the following into your file before we get started: -```rust +```rust filename="lib.rs" use anchor_lang::prelude::*; -use anchor_lang::system_program::{Transfer, transfer}; use anchor_lang::solana_program::log::sol_log_compute_units; declare_id!("YOUR_KEY_HERE__YOUR_KEY_HERE"); -// ----------- ACCOUNTS ---------- - -// ----------- GAME CONFIG ---------- - -// ----------- STATUS ---------- - -// ----------- INVENTORY ---------- - -// ----------- HELPER ---------- - -// ----------- CREATE GAME ---------- - -// ----------- CREATE PLAYER ---------- +#[program] +pub mod rpg { + use super::*; -// ----------- SPAWN MONSTER ---------- + pub fn create_game(ctx: Context, max_items_per_player: u8) -> Result<()> { + run_create_game(ctx, max_items_per_player)?; + sol_log_compute_units(); + Ok(()) + } -// ----------- ATTACK MONSTER ---------- + pub fn create_player(ctx: Context) -> Result<()> { + run_create_player(ctx)?; + sol_log_compute_units(); + Ok(()) + } -// ----------- REDEEM TO TREASURY ---------- + pub fn spawn_monster(ctx: Context) -> Result<()> { + run_spawn_monster(ctx)?; + sol_log_compute_units(); + Ok(()) + } -#[program] -pub mod rpg { - use super::*; + pub fn attack_monster(ctx: Context) -> Result<()> { + run_attack_monster(ctx)?; + sol_log_compute_units(); + Ok(()) + } + pub fn deposit_action_points(ctx: Context) -> Result<()> { + run_collect_action_points(ctx)?; + sol_log_compute_units(); + Ok(()) + } } ``` -#### 2. Create Account Structures +### 2. Create Account Structures Now that our initial setup is ready, let's create our accounts. We'll have 3: @@ -903,7 +1067,6 @@ Now that our initial setup is ready, let's create our accounts. We'll have 3: - `experience` - the player's experience - `kills` - number of monsters killed - `next_monster_index` - the index of the next monster to face - - `for_future_use` - 256 bytes reserved for future use - `inventory` - a vector of the player's inventory 3. `Monster` - A PDA account whose address is derived using the game account address, the player's wallet address, and an index (the one stored as @@ -912,21 +1075,55 @@ Now that our initial setup is ready, let's create our accounts. We'll have 3: - `game` - the game the monster is associated with - `hitpoints` - how many hit points the monster has left +This is the final project structure: + +```bash +src/ +├── constants.rs # Constants used throughout the program +├── error/ # Error module +│ ├── errors.rs # Custom error definitions +│ └── mod.rs # Module declarations for error handling +├── helpers.rs # Helper functions used across the program +├── instructions/ # Instruction handlers for different game actions +│ ├── attack_monster.rs # Handles attacking a monster +│ ├── collect_points.rs # Handles collecting points +│ ├── create_game.rs # Handles game creation +│ ├── create_player.rs # Handles player creation +│ ├── mod.rs # Module declarations for instructions +│ └── spawn_monster.rs # Handles spawning a new monster +├── lib.rs # Main entry point for the program +└── state/ # State module for game data structures + ├── game.rs # Game state representation + ├── mod.rs # Module declarations for state + ├── monster.rs # Monster state representation + └── player.rs # Player state representation +``` + When added to the program, the accounts should look like this: ```rust // ----------- ACCOUNTS ---------- -#[account] -pub struct Game { // 8 bytes - pub game_master: Pubkey, // 32 bytes - pub treasury: Pubkey, // 32 bytes - - pub action_points_collected: u64, // 8 bytes +// Inside `state/game.rs` +use anchor_lang::prelude::*; +#[account] +#[derive(InitSpace)] +pub struct Game { + pub game_master: Pubkey, + pub treasury: Pubkey, + pub action_points_collected: u64, pub game_config: GameConfig, } +#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace)] +pub struct GameConfig { + pub max_items_per_player: u8 +} + +// Inside `state/player.rs` +use anchor_lang::prelude::*; #[account] +#[derive(InitSpace)] pub struct Player { // 8 bytes pub player: Pubkey, // 32 bytes pub game: Pubkey, // 32 bytes @@ -939,92 +1136,125 @@ pub struct Player { // 8 bytes pub kills: u64, // 8 bytes pub next_monster_index: u64, // 8 bytes - pub for_future_use: [u8; 256], // Attack/Speed/Defense/Health/Mana?? Metadata?? - pub inventory: Vec, // Max 8 items } -#[account] -pub struct Monster { // 8 bytes - pub player: Pubkey, // 32 bytes - pub game: Pubkey, // 32 bytes +#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace)] +pub struct InventoryItem { + pub name: [u8; 32], // Fixed Name up to 32 bytes + pub amount: u64 +} + - pub hitpoints: u64, // 8 bytes +// Inside `state/monster.rs` +use anchor_lang::prelude::*; +#[account] +#[derive(InitSpace)] +pub struct Monster { + pub player: Pubkey, + pub game: Pubkey, + pub hitpoints: u64, } ``` There aren't a lot of complicated design decisions here, but let's talk about -the `inventory` and `for_future_use` fields on the `Player` struct. Since -`inventory` is variable in length we decided to place it at the end of the -account to make querying easier. We've also decided it's worth spending a little -extra money on rent exemption to have 256 bytes of reserved space in the -`for_future_use` field. We could exclude this and simply reallocate accounts if -we need to add fields in the future, but adding it now simplifies things for us -in the future. - -If we chose to reallocate in the future, we'd need to write more complicated -queries and likely couldn't query in a single call based on `inventory`. -Reallocating and adding a field would move the memory position of `inventory`, -leaving us to write complex logic to query accounts with various structures. +the `inventory` field on the `Player` struct. Since `inventory` is variable in +length we decided to place it at the end of the account to make querying easier. -#### 3. Create ancillary types +### 3. Create Ancillary Types The next thing we need to do is add some of the types our accounts reference that we haven't created yet. Let's start with the game config struct. Technically, this could have gone in the `Game` account, but it's nice to have some separation and encapsulation. -This struct should store the max items allowed per player and some bytes for -future use. Again, the bytes for future use here help us avoid complexity in the -future. Reallocating accounts works best when you're adding fields at the end of -an account rather than in the middle. If you anticipate adding fields in the -middle of existing date, it might make sense to add some "future use" bytes up -front. +This struct should store the max items allowed per player. -```rust +```rust filename="game.rs" // ----------- GAME CONFIG ---------- - -#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +// Inside `state/game.rs` +#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace)] pub struct GameConfig { - pub max_items_per_player: u8, - pub for_future_use: [u64; 16], // Health of Enemies?? Experience per item?? Action Points per Action?? + pub max_items_per_player: u8 } ``` +Reallocating accounts in Solana programs has become more flexible due to +Anchor's +[`realloc`](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html#normal-constraints) +account constraint and Solana's account resizing capabilities. While adding +fields at the end of an account structure remains straightforward, modern +practices allow for more adaptable designs: + +1. Use Anchor's `realloc` constraint in the `#[account()]` attribute to specify + resizing parameters: + + ```rust + #[account( + mut, + realloc = AccountStruct::INIT_SPACE, + realloc::payer = payer, + realloc::zero = false, + )] + ``` + +2. Use Anchor's `InitSpace` attribute to automatically calculate account space. +3. For variable-length fields like `Vec` or `String`, use the `max_len` + attribute to specify maximum size. +4. When adding new fields, consider using `Option` for backward + compatibility. +5. Implement a versioning system in your account structure to manage different + layouts. +6. Ensure the payer account is mutable and a signer to cover reallocation costs: + + ```rust + #[account(mut)] + pub payer: Signer<'info>, + ``` + +This approach allows for easier account structure evolution, regardless of where +new fields are added, while maintaining efficient querying and +serialization/deserialization through Anchor's built-in capabilities. It enables +resizing accounts as needed, automatically handling rent-exemption. + Next, let's create our status flags. Remember, we _could_ store our flags as booleans but we save space by storing multiple flags in a single byte. Each flag takes up a different bit within the byte. We can use the `<<` operator to place `1` in the correct bit. -```rust +```rust filename="constants.rs" // ----------- STATUS ---------- -const IS_FROZEN_FLAG: u8 = 1 << 0; -const IS_POISONED_FLAG: u8 = 1 << 1; -const IS_BURNING_FLAG: u8 = 1 << 2; -const IS_BLESSED_FLAG: u8 = 1 << 3; -const IS_CURSED_FLAG: u8 = 1 << 4; -const IS_STUNNED_FLAG: u8 = 1 << 5; -const IS_SLOWED_FLAG: u8 = 1 << 6; -const IS_BLEEDING_FLAG: u8 = 1 << 7; -const NO_EFFECT_FLAG: u8 = 0b00000000; +pub const IS_FROZEN_FLAG: u8 = 1 << 0; +pub const IS_POISONED_FLAG: u8 = 1 << 1; +pub const IS_BURNING_FLAG: u8 = 1 << 2; +pub const IS_BLESSED_FLAG: u8 = 1 << 3; +pub const IS_CURSED_FLAG: u8 = 1 << 4; +pub const IS_STUNNED_FLAG: u8 = 1 << 5; +pub const IS_SLOWED_FLAG: u8 = 1 << 6; +pub const IS_BLEEDING_FLAG: u8 = 1 << 7; + +pub const NO_EFFECT_FLAG: u8 = 0b00000000; +pub const ANCHOR_DISCRIMINATOR: usize = 8; +pub const MAX_INVENTORY_ITEMS: usize = 8; ``` Finally, let's create our `InventoryItem`. This should have fields for the -item's name, amount, and some bytes reserved for future use. +item's name and amount. -```rust +```rust filename="player.rs" // ----------- INVENTORY ---------- -#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +// Inside `state/player.rs` +#[derive(AnchorSerialize, AnchorDeserialize, Clone, InitSpace)] pub struct InventoryItem { pub name: [u8; 32], // Fixed Name up to 32 bytes - pub amount: u64, - pub for_future_use: [u8; 128], // Metadata?? // Effects // Flags? + pub amount: u64 } + ``` -#### 4. Create helper function for spending action points +### 4. Create a helper function for spending action points The last thing we'll do before writing the program's instructions is create a helper function for spending action points. Players will send action points @@ -1040,26 +1270,40 @@ that will send the lamports from that account to the treasury in one fell swoop. This alleviates any concurrency issues since every player has their own account, but also allows the program to retrieve those lamports at any time. -```rust +```rust filename="helper.rs" // ----------- HELPER ---------- +// Inside /src/helpers.rs +use anchor_lang::{prelude::*, system_program}; + +use crate::{error::RpgError, Player}; + pub fn spend_action_points<'info>( action_points: u64, player_account: &mut Account<'info, Player>, player: &AccountInfo<'info>, system_program: &AccountInfo<'info>, ) -> Result<()> { - - player_account.action_points_spent = player_account.action_points_spent.checked_add(action_points).unwrap(); - player_account.action_points_to_be_collected = player_account.action_points_to_be_collected.checked_add(action_points).unwrap(); - - let cpi_context = CpiContext::new( - system_program.clone(), - Transfer { - from: player.clone(), - to: player_account.to_account_info().clone(), - }); - transfer(cpi_context, action_points)?; + player_account.action_points_spent = player_account + .action_points_spent + .checked_add(action_points) + .ok_or(error!(RpgError::ArithmeticOverflow))?; + + player_account.action_points_to_be_collected = player_account + .action_points_to_be_collected + .checked_add(action_points) + .ok_or(error!(RpgError::ArithmeticOverflow))?; + + system_program::transfer( + CpiContext::new( + system_program.to_account_info(), + system_program::Transfer { + from: player.to_account_info(), + to: player_account.to_account_info(), + }, + ), + action_points, + )?; msg!("Minus {} action points", action_points); @@ -1067,7 +1311,7 @@ pub fn spend_action_points<'info>( } ``` -#### 5. Create Game +### 5. Create Game Our first instruction will create the `game` account. Anyone can be a `game_master` and create their own game, but once a game has been created there @@ -1077,48 +1321,54 @@ For one, the `game` account is a PDA using its `treasury` wallet. This ensures that the same `game_master` can run multiple games if they use a different treasury for each. -Also note that the `treasury` is a signer on the instruction. This is to make -sure whoever is creating the game has the private keys to the `treasury`. This -is a design decision rather than "the right way." Ultimately, it's a security -measure to ensure the game master will be able to retrieve their funds. + -```rust +The `treasury` is a signer on the instruction. This is to make sure whoever is +creating the game has the private keys to the `treasury`. This is a design +decision rather than "the right way." Ultimately, it's a security measure to +ensure the game master will be able to retrieve their funds. + +```rust filename="create_game.rs" // ----------- CREATE GAME ---------- +// Inside src/instructions/create_game.rs +use anchor_lang::prelude::*; + +use crate::{error::RpgError, Game, ANCHOR_DISCRIMINATOR}; + #[derive(Accounts)] pub struct CreateGame<'info> { #[account( init, - seeds=[b"GAME", treasury.key().as_ref()], + seeds = [b"GAME", treasury.key().as_ref()], bump, payer = game_master, - space = std::mem::size_of::()+ 8 + space = ANCHOR_DISCRIMINATOR + Game::INIT_SPACE )] pub game: Account<'info, Game>, - #[account(mut)] pub game_master: Signer<'info>, - - /// CHECK: Need to know they own the treasury pub treasury: Signer<'info>, pub system_program: Program<'info, System>, } pub fn run_create_game(ctx: Context, max_items_per_player: u8) -> Result<()> { + if max_items_per_player == 0 { + return Err(error!(RpgError::InvalidGameConfig)); + } - ctx.accounts.game.game_master = ctx.accounts.game_master.key().clone(); - ctx.accounts.game.treasury = ctx.accounts.treasury.key().clone(); - - ctx.accounts.game.action_points_collected = 0; - ctx.accounts.game.game_config.max_items_per_player = max_items_per_player; + let game = &mut ctx.accounts.game; + game.game_master = ctx.accounts.game_master.key(); + game.treasury = ctx.accounts.treasury.key(); + game.action_points_collected = 0; + game.game_config.max_items_per_player = max_items_per_player; msg!("Game created!"); - Ok(()) } ``` -#### 6. Create Player +### 6. Create Player Our second instruction will create the `player` account. There are three tradeoffs to note about this instruction: @@ -1133,62 +1383,67 @@ tradeoffs to note about this instruction: 100 lamports, but this could be something added to the game config in the future. -```rust +```rust filename="create_player.rs" // ----------- CREATE PLAYER ---------- + +// Inside src/instructions/create_player.rs +use anchor_lang::prelude::*; + +use crate::{ + error::RpgError, helpers::spend_action_points, Game, Player, ANCHOR_DISCRIMINATOR, + CREATE_PLAYER_ACTION_POINTS, NO_EFFECT_FLAG, +}; + #[derive(Accounts)] pub struct CreatePlayer<'info> { pub game: Box>, - #[account( init, - seeds=[ + seeds = [ b"PLAYER", game.key().as_ref(), player.key().as_ref() ], bump, payer = player, - space = std::mem::size_of::() + std::mem::size_of::() * game.game_config.max_items_per_player as usize + 8) - ] + space = ANCHOR_DISCRIMINATOR + Player::INIT_SPACE + )] pub player_account: Account<'info, Player>, - #[account(mut)] pub player: Signer<'info>, - pub system_program: Program<'info, System>, } pub fn run_create_player(ctx: Context) -> Result<()> { - - ctx.accounts.player_account.player = ctx.accounts.player.key().clone(); - ctx.accounts.player_account.game = ctx.accounts.game.key().clone(); - - ctx.accounts.player_account.status_flag = NO_EFFECT_FLAG; - ctx.accounts.player_account.experience = 0; - ctx.accounts.player_account.kills = 0; + let player_account = &mut ctx.accounts.player_account; + player_account.player = ctx.accounts.player.key(); + player_account.game = ctx.accounts.game.key(); + player_account.status_flag = NO_EFFECT_FLAG; + player_account.experience = 0; + player_account.kills = 0; msg!("Hero has entered the game!"); - { // Spend 100 lamports to create player - let action_points_to_spend = 100; + // Spend 100 lamports to create player + let action_points_to_spend = CREATE_PLAYER_ACTION_POINTS; - spend_action_points( - action_points_to_spend, - &mut ctx.accounts.player_account, - &ctx.accounts.player.to_account_info(), - &ctx.accounts.system_program.to_account_info() - )?; - } + spend_action_points( + action_points_to_spend, + player_account, + &ctx.accounts.player.to_account_info(), + &ctx.accounts.system_program.to_account_info(), + ) + .map_err(|_| error!(RpgError::InsufficientActionPoints))?; Ok(()) } ``` -#### 7. Spawn Monster +### 7. Spawn Monster Now that we have a way to create players, we need a way to spawn monsters for them to fight. This instruction will create a new `Monster` account whose -address is a PDA derived with the `game` account, `player` account, and an index +address is a PDA derived from the `game` account, `player` account, and an index representing the number of monsters the player has faced. There are two design decisions here we should talk about: @@ -1196,21 +1451,26 @@ decisions here we should talk about: 2. We wrap both the `game` and `player` accounts in `Box` to allocate them to the Heap -```rust +```rust filename="spawn_monster.rs" // ----------- SPAWN MONSTER ---------- + +// Inside src/instructions/spawn_monster.rs +use anchor_lang::prelude::*; + +use crate::{helpers::spend_action_points, Game, Monster, Player, SPAWN_MONSTER_ACTION_POINTS, ANCHOR_DISCRIMINATOR}; + #[derive(Accounts)] pub struct SpawnMonster<'info> { pub game: Box>, - - #[account(mut, + #[account( + mut, has_one = game, has_one = player, )] pub player_account: Box>, - #[account( init, - seeds=[ + seeds = [ b"MONSTER", game.key().as_ref(), player.key().as_ref(), @@ -1218,46 +1478,39 @@ pub struct SpawnMonster<'info> { ], bump, payer = player, - space = std::mem::size_of::() + 8) - ] + space = ANCHOR_DISCRIMINATOR + Monster::INIT_SPACE + )] pub monster: Account<'info, Monster>, - #[account(mut)] pub player: Signer<'info>, - pub system_program: Program<'info, System>, } pub fn run_spawn_monster(ctx: Context) -> Result<()> { + let monster = &mut ctx.accounts.monster; + monster.player = ctx.accounts.player.key(); + monster.game = ctx.accounts.game.key(); + monster.hitpoints = 100; - { - ctx.accounts.monster.player = ctx.accounts.player.key().clone(); - ctx.accounts.monster.game = ctx.accounts.game.key().clone(); - ctx.accounts.monster.hitpoints = 100; + let player_account = &mut ctx.accounts.player_account; + player_account.next_monster_index = player_account.next_monster_index.checked_add(1).unwrap(); - msg!("Monster Spawned!"); - } - - { - ctx.accounts.player_account.next_monster_index = ctx.accounts.player_account.next_monster_index.checked_add(1).unwrap(); - } + msg!("Monster Spawned!"); - { // Spend 5 lamports to spawn monster - let action_point_to_spend = 5; - - spend_action_points( - action_point_to_spend, - &mut ctx.accounts.player_account, - &ctx.accounts.player.to_account_info(), - &ctx.accounts.system_program.to_account_info() - )?; - } + // Spend 5 lamports to spawn monster + let action_point_to_spend = SPAWN_MONSTER_ACTION_POINTS; + spend_action_points( + action_point_to_spend, + player_account, + &ctx.accounts.player.to_account_info(), + &ctx.accounts.system_program.to_account_info(), + )?; Ok(()) } ``` -#### 8. Attack Monster +### 8. Attack Monster Now! Let's attack those monsters and start gaining some exp! @@ -1274,138 +1527,235 @@ The `saturating_add` function ensures the number will never overflow. Say the `kills` was a u8 and my current kill count was 255 (0xFF). If I killed another and added normally, e.g. `255 + 1 = 0 (0xFF + 0x01 = 0x00) = 0`, the kill count would end up as 0. `saturating_add` will keep it at its max if it's about to +would end up as 0. `saturating_add` will keep it at its max if it's about to roll over, so `255 + 1 = 255`. The `checked_add` function will throw an error if it's about to overflow. Keep this in mind when doing math in Rust. Even though `kills` is a u64 and will never roll with it's current programming, it's good +it's about to overflow. Keep this in mind when doing math in Rust. Even though +`kills` is a u64 and will never roll with it's current programming, it's good practice to use safe math and consider roll-overs. -```rust +```rust filename="attack_monster.rs" // ----------- ATTACK MONSTER ---------- + +// Inside src/instructions/attack_monster.rs +use anchor_lang::prelude::*; +use crate::{helpers::spend_action_points, Monster, Player, ATTACK_ACTION_POINTS, error::RpgError}; + #[derive(Accounts)] pub struct AttackMonster<'info> { - #[account( mut, has_one = player, )] pub player_account: Box>, - #[account( mut, has_one = player, - constraint = monster.game == player_account.game + constraint = monster.game == player_account.game @ RpgError::GameMismatch )] pub monster: Box>, - #[account(mut)] pub player: Signer<'info>, - pub system_program: Program<'info, System>, } pub fn run_attack_monster(ctx: Context) -> Result<()> { + let player_account = &mut ctx.accounts.player_account; + let monster = &mut ctx.accounts.monster; - let mut did_kill = false; - - { - let hp_before_attack = ctx.accounts.monster.hitpoints; - let hp_after_attack = ctx.accounts.monster.hitpoints.saturating_sub(1); - let damage_dealt = hp_before_attack - hp_after_attack; - ctx.accounts.monster.hitpoints = hp_after_attack; - + let hp_before_attack = monster.hitpoints; + let hp_after_attack = monster.hitpoints.saturating_sub(1); + let damage_dealt = hp_before_attack.saturating_sub(hp_after_attack); + monster.hitpoints = hp_after_attack; - - if hp_before_attack > 0 && hp_after_attack == 0 { - did_kill = true; - } - - if damage_dealt > 0 { - msg!("Damage Dealt: {}", damage_dealt); - } else { - msg!("Stop it's already dead!"); - } - } - - { - ctx.accounts.player_account.experience = ctx.accounts.player_account.experience.saturating_add(1); + if damage_dealt > 0 { + msg!("Damage Dealt: {}", damage_dealt); + player_account.experience = player_account.experience.saturating_add(1); msg!("+1 EXP"); - if did_kill { - ctx.accounts.player_account.kills = ctx.accounts.player_account.kills.saturating_add(1); + if hp_after_attack == 0 { + player_account.kills = player_account.kills.saturating_add(1); msg!("You killed the monster!"); } + } else { + msg!("Stop it's already dead!"); } - { // Spend 1 lamports to attack monster - let action_point_to_spend = 1; + // Spend 1 lamport to attack monster + let action_point_to_spend = ATTACK_ACTION_POINTS; - spend_action_points( - action_point_to_spend, - &mut ctx.accounts.player_account, - &ctx.accounts.player.to_account_info(), - &ctx.accounts.system_program.to_account_info() - )?; - } + spend_action_points( + action_point_to_spend, + player_account, + &ctx.accounts.player.to_account_info(), + &ctx.accounts.system_program.to_account_info() + )?; Ok(()) } ``` -#### Redeem to Treasury +### 9. Redeem to Treasury This is our last instruction. This instruction lets anyone send the spent `action_points` to the `treasury` wallet. Again, let's box the rpg accounts and use safe math. -```rust +```rust filename="collect_points.rs" // ----------- REDEEM TO TREASUREY ---------- + +// Inside src/instructions/collect_points.rs +use anchor_lang::prelude::*; +use crate::{error::RpgError, Game, Player}; + #[derive(Accounts)] pub struct CollectActionPoints<'info> { - #[account( mut, - has_one=treasury + has_one = treasury @ RpgError::InvalidTreasury )] pub game: Box>, - #[account( mut, - has_one=game + has_one = game @ RpgError::PlayerGameMismatch )] pub player: Box>, - #[account(mut)] /// CHECK: It's being checked in the game account - pub treasury: AccountInfo<'info>, - + pub treasury: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } -// literally anyone who pays for the TX fee can run this command - give it to a clockwork bot +// Literally anyone who pays for the TX fee can run this command - give it to a clockwork bot pub fn run_collect_action_points(ctx: Context) -> Result<()> { - let transfer_amount: u64 = ctx.accounts.player.action_points_to_be_collected; + let transfer_amount = ctx.accounts.player.action_points_to_be_collected; + + // Transfer lamports from player to treasury + let player_info = ctx.accounts.player.to_account_info(); + let treasury_info = ctx.accounts.treasury.to_account_info(); + + **player_info.try_borrow_mut_lamports()? = player_info + .lamports() + .checked_sub(transfer_amount) + .ok_or(RpgError::InsufficientFunds)?; - **ctx.accounts.player.to_account_info().try_borrow_mut_lamports()? -= transfer_amount; - **ctx.accounts.treasury.to_account_info().try_borrow_mut_lamports()? += transfer_amount; + **treasury_info.try_borrow_mut_lamports()? = treasury_info + .lamports() + .checked_add(transfer_amount) + .ok_or(RpgError::ArithmeticOverflow)?; ctx.accounts.player.action_points_to_be_collected = 0; - ctx.accounts.game.action_points_collected = ctx.accounts.game.action_points_collected.checked_add(transfer_amount).unwrap(); + ctx.accounts.game.action_points_collected = ctx.accounts.game + .action_points_collected + .checked_add(transfer_amount) + .ok_or(RpgError::ArithmeticOverflow)?; - msg!("The treasury collected {} action points to treasury", transfer_amount); + msg!("The treasury collected {} action points", transfer_amount); Ok(()) } ``` -#### Putting it all Together +### 10. Error Handling + +Now, let's add all the errors that we have used till now in `errors.rs` file. + +```rust filename="errors.rs" +// ------------RPG ERRORS-------------- + +// Inside src/error/errors.rs + +use anchor_lang::prelude::*; + +#[error_code] +pub enum RpgError { + #[msg("Arithmetic overflow occurred")] + ArithmeticOverflow, + #[msg("Invalid game configuration")] + InvalidGameConfig, + #[msg("Player not found")] + PlayerNotFound, + #[msg("Monster not found")] + MonsterNotFound, + #[msg("Insufficient action points")] + InsufficientActionPoints, + #[msg("Invalid attack")] + InvalidAttack, + #[msg("Maximum inventory size reached")] + MaxInventoryReached, + #[msg("Invalid item operation")] + InvalidItemOperation, + #[msg("Monster and player are not in the same game")] + GameMismatch, + #[msg("Invalid treasury account")] + InvalidTreasury, + #[msg("Player does not belong to the specified game")] + PlayerGameMismatch, + #[msg("Insufficient funds for transfer")] + InsufficientFunds +} +``` + +### 11. Module Declarations + +We need to declare all the modules used in the project as follows: + +```rust + +// Inside src/error/mod.rs +pub mod errors; +pub use errors::RpgError; // Expose the custom error type + +// Inside src/instructions/mod.rs +pub mod attack_monster; +pub mod collect_points; +pub mod create_game; +pub mod create_player; +pub mod spawn_monster; + +pub use attack_monster::*; // Expose attack_monster functions +pub use collect_points::*; // Expose collect_points functions +pub use create_game::*; // Expose create_game functions +pub use create_player::*; // Expose create_player functions +pub use spawn_monster::*; // Expose spawn_monster functions + +// Inside src/state/mod.rs +pub mod game; +pub mod monster; +pub mod player; + +pub use game::*; // Expose game state +pub use monster::*; // Expose monster state +pub use player::*; // Expose player state +``` + +### 12. Putting it all Together Now that all of our instruction logic is written, let's add these functions to actual instructions in the program. It can also be helpful to log compute units for each instruction. -```rust +```rust filename="lib.rs" + +// Insider src/lib.rs +use anchor_lang::prelude::*; +use anchor_lang::solana_program::log::sol_log_compute_units; + +mod state; +mod instructions; +mod constants; +mod helpers; +mod error; + +use state::*; +use constants::*; +use instructions::*; + +declare_id!("5Sc3gJv4tvPiFzE75boYMJabbNRs44zRhtT23fLdKewz"); + #[program] pub mod rpg { use super::*; @@ -1439,7 +1789,6 @@ pub mod rpg { sol_log_compute_units(); Ok(()) } - } ``` @@ -1450,73 +1799,134 @@ successfully. anchor build ``` -#### Testing +### Testing -Now, let's see this baby work! +Now, let's put everything together and see it in action! -Let's set up the `tests/rpg.ts` file. We will be filling out each test in turn. -But first, we needed to set up a couple of different accounts. Mainly the -`gameMaster` and the `treasury`. +We'll begin by setting up the `tests/rpg.ts` file. We will be writing each test +step by step. But before diving into the tests, we need to initialize a few +important accounts, specifically the `gameMaster` and the `treasury` accounts. -```typescript +```typescript filename="rpg.ts" import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; -import { Rpg, IDL } from "../target/types/rpg"; +import { Rpg } from "../target/types/rpg"; import { assert } from "chai"; +import { + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + TransactionSignature, + TransactionConfirmationStrategy, +} from "@solana/web3.js"; import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; -describe("RPG", () => { - // Configure the client to use the local cluster. - anchor.setProvider(anchor.AnchorProvider.env()); - - const program = anchor.workspace.Rpg as Program; - const wallet = anchor.workspace.Rpg.provider.wallet - .payer as anchor.web3.Keypair; - const gameMaster = wallet; - const player = wallet; - - const treasury = anchor.web3.Keypair.generate(); - - it("Create Game", async () => {}); - - it("Create Player", async () => {}); - - it("Spawn Monster", async () => {}); - - it("Attack Monster", async () => {}); - - it("Deposit Action Points", async () => {}); +const GAME_SEED = "GAME"; +const PLAYER_SEED = "PLAYER"; +const MONSTER_SEED = "MONSTER"; +const MAX_ITEMS_PER_PLAYER = 8; +const INITIAL_MONSTER_HITPOINTS = 100; +const AIRDROP_AMOUNT = 10 * LAMPORTS_PER_SOL; +const CREATE_PLAYER_ACTION_POINTS = 100; +const SPAWN_MONSTER_ACTION_POINTS = 5; +const ATTACK_MONSTER_ACTION_POINTS = 1; +const MONSTER_INDEX_BYTE_LENGTH = 8; + +const provider = anchor.AnchorProvider.env(); +anchor.setProvider(provider); + +const program = anchor.workspace.Rpg as Program; +const wallet = provider.wallet as NodeWallet; +const gameMaster = wallet; +const player = wallet; + +const treasury = Keypair.generate(); + +const findProgramAddress = (seeds: Buffer[]): [PublicKey, number] => + PublicKey.findProgramAddressSync(seeds, program.programId); + +const confirmTransaction = async ( + signature: TransactionSignature, + provider: anchor.Provider, +) => { + const latestBlockhash = await provider.connection.getLatestBlockhash(); + const confirmationStrategy: TransactionConfirmationStrategy = { + signature, + blockhash: latestBlockhash.blockhash, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }; + + try { + const confirmation = + await provider.connection.confirmTransaction(confirmationStrategy); + if (confirmation.value.err) { + throw new Error( + `Transaction failed: ${confirmation.value.err.toString()}`, + ); + } + } catch (error) { + throw new Error(`Transaction confirmation failed: ${error.message}`); + } +}; + +const createGameAddress = () => + findProgramAddress([Buffer.from(GAME_SEED), treasury.publicKey.toBuffer()]); + +const createPlayerAddress = (gameAddress: PublicKey) => + findProgramAddress([ + Buffer.from(PLAYER_SEED), + gameAddress.toBuffer(), + player.publicKey.toBuffer(), + ]); + +const createMonsterAddress = ( + gameAddress: PublicKey, + monsterIndex: anchor.BN, +) => + findProgramAddress([ + Buffer.from(MONSTER_SEED), + gameAddress.toBuffer(), + player.publicKey.toBuffer(), + monsterIndex.toArrayLike(Buffer, "le", MONSTER_INDEX_BYTE_LENGTH), + ]); + +describe("RPG game", () => { + it("creates a new game", async () => {}); + + it("creates a new player", async () => {}); + + it("spawns a monster", async () => {}); + + it("attacks a monster", async () => {}); + + it("deposits action points", async () => {}); }); ``` -Now lets add in the `Create Game` test. Just call `createGame` with eight items, -be sure to pass in all the accounts, and make sure the `treasury` account signs -the transaction. +Now lets add in the `creates a new game` test. Just call `createGame` with eight +items, be sure to pass in all the accounts, and make sure the `treasury` account +signs the transaction. ```typescript -it("Create Game", async () => { - const [gameKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("GAME"), treasury.publicKey.toBuffer()], - program.programId, - ); - - const txHash = await program.methods - .createGame( - 8, // 8 Items per player - ) - .accounts({ - game: gameKey, - gameMaster: gameMaster.publicKey, - treasury: treasury.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .signers([treasury]) - .rpc(); - - await program.provider.connection.confirmTransaction(txHash); - - // Print out if you'd like - // const account = await program.account.game.fetch(gameKey); +it("creates a new game", async () => { + try { + const [gameAddress] = createGameAddress(); + + const createGameSignature = await program.methods + .createGame(MAX_ITEMS_PER_PLAYER) + .accounts({ + game: gameAddress, + gameMaster: gameMaster.publicKey, + treasury: treasury.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .signers([treasury]) + .rpc(); + + await confirmTransaction(createGameSignature, provider); + } catch (error) { + throw new Error(`Failed to create game: ${error.message}`); + } }); ``` @@ -1531,118 +1941,91 @@ anchor test some `.pnp.*` files and no `node_modules`, you may want to call `rm -rf .pnp.*` followed by `npm i` and then `yarn install`. That should work. -Now that everything is running, let's implement the `Create Player`, -`Spawn Monster`, and `Attack Monster` tests. Run each test as you complete them -to make sure things are running smoothly. +Now that everything is running, let's implement the `creates a new player`, +`spawns a monster`, and `attacks a monster` tests. Run each test as you complete +them to make sure things are running smoothly. ```typescript -it("Create Player", async () => { - const [gameKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("GAME"), treasury.publicKey.toBuffer()], - program.programId, - ); - - const [playerKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("PLAYER"), gameKey.toBuffer(), player.publicKey.toBuffer()], - program.programId, - ); - - const txHash = await program.methods - .createPlayer() - .accounts({ - game: gameKey, - playerAccount: playerKey, - player: player.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .rpc(); - - await program.provider.connection.confirmTransaction(txHash); - - // Print out if you'd like - // const account = await program.account.player.fetch(playerKey); +it("creates a new player", async () => { + try { + const [gameAddress] = createGameAddress(); + const [playerAddress] = createPlayerAddress(gameAddress); + + const createPlayerSignature = await program.methods + .createPlayer() + .accounts({ + game: gameAddress, + playerAccount: playerAddress, + player: player.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .rpc(); + + await confirmTransaction(createPlayerSignature, provider); + } catch (error) { + throw new Error(`Failed to create player: ${error.message}`); + } }); -it("Spawn Monster", async () => { - const [gameKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("GAME"), treasury.publicKey.toBuffer()], - program.programId, - ); - - const [playerKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("PLAYER"), gameKey.toBuffer(), player.publicKey.toBuffer()], - program.programId, - ); +it("spawns a monster", async () => { + try { + const [gameAddress] = createGameAddress(); + const [playerAddress] = createPlayerAddress(gameAddress); - const playerAccount = await program.account.player.fetch(playerKey); + const playerAccount = await program.account.player.fetch(playerAddress); + const [monsterAddress] = createMonsterAddress( + gameAddress, + playerAccount.nextMonsterIndex, + ); - const [monsterKey] = anchor.web3.PublicKey.findProgramAddressSync( - [ - Buffer.from("MONSTER"), - gameKey.toBuffer(), - player.publicKey.toBuffer(), - playerAccount.nextMonsterIndex.toBuffer("le", 8), - ], - program.programId, - ); - - const txHash = await program.methods - .spawnMonster() - .accounts({ - game: gameKey, - playerAccount: playerKey, - monster: monsterKey, - player: player.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .rpc(); - - await program.provider.connection.confirmTransaction(txHash); - - // Print out if you'd like - // const account = await program.account.monster.fetch(monsterKey); + const spawnMonsterSignature = await program.methods + .spawnMonster() + .accounts({ + game: gameAddress, + playerAccount: playerAddress, + monster: monsterAddress, + player: player.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .rpc(); + + await confirmTransaction(spawnMonsterSignature, provider); + } catch (error) { + throw new Error(`Failed to spawn monster: ${error.message}`); + } }); -it("Attack Monster", async () => { - const [gameKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("GAME"), treasury.publicKey.toBuffer()], - program.programId, - ); +it("attacks a monster", async () => { + try { + const [gameAddress] = createGameAddress(); + const [playerAddress] = createPlayerAddress(gameAddress); - const [playerKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("PLAYER"), gameKey.toBuffer(), player.publicKey.toBuffer()], - program.programId, - ); + const playerAccount = await program.account.player.fetch(playerAddress); + const [monsterAddress] = createMonsterAddress( + gameAddress, + playerAccount.nextMonsterIndex.subn(1), + ); - // Fetch the latest monster created - const playerAccount = await program.account.player.fetch(playerKey); - const [monsterKey] = anchor.web3.PublicKey.findProgramAddressSync( - [ - Buffer.from("MONSTER"), - gameKey.toBuffer(), - player.publicKey.toBuffer(), - playerAccount.nextMonsterIndex.subn(1).toBuffer("le", 8), - ], - program.programId, - ); - - const txHash = await program.methods - .attackMonster() - .accounts({ - playerAccount: playerKey, - monster: monsterKey, - player: player.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .rpc(); - - await program.provider.connection.confirmTransaction(txHash); - - // Print out if you'd like - // const account = await program.account.monster.fetch(monsterKey); - - const monsterAccount = await program.account.monster.fetch(monsterKey); - assert(monsterAccount.hitpoints.eqn(99)); + const attackMonsterSignature = await program.methods + .attackMonster() + .accounts({ + playerAccount: playerAddress, + monster: monsterAddress, + player: player.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .rpc(); + + await confirmTransaction(attackMonsterSignature, provider); + + const monsterAccount = await program.account.monster.fetch(monsterAddress); + assert( + monsterAccount.hitpoints.eqn(INITIAL_MONSTER_HITPOINTS - 1), + "Monster hitpoints should decrease by 1 after attack", + ); + } catch (error) { + throw new Error(`Failed to attack monster: ${error.message}`); + } }); ``` @@ -1650,7 +2033,7 @@ Notice the monster that we choose to attack is `playerAccount.nextMonsterIndex.subn(1).toBuffer('le', 8)`. This allows us to attack the most recent monster spawned. Anything below the `nextMonsterIndex` should be okay. Lastly, since seeds are just an array of bytes we have to turn -the index into the u64, which is little endian `le` at 8 bytes. +the index into the u64, which is a little endian `le` at 8 bytes. Run `anchor test` to deal some damage! @@ -1662,91 +2045,101 @@ game were running continuously, it probably makes sense to use something like [clockwork](https://www.clockwork.xyz/) cron jobs. ```typescript -it("Deposit Action Points", async () => { - const [gameKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("GAME"), treasury.publicKey.toBuffer()], - program.programId, - ); - - const [playerKey] = anchor.web3.PublicKey.findProgramAddressSync( - [Buffer.from("PLAYER"), gameKey.toBuffer(), player.publicKey.toBuffer()], - program.programId, - ); - - // To show that anyone can deposit the action points - // Ie, give this to a clockwork bot - const clockworkWallet = anchor.web3.Keypair.generate(); - - // To give it a starting balance - const clockworkProvider = new anchor.AnchorProvider( - program.provider.connection, - new NodeWallet(clockworkWallet), - anchor.AnchorProvider.defaultOptions(), - ); - const clockworkProgram = new anchor.Program( - IDL, - program.programId, - clockworkProvider, - ); - - // Have to give the accounts some lamports else the tx will fail - const amountToInitialize = 10000000000; - - const clockworkAirdropTx = - await clockworkProgram.provider.connection.requestAirdrop( - clockworkWallet.publicKey, - amountToInitialize, +it("deposits action points", async () => { + try { + const [gameAddress] = createGameAddress(); + const [playerAddress] = createPlayerAddress(gameAddress); + + // To show that anyone can deposit the action points + // Ie, give this to a clockwork bot + const clockworkWallet = anchor.web3.Keypair.generate(); + + // To give it a starting balance + const clockworkProvider = new anchor.AnchorProvider( + program.provider.connection, + new NodeWallet(clockworkWallet), + anchor.AnchorProvider.defaultOptions(), ); - await program.provider.connection.confirmTransaction( - clockworkAirdropTx, - "confirmed", - ); - const treasuryAirdropTx = - await clockworkProgram.provider.connection.requestAirdrop( + // Have to give the accounts some lamports else the tx will fail + const amountToInitialize = 10000000000; + + const clockworkAirdropTx = + await clockworkProvider.connection.requestAirdrop( + clockworkWallet.publicKey, + amountToInitialize, + ); + + await confirmTransaction(clockworkAirdropTx, clockworkProvider); + + const treasuryAirdropTx = await clockworkProvider.connection.requestAirdrop( treasury.publicKey, amountToInitialize, ); - await program.provider.connection.confirmTransaction( - treasuryAirdropTx, - "confirmed", - ); - - const txHash = await clockworkProgram.methods - .depositActionPoints() - .accounts({ - game: gameKey, - player: playerKey, - treasury: treasury.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .rpc(); - - await program.provider.connection.confirmTransaction(txHash); - - const expectedActionPoints = 100 + 5 + 1; // Player Create ( 100 ) + Monster Spawn ( 5 ) + Monster Attack ( 1 ) - const treasuryBalance = await program.provider.connection.getBalance( - treasury.publicKey, - ); - assert( - treasuryBalance == amountToInitialize + expectedActionPoints, // Player Create ( 100 ) + Monster Spawn ( 5 ) + Monster Attack ( 1 ) - ); - - const gameAccount = await program.account.game.fetch(gameKey); - assert(gameAccount.actionPointsCollected.eqn(expectedActionPoints)); - - const playerAccount = await program.account.player.fetch(playerKey); - assert(playerAccount.actionPointsSpent.eqn(expectedActionPoints)); - assert(playerAccount.actionPointsToBeCollected.eqn(0)); + + await confirmTransaction(treasuryAirdropTx, clockworkProvider); + + const depositActionPointsSignature = await program.methods + .depositActionPoints() + .accounts({ + game: gameAddress, + player: playerAddress, + treasury: treasury.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .rpc(); + + await confirmTransaction(depositActionPointsSignature, provider); + + const expectedActionPoints = + CREATE_PLAYER_ACTION_POINTS + + SPAWN_MONSTER_ACTION_POINTS + + ATTACK_MONSTER_ACTION_POINTS; + const treasuryBalance = await provider.connection.getBalance( + treasury.publicKey, + ); + assert( + treasuryBalance === AIRDROP_AMOUNT + expectedActionPoints, + "Treasury balance should match expected action points", + ); + + const gameAccount = await program.account.game.fetch(gameAddress); + assert( + gameAccount.actionPointsCollected.eqn(expectedActionPoints), + "Game action points collected should match expected", + ); + + const playerAccount = await program.account.player.fetch(playerAddress); + assert( + playerAccount.actionPointsSpent.eqn(expectedActionPoints), + "Player action points spent should match expected", + ); + assert( + playerAccount.actionPointsToBeCollected.eqn(0), + "Player should have no action points to be collected", + ); + } catch (error) { + throw new Error(`Failed to deposit action points: ${error.message}`); + } }); ``` Finally, run `anchor test` to see everything working. +```bash + +RPG game + ✔ creates a new game (317ms) + ✔ creates a new player (399ms) + ✔ spawns a monster (411ms) + ✔ attacks a monster (413ms) + ✔ deposits action points (1232ms) +``` + Congratulations! This was a lot to cover, but you now have a mini RPG game engine. If things aren't quite working, go back through the lab and find where -you went wrong. If you need, you can refer to the -[`main` branch of the solution code](https://github.com/Unboxed-Software/anchor-rpg). +you went wrong. If you need to, you can refer to the +[`main` branch of the solution code](https://github.com/solana-developers/anchor-rpg). Be sure to put these concepts into practice in your own programs. Each little optimization adds up! @@ -1754,17 +2147,17 @@ optimization adds up! ## Challenge Now it's your turn to practice independently. Go back through the lab code -looking for additional optimizations and/or expansion you can make. Think +looking for additional optimizations and/or expansions you can make. Think through new systems and features you would add and how you would optimize them. -You can find some example modifications on the `challenge-solution` branch of -the -[RPG repository](https://github.com/Unboxed-Software/anchor-rpg/tree/challenge-solution). +You can find some example modifications on the +[`challenge-solution` branch of the RPG repository](https://github.com/solana-developers/anchor-rpg/tree/challenge-solution). Finally, go through one of your own programs and think about optimizations you can make to improve memory management, storage size, and/or concurrency. + Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=4a628916-91f5-46a9-8eb0-6ba453aa6ca6)! From a72e6ee8cdb0f785bb24f9d2a95045fddafb0a3c Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Fri, 4 Oct 2024 15:51:47 +1000 Subject: [PATCH 34/54] Prettier --- .../onchain-development/intro-to-anchor.md | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/content/courses/onchain-development/intro-to-anchor.md b/content/courses/onchain-development/intro-to-anchor.md index 69a4cb110..8b30b7ab6 100644 --- a/content/courses/onchain-development/intro-to-anchor.md +++ b/content/courses/onchain-development/intro-to-anchor.md @@ -84,14 +84,16 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); ### Define instruction logic The `#[program]` attribute macro defines the module containing all of your -program's instruction handlers. This is where you implement the business logic for each -operation in your program. +program's instruction handlers. This is where you implement the business logic +for each operation in your program. Each public function in the module with the `#[program]` attribute will be treated as a separate instruction handler. -Each instruction handler (function) requires a parameter of type `Context` and can include more parameters as needed. Anchor will automatically handle instruction data -deserialization so that you can work with instruction data as Rust types. +Each instruction handler (function) requires a parameter of type `Context` and +can include more parameters as needed. Anchor will automatically handle +instruction data deserialization so that you can work with instruction data as +Rust types. ```rust #[program] @@ -135,8 +137,8 @@ pub struct Context<'a, 'b, 'c, 'info, T: Bumps> { ``` `Context` is a generic type where `T` defines the list of accounts an -instruction handler requires. When you use `Context`, you specify the concrete type of -`T` as a struct that adopts the `Accounts` trait. +instruction handler requires. When you use `Context`, you specify the concrete +type of `T` as a struct that adopts the `Accounts` trait. The first argument of every instruction handler must be `Context`. `Context` takes a generic of your `Accounts` struct, eg, if `AddMovieReview` was the @@ -152,8 +154,8 @@ Through this context argument the instruction can then access: - The accounts passed into the instruction (`ctx.accounts`) - The program ID (`ctx.program_id`) of the executing program - The remaining accounts (`ctx.remaining_accounts`). The `remaining_accounts` is - a vector that contains all accounts that were passed into the instruction handler but - are not declared in the `Accounts` struct. + a vector that contains all accounts that were passed into the instruction + handler but are not declared in the `Accounts` struct. - The bumps for any PDA accounts in the `Accounts` struct (`ctx.bumps`) - The seeds for any PDA accounts in tha `Accounts` struct (`ctx.seeds`) @@ -207,8 +209,8 @@ pub struct InstructionAccounts<'info> { When `instruction_one` is invoked, the program: -- Checks that the accounts passed into the instruction handler match the account types - specified in the `InstructionAccounts` struct +- Checks that the accounts passed into the instruction handler match the account + types specified in the `InstructionAccounts` struct - Checks the accounts against any additional constraints specified > If any accounts passed into `instruction_one` fail the account validation or @@ -602,9 +604,10 @@ pub struct Initialize<'info> { #### 4. Add the `initialize` instruction handler Now that we have our `Counter` account and `Initialize` type , let's implement -the `initialize` instruction handler within `#[program]`. This instruction handler requires a `Context` of type `Initialize` and takes no additional instruction data. -In the instruction logic, we are simply setting the `counter` account's `count` field -to `0`. +the `initialize` instruction handler within `#[program]`. This instruction +handler requires a `Context` of type `Initialize` and takes no additional +instruction data. In the instruction logic, we are simply setting the `counter` +account's `count` field to `0`. ```rust pub fn initialize(ctx: Context) -> Result<()> { @@ -619,8 +622,8 @@ pub fn initialize(ctx: Context) -> Result<()> { #### 5. Implement `Context` type `Update` Now, using the `#[derive(Accounts)]` macro again, let's create the `Update` type -that lists the accounts that the `increment` instruction handler requires. It'll need -the following accounts: +that lists the accounts that the `increment` instruction handler requires. It'll +need the following accounts: - `counter` - an existing counter account to increment - `user` - payer for the transaction fee @@ -639,9 +642,12 @@ pub struct Update<'info> { #### 6. Add `increment` instruction handler -Lastly, within `#[program]`, let's implement an `increment` instruction handler to -increment the `count` once a `counter` account is initialized by the first -instruction handler. This instruction handler requires a `Context` of type `Update` (implemented in the next step) and takes no additional instruction data. In the instruction logic, we are simply incrementing an existing `counter` account's `count` field by `1`. +Lastly, within `#[program]`, let's implement an `increment` instruction handler +to increment the `count` once a `counter` account is initialized by the first +instruction handler. This instruction handler requires a `Context` of type +`Update` (implemented in the next step) and takes no additional instruction +data. In the instruction logic, we are simply incrementing an existing `counter` +account's `count` field by `1`. ```rust pub fn increment(ctx: Context) -> Result<()> { From 8c76f3f8abdb3385498af6dd074133af5d730d4a Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Fri, 4 Oct 2024 15:52:03 +1000 Subject: [PATCH 35/54] Add TODO --- content/courses/intro-to-solana/interact-with-wallets.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/courses/intro-to-solana/interact-with-wallets.md b/content/courses/intro-to-solana/interact-with-wallets.md index 27738df9f..960366e60 100644 --- a/content/courses/intro-to-solana/interact-with-wallets.md +++ b/content/courses/intro-to-solana/interact-with-wallets.md @@ -346,6 +346,8 @@ using in this lab. ### Download the starter code + + Download the [starter code for this project](https://github.com/Unboxed-Software/solana-ping-frontend/tree/starter). This project is a simple Next.js application. It's mostly empty except for the From 8048f51042bb9c7a07cca562858fcb2d92cdd06a Mon Sep 17 00:00:00 2001 From: wuuer Date: Fri, 4 Oct 2024 14:22:42 +0800 Subject: [PATCH 36/54] fix and improve close-mint course (#536) * Update the Token Extensions Program link * sync token-program.md from main * change "mint" to "token mint account". add a typescript code example for using an account on devnet while calling "initializeKeypair" in the section [2. Run validator node] add a typescript code example for importing our new function in the section [3. Create a mint with close authority] change "const mintInfo" to "let mintInfo" because "mintInfo" is used again in the "CLOSE MINT" code part. --------- Co-authored-by: Jeff Wood --- .../courses/token-extensions/close-mint.md | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/content/courses/token-extensions/close-mint.md b/content/courses/token-extensions/close-mint.md index 42a0df6eb..8c3cd9caa 100644 --- a/content/courses/token-extensions/close-mint.md +++ b/content/courses/token-extensions/close-mint.md @@ -198,10 +198,10 @@ await setAuthority( ## Lab -In this lab, we'll create a mint with the `close mint` extension. We will then -mint some of the tokens and see what happens when we try to close it with a -non-zero supply (hint, the close transaction will fail). Lastly, we will burn -the supply and close the account. +In this lab, we'll create a token mint account with the `close mint` extension. +We will then mint some of the tokens and see what happens when we try to close +it with a non-zero supply (hint, the close transaction will fail). Lastly, we +will burn the supply and close the account. ### 1. Getting Started @@ -304,6 +304,14 @@ running `solana config get` in your terminal. And then go to address. You can get your address from running `solana address` in your terminal. +For example, assuming `keypairPath` is `/home/.config/solana/id.json` + +```typescript +const payer = initializeKeypair(connection, { + keypairPath: "/home/.config/solana/id.json", +}); +``` + ### 3. Create a mint with close authority Let's create a closable mint by creating the function `createClosableMint` in a @@ -399,7 +407,13 @@ export async function createClosableMint( ``` Now let's call this function in `src/index.ts`. First you'll need to import our -new function. Then paste the following under the right comment section: +new function by uncommenting the 3rd line. + +```ts +import { createClosableMint } from "./create-mint"; +``` + +Then paste the following under the right comment section: ```ts // CREATE A MINT WITH CLOSE AUTHORITY @@ -470,7 +484,7 @@ Underneath the minting functions, add the following code block: /** * Get mint information to verify supply */ -const mintInfo = await getMint( +let mintInfo = await getMint( connection, mintKeypair.publicKey, "finalized", @@ -591,7 +605,7 @@ Putting this all together we get: ```ts // CLOSE MINT -const mintInfo = await getMint( +mintInfo = await getMint( connection, mintKeypair.publicKey, "finalized", From e019790d33c1b75561146c4abd4a870b9bddca1e Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Fri, 4 Oct 2024 12:33:19 +0530 Subject: [PATCH 37/54] Program security - Account data matching lesson updated (#440) * Added fix for code snippets and content * Update content/courses/program-security/account-data-matching.md --------- Co-authored-by: Mike MacCana --- .../program-security/account-data-matching.md | 300 ++++++++++-------- 1 file changed, 167 insertions(+), 133 deletions(-) diff --git a/content/courses/program-security/account-data-matching.md b/content/courses/program-security/account-data-matching.md index baff3b5bf..6ecb10f5a 100644 --- a/content/courses/program-security/account-data-matching.md +++ b/content/courses/program-security/account-data-matching.md @@ -12,39 +12,43 @@ description: - Use **data validation checks** to verify that account data matches an expected value. Without appropriate data validation checks, unexpected accounts may be - used in an instruction. + used in an instruction handler. - To implement data validation checks in Rust, simply compare the data stored on an account to an expected value. + ```rust if ctx.accounts.user.key() != ctx.accounts.user_data.user { return Err(ProgramError::InvalidAccountData.into()); } ``` -- In Anchor, you can use `constraint` to checks whether the given expression - evaluates to true. Alternatively, you can use `has_one` to check that a target - account field stored on the account matches the key of an account in the - `Accounts` struct. + +- In Anchor, you can use a + [`constraint`](https://www.anchor-lang.com/docs/account-constraints) to check + whether the given expression evaluates to true. Alternatively, you can use + `has_one` to check that a target account field stored on the account matches + the key of an account in the `Accounts` struct. ## Lesson Account data matching refers to data validation checks used to verify the data stored on an account matches an expected value. Data validation checks provide a way to include additional constraints to ensure the appropriate accounts are -passed into an instruction. +passed into an instruction handler. -This can be useful when accounts required by an instruction have dependencies on -values stored in other accounts or if an instruction is dependent on the data -stored in an account. +This can be useful when accounts required by an instruction handler have +dependencies on values stored in other accounts or if an instruction handler is +dependent on the data stored in an account. -#### Missing data validation check +### Missing data validation check -The example below includes an `update_admin` instruction that updates the -`admin` field stored on an `admin_config` account. +The example below includes an `update_admin` instruction handler that updates +the `admin` field stored on an `admin_config` account. -The instruction is missing a data validation check to verify the `admin` account -signing the transaction matches the `admin` stored on the `admin_config` +The instruction handler is missing a data validation check to verify the `admin` +account signing the transaction matches the `admin` stored on the `admin_config` account. This means any account signing the transaction and passed into the -instruction as the `admin` account can update the `admin_config` account. +instruction handler as the `admin` account can update the `admin_config` +account. ```rust use anchor_lang::prelude::*; @@ -67,7 +71,8 @@ pub struct UpdateAdmin<'info> { pub admin_config: Account<'info, AdminConfig>, #[account(mut)] pub admin: Signer<'info>, - pub new_admin: SystemAccount<'info>, + /// CHECK: This account will not be checked by anchor + pub new_admin: UncheckedAccount<'info>, } #[account] @@ -76,7 +81,7 @@ pub struct AdminConfig { } ``` -#### Add data validation check +### Add Data Validation Check The basic Rust approach to solve this problem is to simply compare the passed in `admin` key to the `admin` key stored in the `admin_config` account, throwing an @@ -88,9 +93,9 @@ if ctx.accounts.admin.key() != ctx.accounts.admin_config.admin { } ``` -By adding a data validation check, the `update_admin` instruction would only -process if the `admin` signer of the transaction matched the `admin` stored on -the `admin_config` account. +By adding a data validation check, the `update_admin` instruction handler would +only process if the `admin` signer of the transaction matched the `admin` stored +on the `admin_config` account. ```rust use anchor_lang::prelude::*; @@ -116,7 +121,8 @@ pub struct UpdateAdmin<'info> { pub admin_config: Account<'info, AdminConfig>, #[account(mut)] pub admin: Signer<'info>, - pub new_admin: SystemAccount<'info>, + /// CHECK: This account will not be checked by anchor + pub new_admin: UncheckedAccount<'info>, } #[account] @@ -125,11 +131,11 @@ pub struct AdminConfig { } ``` -#### Use Anchor constraints +### Use Anchor Constraints Anchor simplifies this with the `has_one` constraint. You can use the `has_one` -constraint to move the data validation check from the instruction logic to the -`UpdateAdmin` struct. +constraint to move the data validation check from the instruction handler logic +to the `UpdateAdmin` struct. In the example below, `has_one = admin` specifies that the `admin` account signing the transaction must match the `admin` field stored on the @@ -161,7 +167,8 @@ pub struct UpdateAdmin<'info> { pub admin_config: Account<'info, AdminConfig>, #[account(mut)] pub admin: Signer<'info>, - pub new_admin: SystemAccount<'info>, + /// CHECK: This account will not be checked by anchor + pub new_admin: UncheckedAccount<'info>, } #[account] @@ -185,46 +192,51 @@ pub struct UpdateAdmin<'info> { pub admin_config: Account<'info, AdminConfig>, #[account(mut)] pub admin: Signer<'info>, - pub new_admin: SystemAccount<'info>, + /// CHECK: This account will not be checked by anchor + pub new_admin: UncheckedAccount<'info>, } ``` ## Lab -For this lab we'll create a simple “vault” program similar to the program we +For this lab, we'll create a simple “vault” program similar to the program we used in the Signer Authorization lesson and the Owner Check lesson. Similar to those labs, we'll show in this lab how a missing data validation check could allow the vault to be drained. -#### 1. Starter +### 1. Starter -To get started, download the starter code from the `starter` branch of -[this repository](https://github.com/Unboxed-Software/solana-account-data-matching). +To get started, download the starter code from the +[`starter` branch of this repository](https://github.com/solana-developers/account-data-matching/tree/starter). The starter code includes a program with two instructions and the boilerplate setup for the test file. -The `initialize_vault` instruction initializes a new `Vault` account and a new -`TokenAccount`. The `Vault` account will store the address of a token account, -the authority of the vault, and a withdraw destination token account. +The `initialize_vault` instruction handler initializes a new `Vault` account and +a new `TokenAccount`. The `Vault` account will store the address of a token +account, the authority of the vault, and a withdraw destination token account. The authority of the new token account will be set as the `vault`, a PDA of the program. This allows the `vault` account to sign for the transfer of tokens from the token account. -The `insecure_withdraw` instruction transfers all the tokens in the `vault` -account's token account to a `withdraw_destination` token account. +The `insecure_withdraw` instruction handler transfers all the tokens in the +`vault` account's token account to a `withdraw_destination` token account. -Notice that this instruction \***\*does\*\*** have a signer check for + + +Notice that this instruction handler \***\*does\*\*** have a signer check for `authority` and an owner check for `vault`. However, nowhere in the account -validation or instruction logic is there code that checks that the `authority` -account passed into the instruction matches the `authority` account on the -`vault`. +validation or instruction handler logic is there code that checks that the +`authority` account passed into the instruction handler matches the `authority` +account on the `vault`. ```rust use anchor_lang::prelude::*; use anchor_spl::token::{self, Mint, Token, TokenAccount}; -declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); +declare_id!("J89xWAprDsLAAwcTA6AhrK49UMSAYJJWdXvw4ZQK4suu"); + +pub const DISCRIMINATOR_SIZE: usize = 8; #[program] pub mod account_data_matching { @@ -240,7 +252,7 @@ pub mod account_data_matching { pub fn insecure_withdraw(ctx: Context) -> Result<()> { let amount = ctx.accounts.token_account.amount; - let seeds = &[b"vault".as_ref(), &[*ctx.bumps.get("vault").unwrap()]]; + let seeds = &[b"vault".as_ref(), &[ctx.bumps.vault]]; let signer = [&seeds[..]]; let cpi_ctx = CpiContext::new_with_signer( @@ -263,7 +275,7 @@ pub struct InitializeVault<'info> { #[account( init, payer = authority, - space = 8 + 32 + 32 + 32, + space = DISCRIMINATOR_SIZE + Vault::INIT_SPACE, seeds = [b"vault"], bump, )] @@ -306,6 +318,7 @@ pub struct InsecureWithdraw<'info> { } #[account] +#[derive(Default, InitSpace)] pub struct Vault { token_account: Pubkey, authority: Pubkey, @@ -313,64 +326,73 @@ pub struct Vault { } ``` -#### 2. Test `insecure_withdraw` instruction +### 2. Test insecure_withdraw Instruction Handler To prove that this is a problem, let's write a test where an account other than the vault's `authority` tries to withdraw from the vault. The test file includes the code to invoke the `initialize_vault` instruction -using the provider wallet as the `authority` and then mints 100 tokens to the -`vault` token account. +handler using the provider wallet as the `authority` and then mints 100 tokens +to the `vault` token account. -Add a test to invoke the `insecure_withdraw` instruction. Use -`withdrawDestinationFake` as the `withdrawDestination` account and `walletFake` -as the `authority`. Then send the transaction using `walletFake`. +Add a test to invoke the `insecure_withdraw` instruction handler. Use +`fakeWithdrawDestination` as the `withdrawDestination` account and `fakeWallet` +as the `authority`. Then send the transaction using `fakeWallet`. Since there are no checks the verify the `authority` account passed into the -instruction matches the values stored on the `vault` account initialized in the -first test, the instruction will process successfully and the tokens will be -transferred to the `withdrawDestinationFake` account. +instruction handler matches the values stored on the `vault` account initialized +in the first test, the instruction handler will process successfully and the +tokens will be transferred to the `fakeWithdrawDestination` account. ```typescript -describe("account-data-matching", () => { +describe("Account Data Matching", () => { ... - it("Insecure withdraw", async () => { - const tx = await program.methods - .insecureWithdraw() - .accounts({ - vault: vaultPDA, - tokenAccount: tokenPDA, - withdrawDestination: withdrawDestinationFake, - authority: walletFake.publicKey, - }) - .transaction() - - await anchor.web3.sendAndConfirmTransaction(connection, tx, [walletFake]) - - const balance = await connection.getTokenAccountBalance(tokenPDA) - expect(balance.value.uiAmount).to.eq(0) - }) + it("allows insecure withdrawal", async () => { + try { + const tx = await program.methods + .insecureWithdraw() + .accounts({ + vault: vaultPDA, + tokenAccount: tokenPDA, + withdrawDestination: fakeWithdrawDestination, + authority: fakeWallet.publicKey, + }) + .transaction(); + + await anchor.web3.sendAndConfirmTransaction(provider.connection, tx, [ + fakeWallet, + ]); + + const tokenAccount = await getAccount(provider.connection, tokenPDA); + expect(Number(tokenAccount.amount)).to.equal(0); + } catch (error) { + throw new Error( + `Insecure withdraw failed unexpectedly: ${error.message}`, + ); + } + }); }) ``` Run `anchor test` to see that both transactions will complete successfully. ```bash -account-data-matching - ✔ Initialize Vault (811ms) - ✔ Insecure withdraw (403ms) +Account Data Matching + ✔ initializes the vault and mints tokens (879ms) + ✔ allows insecure withdrawal (431ms) ``` -#### 3. Add `secure_withdraw` instruction +### 3. Add secure_withdraw Instruction Handler -Let's go implement a secure version of this instruction called +Let's go implement a secure version of this instruction handler called `secure_withdraw`. -This instruction will be identical to the `insecure_withdraw` instruction, -except we'll use the `has_one` constraint in the account validation struct -(`SecureWithdraw`) to check that the `authority` account passed into the -instruction matches the `authority` account on the `vault` account. That way -only the correct authority account can withdraw the vault's tokens. +This instruction handler will be identical to the `insecure_withdraw` +instruction handler, except we'll use the `has_one` constraint in the account +validation struct (`SecureWithdraw`) to check that the `authority` account +passed into the instruction handler matches the `authority` account on the +`vault` account. That way only the correct authority account can withdraw the +vault's tokens. ```rust use anchor_lang::prelude::*; @@ -378,6 +400,8 @@ use anchor_spl::token::{self, Mint, Token, TokenAccount}; declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); +pub const DISCRIMINATOR_SIZE: usize = 8; + #[program] pub mod account_data_matching { use super::*; @@ -385,7 +409,7 @@ pub mod account_data_matching { pub fn secure_withdraw(ctx: Context) -> Result<()> { let amount = ctx.accounts.token_account.amount; - let seeds = &[b"vault".as_ref(), &[*ctx.bumps.get("vault").unwrap()]]; + let seeds = &[b"vault".as_ref(), &[ctx.bumps.vault]]; let signer = [&seeds[..]]; let cpi_ctx = CpiContext::new_with_signer( @@ -411,7 +435,6 @@ pub struct SecureWithdraw<'info> { has_one = token_account, has_one = authority, has_one = withdraw_destination, - )] pub vault: Account<'info, Vault>, #[account( @@ -427,94 +450,104 @@ pub struct SecureWithdraw<'info> { } ``` -#### 4. Test `secure_withdraw` instruction +### 4. Test secure_withdraw Instruction Handler -Now let's test the `secure_withdraw` instruction with two tests: one that uses -`walletFake` as the authority and one that uses `wallet` as the authority. We -expect the first invocation to return an error and the second to succeed. +Now let's test the `secure_withdraw` instruction handler with two tests: one +that uses `fakeWallet` as the authority and one that uses `wallet` as the +authority. We expect the first invocation to return an error and the second to +succeed. ```typescript describe("account-data-matching", () => { ... - it("Secure withdraw, expect error", async () => { + it("prevents unauthorized secure withdrawal", async () => { try { const tx = await program.methods .secureWithdraw() .accounts({ vault: vaultPDA, tokenAccount: tokenPDA, - withdrawDestination: withdrawDestinationFake, - authority: walletFake.publicKey, + withdrawDestination: fakeWithdrawDestination, + authority: fakeWallet.publicKey, }) - .transaction() + .transaction(); - await anchor.web3.sendAndConfirmTransaction(connection, tx, [walletFake]) - } catch (err) { - expect(err) - console.log(err) + await anchor.web3.sendAndConfirmTransaction(provider.connection, tx, [ + fakeWallet, + ]); + + throw new Error("Secure withdraw should have failed but didn't"); + } catch (error) { + expect(error).to.be.an("error"); + console.log("Expected error occurred:", error.message); } - }) - - it("Secure withdraw", async () => { - await spl.mintTo( - connection, - wallet.payer, - mint, - tokenPDA, - wallet.payer, - 100 - ) - - await program.methods - .secureWithdraw() - .accounts({ - vault: vaultPDA, - tokenAccount: tokenPDA, - withdrawDestination: withdrawDestination, - authority: wallet.publicKey, - }) - .rpc() - - const balance = await connection.getTokenAccountBalance(tokenPDA) - expect(balance.value.uiAmount).to.eq(0) - }) + }); + + it("allows secure withdrawal by authorized user", async () => { + try { + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await mintTo( + provider.connection, + wallet.payer, + mint, + tokenPDA, + wallet.payer, + 100, + ); + + await program.methods + .secureWithdraw() + .accounts({ + vault: vaultPDA, + tokenAccount: tokenPDA, + withdrawDestination, + authority: wallet.publicKey, + }) + .rpc(); + + const tokenAccount = await getAccount(provider.connection, tokenPDA); + expect(Number(tokenAccount.amount)).to.equal(0); + } catch (error) { + throw new Error(`Secure withdraw failed unexpectedly: ${error.message}`); + } + }); }) ``` Run `anchor test` to see that the transaction using an incorrect authority -account will now return an Anchor Error while the transaction using correct -accounts completes successfully. +account will now return an Anchor Error while the transaction using the correct +accounts complete successfully. ```bash -'Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS invoke [1]', -'Program log: Instruction: SecureWithdraw', -'Program log: AnchorError caused by account: vault. Error Code: ConstraintHasOne. Error Number: 2001. Error Message: A has one constraint was violated.', -'Program log: Left:', -'Program log: DfLZV18rD7wCQwjYvhTFwuvLh49WSbXFeJFPQb5czifH', -'Program log: Right:', -'Program log: 5ovvmG5ntwUC7uhNWfirjBHbZD96fwuXDMGXiyMwPg87', -'Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS consumed 10401 of 200000 compute units', -'Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS failed: custom program error: 0x7d1' +"Program J89xWAprDsLAAwcTA6AhrK49UMSAYJJWdXvw4ZQK4suu invoke [1]", +"Program log: Instruction: SecureWithdraw", +"Program log: AnchorError caused by account: vault. Error Code: ConstraintHasOne. Error Number: 2001. Error Message: A has one constraint was violated.", +"Program log: Left:", +"Program log: GprrWv9r8BMxQiWea9MrbCyK7ig7Mj8CcseEbJhDDZXM", +"Program log: Right:", +"Program log: 2jTDDwaPzbpG2oFnnqtuHJpiS9k9dDVqzzfA2ofcqfFS", +"Program J89xWAprDsLAAwcTA6AhrK49UMSAYJJWdXvw4ZQK4suu consumed 11790 of 200000 compute units", +"Program J89xWAprDsLAAwcTA6AhrK49UMSAYJJWdXvw4ZQK4suu failed: custom program error: 0x7d1" ``` Note that Anchor specifies in the logs the account that causes the error (`AnchorError caused by account: vault`). ```bash -✔ Secure withdraw, expect error (77ms) -✔ Secure withdraw (10073ms) +✔ prevents unauthorized secure withdrawal +✔ allows secure withdrawal by authorized user (1713ms) ``` And just like that, you've closed up the security loophole. The theme across most of these potential exploits is that they're quite simple. However, as your -programs grow in scope and complexity, it becomse increasingly easy to miss +programs grow in scope and complexity, it becomes increasingly easy to miss possible exploits. It's great to get in a habit of writing tests that send instructions that _shouldn't_ work. The more the better. That way you catch problems before you deploy. If you want to take a look at the final solution code you can find it on the -`solution` branch of -[the repository](https://github.com/Unboxed-Software/solana-account-data-matching/tree/solution). +[`solution` branch of the repository](https://github.com/solana-developers/account-data-matching/tree/solution). ## Challenge @@ -528,6 +561,7 @@ Remember, if you find a bug or exploit in somebody else's program, please alert them! If you find one in your own program, be sure to patch it right away. + Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=a107787e-ad33-42bb-96b3-0592efc1b92f)! From 9a4e026932ee18e98bb94ef376116346032cbd43 Mon Sep 17 00:00:00 2001 From: Dogukan Ali Gundogan <59707019+DogukanGun@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:08:18 +0200 Subject: [PATCH 38/54] Update solana pay content (#345) * Update solana pay content * Update for the review * Delete unnecessary else * Prettier the code * Review update for error handling * Name fixing for content of solana pay course * Change naming at solana pay's content --- content/courses/solana-pay/solana-pay.md | 157 +++++++++++------------ 1 file changed, 75 insertions(+), 82 deletions(-) diff --git a/content/courses/solana-pay/solana-pay.md b/content/courses/solana-pay/solana-pay.md index bfacb6677..5d08a75e4 100644 --- a/content/courses/solana-pay/solana-pay.md +++ b/content/courses/solana-pay/solana-pay.md @@ -3,10 +3,9 @@ title: Solana Pay objectives: - Use the Solana Pay specification to build payment requests and initiate transactions using URLs encoded as QR codes - - Use the `@solana/pay` library to help with the creation of Solana Pay - transaction requests + - Use the `@solana/pay` library to create Solana Pay transaction requests - Partially sign transactions and implement transaction gating based on - certain conditions + specific conditions description: "How to create Solana Pay payment requests using links and QR codes." --- @@ -15,19 +14,19 @@ description: - **Solana Pay** is a specification for encoding Solana transaction requests within URLs, enabling standardized transaction requests across different - Solana apps and wallets -- **Partial signing** of transactions allows for the creation of transactions - that require multiple signatures before they are submitted to the network + Solana apps and wallets. +- **Partial signing** of transactions allows the creation of transactions that + require multiple signatures before they are submitted to the network. - **Transaction gating** involves implementing rules that determine whether - certain transactions are allowed to be processed or not, based on certain - conditions or the presence of specific data in the transaction + certain transactions are allowed to be processed, based on specific conditions + or the presence of particular data in the transaction. ## Lesson The Solana community is continually improving and expanding the network's -functionality. But that doesn't always mean developing brand new technology. +functionality. But that doesn't always mean developing brand-new technology. Sometimes it means leveraging the network's existing features in new and -interesting ways. +innovative ways. Solana Pay is a great example of this. Rather than adding new functionality to the network, Solana Pay uses the network's existing signing features in a unique @@ -38,17 +37,17 @@ Throughout this lesson, you'll learn how to use Solana Pay to create transfer and transaction requests, encode these requests as a QR code, partially sign transactions, and gate transactions based on conditions you choose. Rather than leaving it at that, we hope you'll see this as an example of leveraging existing -features in new and interesting ways, using it as a launching pad for your own +features in new and innovative ways, using it as a launching pad for your own unique client-side network interactions. ### Solana Pay The [Solana Pay specification](https://docs.solanapay.com/spec) is a set of standards that allow users to request payments and initiate transactions using -URLs in a uniform way across various Solana apps and wallets. +URLs uniformly across various Solana apps and wallets. Request URLs are prefixed with `solana:` so that platforms can direct the link -to the appropriate application. For example, on mobile a URL that starts with +to the appropriate application. For example, on mobile, a URL that starts with `solana:` will be directed to wallet applications that support the Solana Pay specification. From there, the wallet can use the remainder of the URL to appropriately handle the request. @@ -141,8 +140,8 @@ a function that handles the request and response. import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse, + request: NextApiRequest, + response: NextApiResponse, ) { // Handle the request } @@ -163,18 +162,17 @@ Building on the empty endpoint from before, that may look like this: import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse, + request: NextApiRequest, + response: NextApiResponse, ) { - if (req.method === "GET") { - return get(res); - } else { - return res.status(405).json({ error: "Method not allowed" }); + if (request.method === "GET") { + return get(response); } + return response.status(405).json({ error: "Method not allowed" }); } -function get(res: NextApiResponse) { - res.status(200).json({ +function get(response: NextApiResponse) { + response.status(200).json({ label: "Store Name", icon: "https://solana.com/src/img/branding/solanaLogoMark.svg", }); @@ -205,26 +203,26 @@ transaction and return it to the wallet for signing by: import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse, + request: NextApiRequest, + response: NextApiResponse, ) { - if (req.method === "GET") { - return get(res); - } else if (req.method === "POST") { - return post(req, res); - } else { - return res.status(405).json({ error: "Method not allowed" }); + if (request.method === "GET") { + return get(response); } + if (request.method === "POST") { + return post(request, response); + } + return response.status(405).json({ error: "Method not allowed" }); } -function get(res: NextApiResponse) { - res.status(200).json({ +function get(response: NextApiResponse) { + response.status(200).json({ label: "Store Name", icon: "https://solana.com/src/img/branding/solanaLogoMark.svg", }); } -async function post(req: PublicKey, res: PublicKey) { - const { account, reference } = req.body; +async function post(request: NextApiRequest, response: NextApiResponse) { + const { account, reference } = request.body; const connection = new Connection(clusterApiUrl("devnet")); @@ -241,14 +239,14 @@ async function post(req: PublicKey, res: PublicKey) { lamports: 0.001 * LAMPORTS_PER_SOL, }); - transaction.add(instruction); - - transaction.keys.push({ + instruction.keys.push({ pubkey: reference, isSigner: false, isWritable: false, }); + transaction.add(instruction); + const serializedTransaction = transaction.serialize({ requireAllSignatures: false, }); @@ -256,7 +254,7 @@ async function post(req: PublicKey, res: PublicKey) { const message = "Simple transfer of 0.001 SOL"; - res.send(200).json({ + response.status(200).json({ transaction: base64, message, }); @@ -495,21 +493,21 @@ variable. The first thing we'll do in this file is the following: import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( - req: NextApiRequest, - res: NextApiResponse, + request: NextApiRequest, + response: NextApiResponse, ) { - if (req.method === "GET") { - return get(res); - } else if (req.method === "POST") { - return await post(req, res); - } else { - return res.status(405).json({ error: "Method not allowed" }); + if (request.method === "GET") { + return get(response); } + if (request.method === "POST") { + return await post(request, response); + } + return response.status(405).json({ error: "Method not allowed" }); } -function get(res: NextApiResponse) {} +function get(response: NextApiResponse) {} -async function post(req: NextApiRequest, res: NextApiResponse) {} +async function post(request: NextApiRequest, response: NextApiResponse) {} ``` #### 4. Update `get` function @@ -519,8 +517,8 @@ endpoint to return a label and icon. Update the `get` function to send a response with a "Scavenger Hunt!" label and a Solana logo icon. ```jsx -function get(res: NextApiResponse) { - res.status(200).json({ +function get(response: NextApiResponse) { + response.status(200).json({ label: "Scavenger Hunt!", icon: "https://solana.com/src/img/branding/solanaLogoMark.svg", }); @@ -561,35 +559,31 @@ import { NextApiRequest, NextApiResponse } from "next" import { PublicKey, Transaction } from "@solana/web3.js" ... -async function post(req: NextApiRequest, res: NextApiResponse) { - const { account } = req.body - const { reference, id } = req.query - - if (!account || !reference || !id) { - res.status(400).json({ error: "Missing required parameter(s)" }) - return - } - - try { - const transaction = await buildTransaction( - new PublicKey(account), - new PublicKey(reference), - id.toString() - ) - - res.status(200).json({ - transaction: transaction, - message: `You've found location ${id}!`, - }) - } catch (err) { - console.log(err) - let error = err as any - if (error.message) { - res.status(200).json({ transaction: "", message: error.message }) - } else { - res.status(500).json({ error: "error creating transaction" }) - } - } +async function post(request: NextApiRequest, response: NextApiResponse) { + const { account } = request.body; + const { reference, id } = request.query; + + if (!account || !reference || !id) { + response.status(400).json({ error: "Missing required parameter(s)" }); + return; + } + + try { + const transaction = await buildTransaction( + new PublicKey(account), + new PublicKey(reference), + id.toString(), + ); + + response.status(200).json({ + transaction: transaction, + message: `You've found location ${id}!`, + }); + } catch (error) { + console.log(error); + response.status(500).json({ transaction: "", message: error.message }); + return; + } } async function buildTransaction( @@ -769,9 +763,8 @@ function verifyCorrectLocation( if (!lastLocation || currentLocation.index !== lastLocation.index + 1) { return false; - } else { - return true; } + return true; } ``` From cd05d15a8933198a2db7e56134e505945e273d97 Mon Sep 17 00:00:00 2001 From: danielAsaboro Date: Fri, 4 Oct 2024 08:17:23 +0100 Subject: [PATCH 39/54] Generalized state compression (#470) * chore: updated lesson title, and summary * chore: updated lesson and theoretical overview of state compression * chore: updated lesson on concurrent merkle tree and broke it down into smaller sections for easy understanding * chore: updated lesson on data access in a state-compressed program and tooling * chore: updated lesson on indexing data for easy lookup * chore: updated lesson on creating rust types for compression development process * chore: updated lesson on initializing a new tree for compression development process * chore: updated lesson on adding hashes to the tree for compression development process * chore: updated lesson on adding hashes for compression development process * chore: updated lesson on updating and deleting hashes for compression development process * chore: updated lesson on updating and deleting hashes for compression development process * chore: updated lesson on accessing data from a client for compression development process * chore: update project setup in Lab section for Building a Note-Taking App with Generalized State Compression * chore: update step 2: define note schema in Lab section for Building a Note-Taking App with Generalized State Compression * chore: update step 3: define input account and constraints - in Lab section for Building a Note-Taking App with Generalized State Compression * chore: update step 4: create "create_note_tree" instruction - in Lab section for Building a Note-Taking App with Generalized State Compression * chore: update step 5: create "append_note_tree" instructuion - in Labs section for buiding a Note-Taking App with Generalized State Compression * chore: update step 6: create "update_note" instruction - in Lab section for Building a Note-Taking App with Generalized State Compression * chore: update step 7: "client test setup" instruction - in Lab section for Building a Note-Taking App with Generalized State Compression * fix: update step 7 for more info into the utility file provided * chore: update step 8: "write client test" instruction - in Lab section for Building a Note-Taking App with Generalized State Compression * chore: update challenge section in Lab section for Building a Note-Taking App with Generalized State Compression * fix: fixed content formatting issues * fix: used grammarly to fix grammar issues * fix: fixed on-chain to onchain * fix: fixed lack of objectives semantic tag after title section * fix: fixed description semantic tag issue * Update content/courses/state-compression/generalized-state-compression.md Fixed Merkle tree description summary Co-authored-by: Mike MacCana * fix: fixed concurrent tree explanation markdown * fix: fixed grammar issue in last intro to theoretical overview of state compression * fix deterministic with predictably * Update content/courses/state-compression/generalized-state-compression.md Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md Made the intro more clearer Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md reinforce that the final hash is called a Merkle root Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md removed redundant explanation of merkle tree Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md fixed redundant word "entire" Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md called out the concurrent modifier in a merkle tree Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md removed redundant establishment of concurrent merkle tree as the solution to dealing with multiple transactions in the same slot Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md remove redundant explanation of a concurrent merkle tree Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md corrected incorrect information of what is logged to the Noop Program Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md clarified what the Solana Noop Program is to the reader Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md added code blocks to file names, config option, and package names Co-authored-by: Mike MacCana * Update content/courses/state-compression/generalized-state-compression.md rename functions that handle instructions as function handlers as per the CONTRIBUTING.md guidelines Co-authored-by: Mike MacCana * fix: fixed formatting issues * fix: broke down content * fix: reflected Mikes suggestion * fix: fixed all rust content and addded extra documentation * fix: updated the client side test, and made more explicit for clarity * fix: fixed grammar issues with header semantic tag content * fix: rephrased what state compression is per mikes suggestion * fix: fix grammar and vocab issues * fix: fixed instructional handler issue * fix: fixed redundancies in test files * fix: redirected them to main branch from solution branch * fix: added package.json and yarn lock * revert: revert package json and yarn lock to main state --------- Co-authored-by: Mike MacCana --- .../generalized-state-compression.md | 1711 ++++++++++------- package.json | 2 +- yarn.lock | 2 +- 3 files changed, 998 insertions(+), 717 deletions(-) diff --git a/content/courses/state-compression/generalized-state-compression.md b/content/courses/state-compression/generalized-state-compression.md index f91169e38..4e7b04cb6 100644 --- a/content/courses/state-compression/generalized-state-compression.md +++ b/content/courses/state-compression/generalized-state-compression.md @@ -1,309 +1,374 @@ --- -title: Generalized State Compression +title: Generalized State Compression objectives + objectives: - - Explain the logic flow behind Solana state compression + - Explain the flow of Solana’s state compression logic. - Explain the difference between a Merkle tree and a concurrent Merkle tree - - Implement generic state compression in basic Solana programs + - Implement generic state compression in a basic Solana program + description: - "How state compression - the tech behind compressed NFTs - works, and how to - implement it in your own Solana programs." + Understand how state compression - the technology behind compressed NFTs works + - and learn how to apply it in your Solana programs. --- ## Summary -- State Compression on Solana is most commonly used for compressed NFTs, but - it's possible to use it for arbitrary data -- State Compression lowers the amount of data you have to store onchain by - leveraging Merkle trees. -- Merkle trees store a single hash that represents an entire binary tree of - hashes. Each leaf on a Merkle tree is a hash of that leaf's data. -- Concurrent Merkle trees are a specialized version of Merkle trees that allow - concurrent updates. -- Because data in a state-compressed program is not stored onchain, you have to - user indexers to keep an offchain cache of the data and then verify that data - against the onchain Merkle tree. +- State compression on Solana is primarily used for compressed NFTs (cNFTs), but + it can be applied to any data type +- State Compression lowers the amount of data you have to store onchain using + Merkle trees. +- A Merkle tree compresses data by hashing pairs of data repeatedly until a + single root hash is produced. This root hash is then stored onchain. +- Each leaf on a Merkle tree is a hash of that leaf’s data. +- A concurrent Merkle tree is a specialized version of a Merkle tree. Unlike a + standard Merkle tree, it allows multiple updates simultaneously without + affecting transaction validity. +- Data in a state-compressed program is not stored onchain. So you have to use + indexers to keep an offchain cache of the data. It’s this offchain cache data + that is used to then verify against the onchain Merkle tree. ## Lesson -Previously, we discussed state compression in the context of compressed NFTs. At -the time of writing, compressed NFTs represent the most common use case for -state compression, but it's possible to use state compression within any -program. In this lesson, we'll discuss state compression in more generalized -terms so that you can apply it to any of your programs. +Previously, we talked about state compression in the context of compressed NFTs. + +While compressed NFTs are the main use case for state compression, you can apply +state compression to any Solana program. In this lesson, we’ll discuss state +compression in general terms so you can use it across your Solana projects. ### A theoretical overview of state compression -In traditional programs, data is serialized (typically using borsh) and then -stored directly in an account. This allows the data to be easily read and -written through Solana programs. You can “trust” the data stored in the accounts -because it can't be modified except through the mechanisms surfaced by the -program. - -State compression effectively asserts that the most important piece of this -equation is how “trustworthy” the data is. If all we care about is the ability -to trust that data is what it claims to be, then we can actually get away with -**_not_** storing the data in an account onchain. Instead, we can store hashes -of the data where the hashes can be used to prove or verify the data. The data -hash takes up significantly less storage space than the data itself. We can then -store the actual data somewhere much cheaper and worry about verifying it -against the onchain hash when the data is accessed. - -The specific data structure used by the Solana State Compression program is a -special binary tree structure known as a **concurrent Merkle tree**. This tree -structure hashes pieces of data together in a deterministic way to compute a -single, final hash that gets stored onchain. This final hash is significantly -smaller in size than all the original data combined, hence the “compression.” -The steps to this process are: - -1. Take any piece of data -2. Create a hash of this data -3. Store this hash as a “leaf” at the bottom of the tree -4. Each leaf pair is then hashed together, creating a “branch” -5. Each branch is then hashed together -6. Continually climb the tree and hash adjacent branches together -7. Once at the top of the tree, a final ”root hash” is produced -8. Store the root hash onchain as verifiable proof of the data within each leaf -9. Anyone wanting to verify that the data they have matches the “source of - truth” can go through the same process and compare the final hash without - having to store all the data onchain - -This involves a few rather serious development tradeoffs: - -1. Since the data is no longer stored in an account onchain, it is more - difficult to access. -2. Once the data has been accessed, developers must decide how often their - applications will verify the data against the onchain hash. -3. Any changes to the data will require sending the entirety of the previously - hashed data _and_ the new data into an instruction. Developer may also have - to provide additional data relevant to the proofs required to verify the - original data against the hash. - -Each of these will be a consideration when determining **if**, **when**, and -**how** to implement state compression for your program. +Normally, data in Solana programs is serialized (usually with borsh) and stored +directly in an account. This makes it easy to read and write the data through +the program. The account data is trustworthy because only the program can modify +it. + +However to verify the integrity of the data, then there’s no need to store the +actual data onchain. Instead, we can store hashes of the data, which can be used +to prove or verify its accuracy. This is called _state compression_. + +These hashes take up far less storage space than the original data. The full +data can be stored in a cheaper, offchain location, and only needs to be +verified against the onchain hash when accessed. + +The Solana State Compression program uses a Solana State Compression program +known as a **concurrent Merkle tree**. A concurrent Merkle tree is a special +kind of binary tree that deterministically hashes data, i.e. the same inputs +will always produce the same Merkle root. + +The final hash, called a _Merkle root_, is significantly smaller in size than +all the original full data sets combined. This is why it’s called "compression". +And it’s this hash that’s stored onchain. + +**Outlined below are the steps to this process, in order:** + +1. Take a piece of data. +2. Create a hash of that data. +3. Store the hash as a "leaf" at the bottom of the tree. +4. Hash pairs of leaves together to create branches. +5. Hash pairs of branches together. +6. Repeat this process until you reach the top of the tree. +7. The top of the tree contains a final "root hash." +8. Store this root hash onchain as proof of the data. +9. To verify the data, recompute the hashes and compare the final hash to the + onchain root hash. + +This method comes with some trade-offs: + +1. The data isn’t stored onchain, so it’s harder to access. +2. Developers must decide how often to verify the data against the onchain hash. +3. If the data changes, the entire data set must be sent to the program, along + with the new data. You’ll also need proof that the data matches the hash. + +These considerations will guide you when deciding whether, when, and how to +implement state compression in your programs. With that quick overview, let’s go +into more technical detail. #### Concurrent Merkle trees -A **Merkle tree** is a binary tree structure represented by a single hash. Every -leaf node in the structure is a hash of its inner data while every branch is a -hash of its child leaf hashes. In turn, branches are also hashed together until, -eventually, one final root hash remains. - -Since the Merkle tree is represented as a single hash, any modification to leaf -data changes the root hash. This causes an issue when multiple transactions in -the same slot are attempting to modify leaf data. Since these transactions must -execute in series, all but the first will fail since the root hash and proof -passed in will have been invalidated by the first transaction to be executed. In -other words, a standard Merkle tree can only modify a single leaf per slot. In a -hypothetical state-compressed program that relies on a single Merkle tree for -its state, this severely limits throughput. - -This can be solved with a **concurrent Merkle tree**. A concurrent Merkle -tree is a Merkle tree that stores a secure changelog of the most recent changes -along with their root hash and the proof to derive it. When multiple -transactions in the same slot try to modify leaf data, the changelog can be used -as a source of truth to allow for concurrent changes to be made to the tree. - -In other words, while an account storing a Merkle tree would have only the root -hash, a concurrent Merkle tree will also contain additional data that allows -subsequent writes to successfully occur. This includes: - -1. The root hash - The same root hash that a standard Merkle tree has. -2. A changelog buffer - This buffer contains proof data pertinent to recent root - hash changes so that subsequent writes in the same slot can still be - successful. -3. A canopy - When performing an update action on any given leaf, you need the - entire proof path from that leaf to the root hash. The canopy stores - intermediate proof nodes along that path so they don't all have to be passed - into the program from the client. - -As a program architect, you control three values directly related to these three -items. Your choice determines the size of the tree, the cost to create the tree, -and the number of concurrent changes that can be made to the tree: - -1. Max depth -2. Max buffer size -3. Canopy depth - -The **max depth** is the maximum number of hops to get from any leaf to the root -of the tree. Since Merkle trees are binary trees, every leaf is connected only -to one other leaf. Max depth can then logically be used to calculate the number -of nodes for the tree with `2 ^ maxDepth`. - -The **max buffer size** is effectively the maximum number of concurrent changes -that you can make to a tree within a single slot with the root hash still being -valid. When multiple transactions are submitted in the same slot, each of which -is competing to update leafs on a standard Merkle tree, only the first to run -will be valid. This is because that “write” operation will modify the hash -stored in the account. Subsequent transactions in the same slot will be trying -to validate their data against a now-outdated hash. A concurrent Merkle tree has -a buffer so that the buffer can keep a running log of these modifications. This -allows the State Compression Program to validate multiple data writes in the -same slot because it can look up what the previous hashes were in the buffer and -compare against the appropriate hash. - -The **canopy depth** is the number of proof nodes that are stored onchain for -any given proof path. Verifying any leaf requires the complete proof path for -the tree. The complete proof path is made up of one proof node for every “layer” -of the tree, i.e. a max depth of 14 means there are 14 proof nodes. Every proof -node passed into the program adds 32 bytes to a transaction, so large trees -would quickly exceed the maximum transaction size limit. Caching proof nodes -onchain in the canopy helps improve program composability. - -Each of these three values, max depth, max buffer size, and canopy depth, comes -with a tradeoff. Increasing the value of any of these values increases the size -of the account used to store the tree, thus increasing the cost of creating the -tree. - -Choosing the max depth is fairly straightforward as it directly relates to the -number of leafs and therefore the amount of data you can store. If you need 1 -million cNFTs on a single tree where each cNFT is a leaf of the tree, find the -max depth that makes the following expression true: `2^maxDepth > 1 million`. -The answer is 20. - -Choosing a max buffer size is effectively a question of throughput: how many -concurrent writes do you need? The larger the buffer, the higher the throughput. - -Lastly, the canopy depth will determine your program's composability. State -compression pioneers have made it clear that omitting a canopy is a bad idea. -Program A can't call your state-compressed program B if doing so maxes out the -transaction size limits. Remember, program A also has required accounts and data -in addition to required proof paths, each of which take up transaction space. - -#### Data access on a state-compressed program - -A state-compressed account doesn't store the data itself. Rather, it stores the -concurrent Merkle tree structure discussed above. The raw data itself lives only -in the blockchain's cheaper **ledger state.** This makes data access somewhat -more difficult, but not impossible. - -The Solana ledger is a list of entries containing signed transactions. In -theory, this can be traced back to the genesis block. This effectively means any -data that has ever been put into a transaction exists in the ledger. - -Since the state compression hashing process occurs onchain, all the data exists -in the ledger state and could theoretically be retrieved from the original -transaction by replaying the entire chain state from the beginning. However, -it's much more straightforward (though still complicated) to have -an **indexer** track and index this data as the transactions occur. This ensures -there is an offchain “cache” of the data that anyone can access and subsequently -verify against the onchain root hash. - -This process is complex, but it will make sense after some practice. - -### State compression tooling - -The theory described above is essential to properly understanding state -compression. But you don't have to implement any of it from scratch. Brilliant -engineers have laid most of the groundwork for you in the form of the SPL State -Compression Program and the Noop Program. +Since a Merkle tree is represented as a single hash, any change to a leaf node +alters the root hash. This becomes problematic when multiple transactions in the +same slot try to update leaf data in the same slot. Since transactions are +executed serially i.e. one after the other — all but the first will fail since +the root hash and proof passed in will have been invalidated by the first +transaction executed. + +In short, a standard Merkle tree can only handle one leaf update per +[slot](https://solana.com/docs/terminology#slot). This significantly limits the +throughput in a state-compressed program that depends on a single Merkle tree +for its state. + +Thankfully, this issue can be addressed using a _concurrent_ Merkle tree. Unlike +a regular Merkle tree, a concurrent Merkle tree keeps a secure changelog of +recent updates, along with their root hash and the proof needed to derive it. +When multiple transactions in the same slot attempt to modify leaf data, the +changelog serves as a reference, enabling concurrent updates to the tree. + +How does the concurrent Merkle tree achieve this? In a standard Merkle tree, +only the root hash is stored. However, a concurrent Merkle tree includes extra +data that ensures subsequent writes can succeed. + +This includes: + +1. The root hash - The same root hash found in a regular Merkle tree. +2. A changelog buffer - A buffer containing proof data for recent root hash + changes, allowing further writes in the same slot to succeed. +3. A canopy - To update a specific leaf, you need the entire proof path from the + leaf to the root hash. The canopy stores intermediate proof nodes along this + path so that not all of them need to be sent from the client to the program. + +### Key Parameters for Configuring a Concurrent Merkle Tree + +As a developer, you are responsible for controlling three key parameters that +directly affect the tree’s size, cost, and the number of concurrent changes it +can handle: + +1. **Max Depth** +2. **Max Buffer Size** +3. **Canopy Depth** + +Let’s take a brief overview of each parameter. + +#### Max Depth + +The **max depth** determines how many levels or "hops" are required to reach the +root of the tree from any leaf. Since Merkle trees are structured as binary +trees, where each leaf is paired with only one other leaf, the max depth can be +used to calculate the total number of nodes in the tree with the formula: +`2^maxDepth`. + +Here’s a quick TypeScript function for illustration: + +```typescript +const getMaxDepth = (itemCount: number) => { + if (itemCount === 0) { + return 0; + } + return Math.ceil(Math.log2(itemCount)); +}; +``` + +A max depth of 20 would allow for over one million leaves, making it suitable +for storing large datasets like NFTs. + +#### Max Buffer Size + +The **max buffer size** controls how many concurrent updates can be made to the +tree within a single slot while keeping the root hash valid. In a standard +Merkle tree, only the first transaction in a slot would be successful since it +updates the root hash, causing all subsequent transactions to fail due to hash +mismatches. However, in a concurrent Merkle tree, the buffer maintains a log of +changes, allowing multiple transactions to update the tree simultaneously by +checking the appropriate root hash from the buffer. A larger buffer size +increases throughput by enabling more concurrent changes. + +#### Canopy Depth + +The **canopy depth** specifies how many proof nodes are stored onchain for any +given proof path. To verify any leaf in the tree, you need a complete proof +path, which includes one proof node for every layer of the tree. For a tree with +a max depth of 14, there will be 14 proof nodes in total. Each proof node adds +32 bytes to the transaction, and without careful management, large trees could +exceed the transaction size limit. + +Storing more proof nodes onchain (i.e., having a deeper canopy) allows other +programs to interact with your tree without exceeding transaction limits, but it +also uses more onchain storage. Consider the complexity of interactions with +your tree when deciding on an appropriate canopy depth. + +### Balancing Trade-offs + +These three values—max depth, max buffer size, and canopy depth—all come with +trade-offs. Increasing any of them will enlarge the account used to store the +tree, raising the cost of creating the tree. + +- **Max Depth:** This is straightforward to determine based on how much data + needs to be stored. For example, if you need to store 1 million compressed + NFTs (cNFTs), where each cNFT is a leaf, you would need a max depth of 20 + (`2^maxDepth > 1 million`). +- **Max Buffer Size:** The choice of buffer size is mainly a question of + throughput—how many concurrent updates are required? A larger buffer allows + for more updates in the same slot. +- **Canopy Depth:** A deeper canopy improves composability, enabling other + programs to interact with your state-compressed program without exceeding + transaction size limits. Omitting the canopy is discouraged, as it could cause + issues with transaction size, especially when other programs are involved. + +### Data Access in a State-Compressed Program + +In a state-compressed program, the actual data isn’t stored directly onchain. +Instead, the concurrent Merkle tree structure is stored, while the raw data +resides in the blockchain’s more affordable ledger state. This makes accessing +the data more challenging, but not impossible. + +The Solana ledger is essentially a list of entries containing signed +transactions, which can be traced back to the Genesis block theoretically. This +means any data that has ever been included in a transaction is stored in the +ledger. + +Since the state compression process happens onchain, all the data is still in +the ledger state. In theory, you could retrieve the original data by replaying +the entire chain state from the start. However, it’s far more practical (though +still somewhat complex) to use an indexer to track and index the data as the +transactions happen. This creates an offchain "cache" of the data that can be +easily accessed and verified against the onchain root hash. + +While this process may seem complex at first, it becomes clearer with practice. + +### State Compression Tooling + +While understanding the theory behind state compression is crucial, you don’t +have to build it all from scratch. Talented engineers have already developed +essential tools like the SPL State Compression Program and the Noop Program to +simplify the process. #### SPL State Compression and Noop Programs -The SPL State Compression Program exists to make the process of creating and -updating concurrent Merkle trees repeatable and composable throughout the Solana -ecosystem. It provides instructions for initializing Merkle trees, managing tree -leafs (i.e. add, update, remove data), and verifying leaf data. - -The State Compression Program also leverages a separate “no op” program whose -primary purpose is to make leaf data easier to index by logging it to the ledger -state. When you want to store compressed data, you pass it to the State -Compression program where it gets hashed and emitted as an “event” to the Noop -program. The hash gets stored in the corresponding concurrent Merkle tree, but -the raw data remains accessible through the Noop program's transaction logs. - -#### Index data for easy lookup - -Under normal conditions, you would typically access onchain data by fetching the -appropriate account. When using state compression, however, it's not so -straightforward. - -As mentioned above, the data now exists in the ledger state rather than in an -account. The easiest place to find the full data is in the logs of the Noop -instruction. Unfortunately, while this data will in a sense exist in the ledger -state forever, it will likely be inaccessible through validators after a certain -period of time. - -To save space and be more performant, validators don't retain every transaction -back to the genesis block. The specific amount of time you'll be able to access -the Noop instruction logs related to your data will vary based on the validator. -Eventually, you'll lose access to it if you're relying directly on instruction -logs. - -Technically, you *can* replay the transaction state back to the genesis block -but the average team isn't going to do that, and it certainly won't be -performant. The +The SPL State Compression Program is designed to streamline and standardize the +creation and management of concurrent Merkle trees across the Solana ecosystem. +It provides Instruction Handlers for initializing Merkle trees, handling tree +leaves (such as adding, updating, or removing data), and verifying the integrity +of leaf data. + +Additionally, the State Compression Program works in conjunction with a separate +"Noop" program. A [no-op program]() +does nothing - literally 'no operation.' The Solana Noop Program only logs data +to the ledger state, however that logging is essential to state compression: + +When you store compressed data, it’s passed to the State Compression Program, +which hashes the data and emits it as an "event" to the Noop Program. While the +hash is stored in the concurrent Merkle tree, the raw data can still be accessed +via the Noop Program’s transaction logs. + +### Indexing Data for Easy Lookup + +Typically, accessing onchain data is as simple as fetching the relevant account. +However, with state compression, it’s not that straightforward. + +As mentioned earlier, the data now resides in the ledger state rather than in an +account. The most accessible place to find the complete data is in the logs of +the Noop instruction. While this data remains in the ledger state indefinitely, +it may become inaccessible through validators after a certain period. + +Validators don’t store all transactions back to the Genesis block to save space +and improve performance. The length of time you can access Noop instruction logs +varies depending on the validator. Eventually, the logs will become unavailable +if you’re relying on direct access to them. + +In theory, it’s possible to replay transaction states back to the genesis block, +but this approach is impractical for most teams and isn’t efficient. Some RPC +providers have adopted the [Digital Asset Standard (DAS)](https://docs.helius.dev/compression-and-das-api/digital-asset-standard-das-api) -has been adopted by many RPC providers to enable efficient queries of compressed -NFTs and other assets. However, at the time of writing, it doesn't support -arbitrary state compression. Instead, you have two primary options: +to enable efficient querying of compressed NFTs and other assets. However, as of +now, DAS does not support arbitrary state compression. + +You essentially have two main options: -1. Use an indexing provider that will build a custom indexing solution for your - program that observes the events sent to the Noop program and stores the +1. Use an indexing provider to create a custom indexing solution for your + program, which will monitor the events sent to the Noop program and store the relevant data offchain. -2. Create your own pseudo-indexing solution that stores transaction data - offchain. +2. Build your indexing solution that stores transaction data offchain. -For many dApps, option 2 makes plenty of sense. Larger-scale applications may -need to rely on infrastructure providers to handle their indexing. +For many dApps, option 2 can be a practical choice. Larger-scale applications, +however, may need to rely on infrastructure providers to manage their indexing +needs. -### State compression development process +### State Compression Development Process -#### Create Rust types +#### Create Rust Types -As with a typical Anchor program, one of the first things you should do is -define your program's Rust types. However, Rust types in a traditional Anchor -program often represent accounts. In a state-compressed program, your account -state will only store the Merkle tree. The more “usable” data schema will just -be serialized and logged to the Noop program. +In a typical Anchor program, developers often start by defining the Rust types +that represent accounts. For a state-compressed program, however, the focus +shifts to defining types that align with the Merkle tree structure. -This type should include all the data stored in the leaf node and any contextual -information needed to make sense of the data. For example, if you were to create -a simple messaging program, your `Message` struct might look as follows: +In state compression, your onchain account will primarily store the Merkle tree. +The more practical data will be serialized and logged to the Noop program for +easier access and management. Your Rust types should encompass all data stored +in the leaf nodes and any contextual information necessary for interpreting that +data. For instance, if you’re developing a simple messaging program, your +`Message` struct might look something like this: ```rust -#[derive(AnchorSerialize)] +const DISCRIMINATOR_SIZE: usize = 8; +const PUBKEY_SIZE: usize = 32; + +/// A log entry for messages sent between two public keys. +#[derive(AnchorSerialize, AnchorDeserialize)] pub struct MessageLog { - leaf_node: [u8; 32], // The leaf node hash - from: Pubkey, // Pubkey of the message sender - to: Pubkey, // Pubkey of the message recipient - message: String, // The message to send + /// The leaf node hash for message logging. + pub leaf_node: [u8; DISCRIMINATOR_SIZE + PUBKEY_SIZE], + /// The public key of the message sender. + pub from: Pubkey, + /// The public key of the message recipient. + pub to: Pubkey, + /// The actual message content. + pub message: String, } -impl MessageLog { - // Constructs a new message log from given leaf node and message - pub fn new(leaf_node: [u8; 32], from: Pubkey, to: Pubkey, message: String) -> Self { - Self { leaf_node, from, to, message } - } +/// Constructs a new `MessageLog`. +/// +/// # Arguments +/// +/// * `leaf_node` - A 32-byte array representing the leaf node hash. +/// * `from` - The public key of the message sender. +/// * `to` - The public key of the message recipient. +/// * `message` - The message to be sent. +/// +/// # Returns +/// +/// Returns a new `MessageLog` instance. +pub fn new_message_log(leaf_node: [u8; DISCRIMINATOR_SIZE + PUBKEY_SIZE], from: Pubkey, to: Pubkey, message: String) -> MessageLog { + MessageLog { leaf_node, from, to, message } } ``` -To be abundantly clear, **this is not an account that you will be able to read -from**. Your program will be creating an instance of this type from instruction -inputs, not constructing an instance of this type from account data that it -reads. We'll discuss how to read data in a later section. +To be absolutely clear, the **`MessageLog` is not an account you will read +from**. Instead, your program will create an instance of `MessageLog` using +inputs from Instructions Handler, rather than constructing it from data read +from an account. We will cover how to read data from compressed accounts later. -#### Initialize a new tree +#### Initialize a New Tree -Clients will create and initialize the Merkle tree account in two separate -instructions. The first is simply allocating the account by calling System -Program. The second will be an instruction that you create on a custom program -that initializes the new account. This initialization is effectively just -recording what the max depth and buffer size for the Merkle tree should be. +To set up a new Merkle tree, clients need to perform two distinct steps. -All this instruction needs to do is build a CPI to invoke the -`init_empty_merkle_tree` instruction on the State Compression Program. Since -this requires the max depth and max buffer size, these will need to be passed in -as arguments to the instruction. +1. First, they allocate the account by calling the System Program. +2. Next, they use a custom program to initialize the new account. This + initialization involves setting the maximum depth and buffer size for the + Merkle tree. -Remember, the max depth refers to the maximum number of hops to get from any -leaf to the root of the tree. Max buffer size refers to the amount of space -reserved for storing a changelog of tree updates. This changelog is used to -ensure that your tree can support concurrent updates within the same block. +The initialization Instruction Handler must create a CPI (Cross-Program +Invocation) to call the `init_empty_merkle_tree` instruction from the State +Compression Program. You’ll need to provide the maximum depth and buffer size as +arguments to this instruction Handler. -For example, if we were initializing a tree for storing messages between users, -the instruction might look like this: +- **Max depth**: Defines the maximum number of hops needed to travel from any + leaf to the root of the tree. +- **Max buffer size**: Specifies the space allocated for storing a changelog of + tree updates. This changelog is essential for supporting concurrent updates + within the same block. + +For instance, if you are initializing a tree to store messages between users, +your Instruction Handler might look like this: ```rust +/// Initializes an empty Merkle tree for storing messages with a specified depth and buffer size. +/// +/// This function creates a CPI (Cross-Program Invocation) call to initialize the Merkle tree account +/// using the provided authority and compression program. The PDA (Program Derived Address) seeds are used for +/// signing the transaction. +/// +/// # Arguments +/// +/// * `ctx` - The context containing the accounts required for Merkle tree initialization. +/// * `max_depth` - The maximum depth of the Merkle tree. +/// * `max_buffer_size` - The maximum buffer size of the Merkle tree. +/// +/// # Returns +/// +/// This function returns a `Result<()>`, indicating success or failure. +/// +/// # Errors +/// +/// This function will return an error if the CPI call to `init_empty_merkle_tree` fails. pub fn create_messages_tree( ctx: Context, max_depth: u32, // Max depth of the Merkle tree @@ -311,130 +376,185 @@ pub fn create_messages_tree( ) -> Result<()> { // Get the address for the Merkle tree account let merkle_tree = ctx.accounts.merkle_tree.key(); - // Define the seeds for pda signing - let signer_seeds: &[&[&[u8]]] = &[ + + // The seeds for PDAs signing + let signers_seeds: &[&[&[u8]]] = &[ &[ - merkle_tree.as_ref(), // The address of the Merkle tree account as a seed - &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda + merkle_tree.as_ref(), // The address of the Merkle tree account + &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the PDA ], ]; - // Create cpi context for init_empty_merkle_tree instruction. + // Create CPI context for `init_empty_merkle_tree` instruction handler let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), // The spl account compression program + ctx.accounts.compression_program.to_account_info(), // The SPL account compression program Initialize { authority: ctx.accounts.tree_authority.to_account_info(), // The authority for the Merkle tree, using a PDA merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be initialized noop: ctx.accounts.log_wrapper.to_account_info(), // The noop program to log data }, - signer_seeds // The seeds for pda signing + signers_seeds // The seeds for PDAs signing ); - // CPI to initialize an empty Merkle tree with given max depth and buffer size + // CPI to initialize an empty Merkle tree with the given max depth and buffer size init_empty_merkle_tree(cpi_ctx, max_depth, max_buffer_size)?; Ok(()) } ``` -#### Add hashes to the tree - -With an initialized Merkle tree, it's possible to start adding data hashes. This -involves passing the uncompressed data to an instruction on your program that -will hash the data, log it to the Noop program, and use the State Compression -Program's `append` instruction to add the hash to the tree. The following -discuss what your instruction needs to do in depth: - -1. Use the `hashv` function from the `keccak` crate to hash the data. In most - cases, you'll want to also hash the owner or authority of the data as well to - ensure that it can only be modified by the proper authority. -2. Create a log object representing the data you wish to log to the Noop - Program, then call `wrap_application_data_v1` to issue a CPI to the Noop - program with this object. This ensures that the uncompressed data is readily - available to any client looking for it. For broad use cases like cNFTs, that - would be indexers. You might also create your own observing client to - simulate what indexers are doing but specific to your application. -3. Build and issue a CPI to the State Compression Program's `append` - instruction. This takes the hash computed in step 1 and adds it to the next - available leaf on your Merkle tree. Just as before, this requires the Merkle - tree address and the tree authority bump as signature seeds. - -When all this is put together using the messaging example, it looks something -like this: +#### Adding Hashes to the Tree + +Once the Merkle tree is initialized, you can begin adding data hashes to it. +This process involves passing the uncompressed data to an Instruction handler +within your program, which will hash the data, log it to the Noop Program, and +then use the State Compression Program’s `append` instruction to add the hash to +the tree. Here’s how the Instruction Handler operates in detail: + +1. **Hash the Data**: Use the `hashv` function from the `keccak` crate to hash + the data. It’s recommended to include the data owner or authority in the hash + to ensure that only the proper authority can modify it. +2. **Log the Data**: Create a log object representing the data you want to log + to the Noop Program. Then, call `wrap_application_data_v1` to issue a CPI + (Cross-Program Invocation) to the Noop Program with this object. This makes + the uncompressed data easily accessible to any client, such as indexers, that + may need it. You could also develop a custom client to observe and index data + for your application specifically. + +3. **Append the Hash**: Construct and issue a CPI to the State Compression + Program’s `append` Instruction. This will take the hash generated in step 1 + and append it to the next available leaf on the Merkle tree. As with previous + steps, this requires the Merkle tree address and tree authority bump as + signature seeds. + +When applied to a messaging system, the resulting implementation might look like +this: ```rust -// Instruction for appending a message to a tree. +/// Appends a message to the Merkle tree. +/// +/// This function hashes the message and the sender’s public key to create a leaf node, +/// logs the message using the noop program, and appends the leaf node to the Merkle tree. +/// +/// # Arguments +/// +/// * `ctx` - The context containing the accounts required for appending the message. +/// * `message` - The message to append to the Merkle tree. +/// +/// # Returns +/// +/// This function returns a `Result<()>`, indicating success or failure. +/// +/// # Errors +/// +/// This function will return an error if any of the CPI calls (logging or appending) fail. pub fn append_message(ctx: Context, message: String) -> Result<()> { - // Hash the message + whatever key should have update authority + // Hash the message + sender’s public key to create a leaf node let leaf_node = keccak::hashv(&[message.as_bytes(), ctx.accounts.sender.key().as_ref()]).to_bytes(); - // Create a new "message log" using the leaf node hash, sender, receipient, and message - let message_log = MessageLog::new(leaf_node.clone(), ctx.accounts.sender.key().clone(), ctx.accounts.receipient.key().clone(), message); - // Log the "message log" data using noop program + + // Create a new "MessageLog" using the leaf node hash, sender, recipient, and message + let message_log = new_message_log( + leaf_node.clone(), + ctx.accounts.sender.key().clone(), + ctx.accounts.recipient.key().clone(), + message, + ); + + // Log the "MessageLog" data using the noop program wrap_application_data_v1(message_log.try_to_vec()?, &ctx.accounts.log_wrapper)?; - // Get the address for the Merkle tree account + + // Get the Merkle tree account address let merkle_tree = ctx.accounts.merkle_tree.key(); - // Define the seeds for pda signing - let signer_seeds: &[&[&[u8]]] = &[ + + // The seeds for PDAs signing + let signers_seeds: &[&[&[u8]]] = &[ &[ merkle_tree.as_ref(), // The address of the Merkle tree account as a seed - &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda + &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the PDA ], ]; - // Create a new cpi context and append the leaf node to the Merkle tree. + + // Create a CPI context and append the leaf node to the Merkle tree let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), // The spl account compression program + ctx.accounts.compression_program.to_account_info(), // The SPL account compression program Modify { - authority: ctx.accounts.tree_authority.to_account_info(), // The authority for the Merkle tree, using a PDA + authority: ctx.accounts.tree_authority.to_account_info(), // Authority for the Merkle tree, using a PDA merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be modified noop: ctx.accounts.log_wrapper.to_account_info(), // The noop program to log data }, - signer_seeds // The seeds for pda signing + signers_seeds, // The seeds for PDAs signing ); - // CPI to append the leaf node to the Merkle tree + + // CPI call to append the leaf node to the Merkle tree append(cpi_ctx, leaf_node)?; + Ok(()) } ``` -#### Update hashes +#### Updating Hashes -To update data, you need to create a new hash to replace the hash at the -relevant leaf on the Merkle tree. To do this, your program needs access to four -things: +To update a leaf in a Merkle tree, you’ll need to generate a new hash to replace +the existing one. This process requires four key inputs: -1. The index of the leaf to update +1. The index of the leaf you wish to update 2. The root hash of the Merkle tree -3. The original data you wish to modify +3. The original data you want to modify 4. The updated data -Given access to this data, a program instruction can follow very similar steps -as those used to append the initial data to the tree: - -1. **Verify update authority** - The first step is new. In most cases, you want - to verify update authority. This typically involves proving that the signer - of the `update` transaction is the true owner or authority of the leaf at the - given index. Since the data is compressed as a hash on the leaf, we can't - simply compare the `authority` public key to a stored value. Instead, we need - to compute the previous hash using the old data and the `authority` listed in - the account validation struct. We then build and issue a CPI to the State - Compression Program's `verify_leaf` instruction using our computed hash. -2. **Hash the new data** - This step is the same as the first step from - appending initial data. Use the `hashv` function from the `keccak` crate to - hash the new data and the update authority, each as their corresponding byte +Using these inputs, you can follow a series of steps similar to those used when +initially appending data to the tree: + +1. **Verify Update Authority**: The first step, unique to updates, is to verify + the authority of the entity making the update. This generally involves + checking that the signer of the `update` transaction is indeed the owner or + authority of the leaf at the specified index. Since the data in the leaf is + hashed, you can’t directly compare the authority’s public key to a stored + value. Instead, compute the previous hash using the old data and the + `authority` listed in the account validation struct. Then, invoke a CPI to + the State Compression Program’s `verify_leaf` instruction to confirm the hash + matches. + +2. **Hash the New Data**: This step mirrors the hashing process for appending + data. Use the `hashv` function from the `keccak` crate to hash the new data + and the update authority, converting each to its corresponding byte representation. -3. **Log the new data** - This step is the same as the second step from - appending initial data. Create an instance of the log struct and call - `wrap_application_data_v1` to issue a CPI to the Noop program. -4. **Replace the existing leaf hash** - This step is slightly different than the - last step of appending initial data. Build and issue a CPI to the State - Compression Program's `replace_leaf` instruction. This uses the old hash, the - new hash, and the leaf index to replace the data of the leaf at the given - index with the new hash. Just as before, this requires the Merkle tree - address and the tree authority bump as signature seeds. - -Combined into a single instruction, this process looks as follows: + +3. **Log the New Data**: As with the initial append operation, create a log + object to represent the new data, and use `wrap_application_data_v1` to + invoke the Noop Program via CPI. This ensures that the new uncompressed data + is logged and accessible offchain. + +4. **Replace the Existing Leaf Hash**: This step is slightly different from + appending new data. Here, you’ll need to invoke a CPI to the State + Compression Program’s `replace_leaf` instruction. This operation will replace + the existing hash at the specified leaf index with the new hash. You’ll need + to provide the old hash, the new hash, and the leaf index. As usual, the + Merkle tree address and tree authority bump are required as signature seeds. + +When combined, the instructions for updating a hash might look like this: ```rust +/// Updates a message in the Merkle tree. +/// +/// This function verifies the old message in the Merkle tree by checking its leaf node, +/// and then replaces it with a new message by modifying the Merkle tree’s leaf node. +/// +/// # Arguments +/// +/// * `ctx` - The context containing the accounts required for updating the message. +/// * `index` - The index of the leaf node to update. +/// * `root` - The root hash of the Merkle tree. +/// * `old_message` - The old message that is currently in the Merkle tree. +/// * `new_message` - The new message to replace the old message. +/// +/// # Returns +/// +/// This function returns a `Result<()>`, indicating success or failure. +/// +/// # Errors +/// +/// This function will return an error if verification or replacement of the Merkle tree leaf fails. pub fn update_message( ctx: Context, index: u32, @@ -442,59 +562,67 @@ pub fn update_message( old_message: String, new_message: String ) -> Result<()> { - let old_leaf = keccak - ::hashv(&[old_message.as_bytes(), ctx.accounts.sender.key().as_ref()]) - .to_bytes(); + // Hash the old message + sender’s public key to create the old leaf node + let old_leaf = keccak::hashv(&[old_message.as_bytes(), ctx.accounts.sender.key().as_ref()]).to_bytes(); + // Get the Merkle tree account address let merkle_tree = ctx.accounts.merkle_tree.key(); - // Define the seeds for pda signing - let signer_seeds: &[&[&[u8]]] = &[ + // The seeds for PDAs signing + let signers_seeds: &[&[&[u8]]] = &[ &[ merkle_tree.as_ref(), // The address of the Merkle tree account as a seed - &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda + &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the PDA ], ]; - // Verify Leaf + // Verify the old leaf node in the Merkle tree { + // If the old and new messages are the same, no update is needed if old_message == new_message { msg!("Messages are the same!"); return Ok(()); } + // Create CPI context for verifying the leaf node let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), // The spl account compression program + ctx.accounts.compression_program.to_account_info(), // The SPL account compression program VerifyLeaf { - merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be modified + merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be verified }, - signer_seeds // The seeds for pda signing + signers_seeds, // The seeds for PDAs signing ); - // Verify or Fails + + // Verify the old leaf node in the Merkle tree verify_leaf(cpi_ctx, root, old_leaf, index)?; } - let new_leaf = keccak - ::hashv(&[new_message.as_bytes(), ctx.accounts.sender.key().as_ref()]) - .to_bytes(); + // Hash the new message + sender’s public key to create the new leaf node + let new_leaf = keccak::hashv(&[new_message.as_bytes(), ctx.accounts.sender.key().as_ref()]).to_bytes(); - // Log out for indexers - let message_log = MessageLog::new(new_leaf.clone(), ctx.accounts.sender.key().clone(), ctx.accounts.recipient.key().clone(), new_message); - // Log the "message log" data using noop program + // Log the new message for indexers using the noop program + let message_log = new_message_log( + new_leaf.clone(), + ctx.accounts.sender.key().clone(), + ctx.accounts.recipient.key().clone(), + new_message, + ); wrap_application_data_v1(message_log.try_to_vec()?, &ctx.accounts.log_wrapper)?; - // replace leaf + // Replace the old leaf with the new leaf in the Merkle tree { + // Create CPI context for replacing the leaf node let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), // The spl account compression program + ctx.accounts.compression_program.to_account_info(), // The SPL account compression program Modify { authority: ctx.accounts.tree_authority.to_account_info(), // The authority for the Merkle tree, using a PDA merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be modified noop: ctx.accounts.log_wrapper.to_account_info(), // The noop program to log data }, - signer_seeds // The seeds for pda signing + signers_seeds, // The seeds for PDAs signing ); - // CPI to append the leaf node to the Merkle tree + + // Replace the old leaf node with the new one in the Merkle tree replace_leaf(cpi_ctx, root, old_leaf, new_leaf, index)?; } @@ -502,55 +630,63 @@ pub fn update_message( } ``` -#### Delete hashes +#### Deleting Hashes -At the time of writing, the State Compression Program doesn't provide an -explicit `delete` instruction. Instead, you'll want to update leaf data with -data that indicates the data as “deleted.” The specific data will depend on your -use case and security concerns. Some may opt to set all data to 0, whereas -others might store a static string that all “deleted” items will have in common. +As of now, the State Compression Program does not have a dedicated `delete` +instruction. -#### Access data from a client +Instead, you can simulate deletion by updating the leaf data with a value that +signals it has been "deleted." -The discussion so far has covered 3 of the 4 standard CRUD procedures: Create, -Update, and Delete. What's left is one of the more difficult concepts in state -compression: reading data. +The exact value you choose will depend on your specific use case and security +requirements. For some, this may involve setting all data fields to zero, while +others might prefer storing a predefined static string that marks the leaf as +deleted. This approach allows you to handle deletions in a way that suits your +application’s needs without compromising data integrity. -Accessing data from a client is tricky primarily because the data isn't stored -in a format that is easy to access. The data hashes stored in the Merkle tree -account can't be used to reconstruct the initial data, and the data logged to -the Noop program isn't available indefinitely. +#### Accessing Data from a Client -Your best bet is one of two options: +We’ve covered creating, updating, and deleting data in state compression, but +reading data presents its unique challenges. -1. Work with an indexing provider to create a custom indexing solution for your - program, then write client-side code based on how the indexer gives you - access to the data. -2. Create your own pseudo-indexer as a lighter-weight solution. +Accessing compressed data from a client can be tricky because the Merkle tree +stores only data hashes, which cannot be used to recover the original data. +Additionally, the uncompressed data logged to the Noop program is not retained +indefinitely. -If your project is truly decentralized such that many participants will interact -with your program through means other than your own frontend, then option 2 -might not be sufficient. However, depending on the scale of the project or -whether or not you'll have control over most program access, it can be a viable -approach. +To access this data, you generally have two options: -There is no “right” way to do this. Two potential approaches are: +1. **Work with an indexing provider** to develop a custom solution tailored to + your program. This allows you to write client-side code to retrieve and + access the data based on how the indexer provides it. +2. **Create your own pseudo-indexer** to store and retrieve the data, offering a + lighter-weight solution. -1. Store the raw data in a database at the same time as sending it to the - program, along with the leaf that the data is hashed and stored to. -2. Create a server that observes your program's transactions, looks up the - associated Noop logs, decodes the logs, and stores them. +If your project is decentralized and expects widespread interaction beyond your +frontend, option 2 might not be sufficient. However, if you have control over +most program interactions, this approach can work. -We'll do a little bit of both when writing tests in this lesson's lab (though we -won't persist data in a db - it will only live in memory for the duration of the -tests). +There’s no one-size-fits-all solution here. Two potential strategies include: -The setup for this is somewhat tedious. Given a particular transaction, you can -fetch the transaction from the RPC provider, get the inner instructions -associated with the Noop program, use the `deserializeApplicationDataEvent` -function from the `@solana/spl-account-compression` JS package to get the logs, -then deserialize them using Borsh. Below is an example based on the messaging -program used above. +1. **Store raw data**: One approach is to store the raw data in a database + simultaneously by sending it to the program. This allows you to keep a record + of the data, along with the Merkle tree leaf where the data was hashed and + stored. + +2. **Create a transaction observer**: Another approach is to create a server + that observes the transactions your program executes. This server would fetch + transactions, look up the related Noop logs, decode them, and store the data. + +When writing tests in the lab, we’ll simulate both of these approaches, although +instead of using a database, the data will be stored in memory for the test’s +duration. + +The process of setting this up can be a bit complex. For a given transaction, +you’ll retrieve it from the RPC provider, extract the inner instructions related +to the Noop program, and use the `deserializeApplicationDataEvent` function from +the `@solana/spl-account-compression` JS package to decode the logs. Then, +you’ll use Borsh to deserialize the data. Here’s an example from the messaging +program to illustrate the process: ```typescript export async function getMessageLog( @@ -612,18 +748,24 @@ export async function getMessageLog( ### Conclusion -Generalized state compression can be difficult but is absolutely possible to -implement with the available tools. Additionally, the tools and programs will -only get better over time. If you come up with solutions that improve your -development experience, please share with the community! +Implementing generalized state compression may be challenging, but it is +entirely achievable using the available tools. As the ecosystem evolves, these +tools and programs will continue to improve, making the process more +streamlined. If you discover solutions that enhance your development experience, +please don’t hesitate to share them with the community! + + +Remember to write comprehensive tests for your state compression implementation. This ensures your program behaves correctly and helps catch potential issues early in the development process. + -## Lab +## Lab: Building a Note-Taking App with Generalized State Compression -Let's practice generalized state compression by creating a new Anchor program. -This program will use custom state compression to power a simple note-taking -app. +In this lab, we’ll walk through the process of developing an Anchor program that +uses custom state compression to power a basic note-taking app. This will give +you hands-on experience in working with compressed data and help reinforce key +concepts around state compression on Solana. -#### 1. Project setup +#### 1. Set up the Project Start by initializing an Anchor program: @@ -631,8 +773,9 @@ Start by initializing an Anchor program: anchor init compressed-notes ``` -We'll be using the `spl-account-compression` crate with the `cpi` feature -enabled. Let's add it as a dependency in `programs/compressed-notes/Cargo.toml`. +Next, we’ll add the `spl-account-compression` crate with the `cpi` feature +enabled. To do this, update the `Cargo.toml` file located at +`programs/compressed-notes` by adding the following dependency: ```toml [dependencies] @@ -641,9 +784,12 @@ spl-account-compression = { version="0.2.0", features = ["cpi"] } solana-program = "1.16.0" ``` -We'll be testing locally but we need both the Compression program and the Noop -program from Mainnet. We'll need to add these to the `Anchor.toml` in the root -directory so they get cloned to our local cluster. +We’ll be running tests locally, but we’ll need both the State Compression +Program and the Noop Program from the Mainnet to do so. To make sure these +programs are available on our local cluster, we need to include them in the +`Anchor.toml` file located in the root directory. Here’s how you can add them: + +In `Anchor.toml`, update the programs section with the following entries: ```toml [test.validator] @@ -656,15 +802,15 @@ address = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV" address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" ``` -Lastly, let's prepare the `lib.rs` file for the rest of the Demo. Remove the -`initialize` instruction and the `Initialize` accounts struct, then add the -imports shown in the code snippet below (be sure to put in **_your_** program -id): +Finally, let’s set up the `lib.rs` file for the remainder of the demo. Start by +removing the `initialize` instruction and the `Initialize` accounts struct. +Next, add the necessary imports as indicated in the code snippet, making sure to +include **_your_** program ID. ```rust use anchor_lang::{ prelude::*, - solana_program::keccak + solana_program::keccak, }; use spl_account_compression::{ Noop, @@ -676,125 +822,216 @@ use spl_account_compression::{ wrap_application_data_v1, }; -declare_id!("YOUR_KEY_GOES_HERE"); - -// STRUCTS GO HERE +// Replace with your program ID +declare_id!("PROGRAM_PUBLIC_KEY_GOES_HERE"); +/// A program that manages compressed notes using a Merkle tree for efficient storage and verification. #[program] pub mod compressed_notes { use super::*; - // FUNCTIONS GO HERE + // Define your program instructions here. + + /// Initializes a new Merkle tree for storing messages. + /// + /// This function creates a Merkle tree with the specified maximum depth and buffer size. + /// + /// # Arguments + /// + /// * `ctx` - The context containing the accounts required for initializing the tree. + /// * `max_depth` - The maximum depth of the Merkle tree. + /// * `max_buffer_size` - The maximum buffer size of the Merkle tree. + pub fn create_messages_tree( + ctx: Context, + max_depth: u32, + max_buffer_size: u32, + ) -> Result<()> { + // Tree creation logic here + Ok(()) + } + + /// Appends a new message to the Merkle tree. + /// + /// This function hashes the message and adds it as a leaf node to the tree. + /// + /// # Arguments + /// + /// * `ctx` - The context containing the accounts required for appending the message. + /// * `message` - The message to append to the Merkle tree. + pub fn append_message(ctx: Context, message: String) -> Result<()> { + // Message appending logic here + Ok(()) + } + + /// Updates an existing message in the Merkle tree. + /// + /// This function verifies the old message and replaces it with the new message in the tree. + /// + /// # Arguments + /// + /// * `ctx` - The context containing the accounts required for updating the message. + /// * `index` - The index of the message in the tree. + /// * `root` - The root of the Merkle tree. + /// * `old_message` - The old message to be replaced. + /// * `new_message` - The new message to replace the old message. + pub fn update_message( + ctx: Context, + index: u32, + root: [u8; 32], + old_message: String, + new_message: String, + ) -> Result<()> { + // Message updating logic here + Ok(()) + } + + // Add more functions as needed +} + +// Add structs for accounts, state, etc., here +/// Struct for holding the account information required for message operations. +#[derive(Accounts)] +pub struct MessageAccounts<'info> { + /// The Merkle tree account. + #[account(mut)] + pub merkle_tree: AccountInfo<'info>, + /// The authority for the Merkle tree. + pub tree_authority: AccountInfo<'info>, + /// The sender’s account. + pub sender: Signer<'info>, + /// The recipient’s account. + pub recipient: AccountInfo<'info>, + /// The compression program (Noop program). + pub compression_program: Program<'info, SplAccountCompression>, + /// The log wrapper account for logging data. + pub log_wrapper: AccountInfo<'info>, } ``` -For the rest of this Demo, we'll be making updates to the program code directly -in the `lib.rs` file. This simplifies the explanations a bit. You're welcome to -modify the structure as you will. +For the remainder of this demo, we’ll be making updates directly in the `lib.rs` +file. This approach simplifies the explanations. You can modify the structure as +needed. -Feel free to build before continuing. This ensures your environment is working -properly and shortens future build times. +It’s a good idea to build your project now to confirm that your environment is +set up correctly and to reduce build times in the future. #### 2. Define `Note` schema -Next, we're going to define what a note looks like within our program. Notes -should have the following properties: +Next, we’ll define the structure of a note within our program. Each note should +have the following attributes: -- `leaf_node` - this should be a 32-byte array representing the hash stored on - the leaf node -- `owner` - the public key of the note owner -- `note` - the string representation of the note +- `leaf_node` - a 32-byte array representing the hash stored on the leaf node. +- `owner` - the public key of the note’s owner. +- `note` - a string containing the text of the note. ```rust -#[derive(AnchorSerialize)] +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +/// A struct representing a log entry in the Merkle tree for a note. pub struct NoteLog { - leaf_node: [u8; 32], // The leaf node hash - owner: Pubkey, // Pubkey of the note owner - note: String, // The note message + /// The leaf node hash generated from the note data. + pub leaf_node: [u8; 32], + /// The public key of the note’s owner. + pub owner: Pubkey, + /// The content of the note. + pub note: String, } -impl NoteLog { - // Constructs a new note from given leaf node and message - pub fn new(leaf_node: [u8; 32], owner: Pubkey, note: String) -> Self { - Self { leaf_node, owner, note } - } +/// Constructs a new note log from a given leaf node, owner, and note message. +/// +/// # Arguments +/// +/// * `leaf_node` - A 32-byte array representing the hash of the note. +/// * `owner` - The public key of the note’s owner. +/// * `note` - The note message content. +/// +/// # Returns +/// +/// A new `NoteLog` struct containing the provided data. +pub fn create_note_log(leaf_node: [u8; 32], owner: Pubkey, note: String) -> NoteLog { + NoteLog { leaf_node, owner, note } } ``` -In a traditional Anchor program, this would be an account struct, but since -we're using state compression, our accounts won't be mirroring our native -structures. Since we don't need all the functionality of an account, we can just -use the `AnchorSerialize` derive macro rather than the `account` macro. +In a traditional Anchor program, a note would typically be represented by a +`Note` struct using the `account` macro. However, because we’re using state +compression we use `NoteLog`, a struct with the `AnchorSerialize` macro applied. -#### 3. Define input accounts and constraints +#### 3. Define Account Constraints -As luck would have it, every one of our instructions will be using the same -accounts. We'll create a single `NoteAccounts` struct for our account -validation. It'll need the following accounts: +All our instruction handlers will use the same +[account constraints](https://www.anchor-lang.com/docs/account-constraints): -- `owner` - this is the creator and owner of the note; should be a signer on the - transaction -- `tree_authority` - the authority for the Merkle tree; used for signing - compression-related CPIs -- `merkle_tree` - the address of the Merkle tree used to store the note hashes; - will be unchecked since it is validated by the State Compression Program -- `log_wrapper` - the address of the Noop Program -- `compression_program` - the address of the State Compression Program +- `owner` - The creator and owner of the note, who must sign the transaction. +- `tree_authority` - The authority for the Merkle tree, used for signing + compression-related CPIs. +- `merkle_tree` - The address of the Merkle tree where note hashes are stored; + this will be unchecked as it’s validated by the State Compression Program. +- `log_wrapper` - The address of the Noop Program. +- `compression_program` - The address of the State Compression Program. ```rust #[derive(Accounts)] +/// Accounts required for interacting with the Merkle tree for note management. pub struct NoteAccounts<'info> { - // The payer for the transaction + /// The payer for the transaction, who also owns the note. #[account(mut)] pub owner: Signer<'info>, - // The pda authority for the Merkle tree, only used for signing + /// The PDA (Program Derived Address) authority for the Merkle tree. + /// This account is only used for signing and is derived from the Merkle tree address. #[account( seeds = [merkle_tree.key().as_ref()], bump, )] pub tree_authority: SystemAccount<'info>, - // The Merkle tree account - /// CHECK: This account is validated by the spl account compression program + /// The Merkle tree account, where the notes are stored. + /// This account is validated by the SPL Account Compression program. + /// + /// The `UncheckedAccount` type is used since the account’s validation is deferred to the CPI. #[account(mut)] pub merkle_tree: UncheckedAccount<'info>, - // The noop program to log data + /// The Noop program used for logging data. + /// This is part of the SPL Account Compression stack and logs the note operations. pub log_wrapper: Program<'info, Noop>, - // The spl account compression program + /// The SPL Account Compression program used for Merkle tree operations. pub compression_program: Program<'info, SplAccountCompression>, } ``` -#### 4. Create `create_note_tree` instruction - -Next, let's create our `create_note_tree` instruction. Remember, clients will -have already allocated the Merkle tree account but will use this instruction to -initialize it. +#### 4. Create `create_note_tree` Instruction handler -All this instruction needs to do is build a CPI to invoke the -`init_empty_merkle_tree` instruction on the State Compression Program. To do -this, it needs the accounts listed in the `NoteAccounts` account validation -struct. It also needs two additional arguments: +Next, we’ll make the `create_note_tree` instruction handler, to initialize the +already allocated Merkle tree account. -1. `max_depth` - the max depth of the Merkle tree -2. `max_buffer_size` - the max buffer size of the Merkle tree +To implement this, you’ll need to build a CPI to invoke the +`init_empty_merkle_tree` instruction from the State Compression Program. The +`NoteAccounts` struct will provide the necessary accounts, but you’ll also need +to include two additional arguments: -These values are required for initializing the data on the Merkle tree account. -Remember, the max depth refers to the maximum number of hops to get from any -leaf to the root of the tree. Max buffer size refers to the amount of space -reserved for storing a changelog of tree updates. This changelog is used to -ensure that your tree can support concurrent updates within the same block. +1. **`max_depth`** - Specifies the maximum depth of the Merkle tree, indicating + the longest path from any leaf to the root. +2. **`max_buffer_size`** - Defines the maximum buffer size for the Merkle tree, + which determines the space allocated for recording tree updates. This buffer + is crucial for supporting concurrent updates within the same block. ```rust #[program] pub mod compressed_notes { use super::*; - // Instruction for creating a new note tree. + /// Instruction to create a new note tree (Merkle tree) for storing compressed notes. + /// + /// # Arguments + /// * `ctx` - The context that includes the accounts required for this transaction. + /// * `max_depth` - The maximum depth of the Merkle tree. + /// * `max_buffer_size` - The maximum buffer size of the Merkle tree. + /// + /// # Returns + /// * `Result<()>` - Returns a success or error result. pub fn create_note_tree( ctx: Context, max_depth: u32, // Max depth of the Merkle tree @@ -803,59 +1040,61 @@ pub mod compressed_notes { // Get the address for the Merkle tree account let merkle_tree = ctx.accounts.merkle_tree.key(); - // Define the seeds for pda signing - let signer_seeds: &[&[&[u8]]] = &[&[ - merkle_tree.as_ref(), // The address of the Merkle tree account as a seed - &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda + // The seeds for PDAs signing + let signers_seeds: &[&[&[u8]]] = &[&[ + merkle_tree.as_ref(), // The Merkle tree account address as the seed + &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the tree authority PDA ]]; - // Create cpi context for init_empty_merkle_tree instruction. + // Create a CPI (Cross-Program Invocation) context for initializing the empty Merkle tree. let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), // The spl account compression program + ctx.accounts.compression_program.to_account_info(), // The SPL Account Compression program Initialize { - authority: ctx.accounts.tree_authority.to_account_info(), // The authority for the Merkle tree, using a PDA - merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be initialized - noop: ctx.accounts.log_wrapper.to_account_info(), // The noop program to log data + authority: ctx.accounts.tree_authority.to_account_info(), // PDA authority for the Merkle tree + merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account + noop: ctx.accounts.log_wrapper.to_account_info(), // The Noop program for logging data }, - signer_seeds, // The seeds for pda signing + signers_seeds, // The seeds for PDAs signing ); - // CPI to initialize an empty Merkle tree with given max depth and buffer size + // CPI call to initialize an empty Merkle tree with the specified depth and buffer size. init_empty_merkle_tree(cpi_ctx, max_depth, max_buffer_size)?; + Ok(()) } - //... + // Additional functions for the program can go here... } ``` -Ensure that your signer seeds on the CPI include both the Merkle tree address -and the tree authority bump. - -#### 5. Create `append_note` instruction - -Now, let's create our `append_note` instruction. This instruction needs to take -the raw note as a String and compress it into a hash that we'll store on the -Merkle tree. We'll also log the note to the Noop program so the entirety of the -data exists within the chain's state. - -The steps here are as follows: - -1. Use the `hashv` function from the `keccak` crate to hash the note and owner, - each as their corresponding byte representation. It's **_crucial_** that you - hash the owner as well as the note. This is how we'll verify note ownership - before updates in the update instruction. -2. Create an instance of the `NoteLog` struct using the hash from step 1, the - owner's public key, and the raw note as a String. Then call - `wrap_application_data_v1` to issue a CPI to the Noop program, passing the - instance of `NoteLog`. This ensures the entirety of the note (not just the - hash) is readily available to any client looking for it. For broad use cases - like cNFTs, that would be indexers. You might create your observing client to - simulate what indexers are doing but for your own application. -3. Build and issue a CPI to the State Compression Program's `append` - instruction. This takes the hash computed in step 1 and adds it to the next - available leaf on your Merkle tree. Just as before, this requires the Merkle - tree address and the tree authority bump as signature seeds. +Make sure that when setting up your CPI, you include both the Merkle tree +address and the tree authority bump in the signer seeds. + +#### 5. Create `append_note` Instruction handler + +Let’s create the `append_note` instruction handler. This will compress a raw +note into a hash and store it on the Merkle tree, while also logging the note to +the Noop program to ensure all data remains available onchain. + +Here’s how to accomplish this: + +1. **Hash the Data**: Utilize the `hashv` function from the `keccak` crate to + compute a hash of the note and the owner’s public key. Both should be + converted to their byte representations. It’s essential to hash the owner + along with the note to facilitate ownership verification during updates. + +2. **Log the Data**: Create a `NoteLog` instance with the hash from step 1, the + owner’s public key, and the note as a `String`. Then, use + `wrap_application_data_v1` to issue a CPI to the Noop program with this + `NoteLog` instance. This ensures the complete note (not just the hash) is + available to clients, similar to how indexers manage cNFTs. You might also + develop an observing client to simulate indexer functionality specific to + your application. + +3. **Append to the Merkle Tree**: Build and issue a CPI to the State Compression + Program’s `append` instruction. This will add the hash from step 1 to the + next available leaf on your Merkle tree. Ensure that the Merkle tree address + and the tree authority bump are included as signature seeds. ```rust #[program] @@ -864,34 +1103,47 @@ pub mod compressed_notes { //... - // Instruction for appending a note to a tree. + /// Instruction to append a note to the Merkle tree. + /// + /// # Arguments + /// * `ctx` - The context containing accounts needed for this transaction. + /// * `note` - The note message to append as a leaf node in the Merkle tree. + /// + /// # Returns + /// * `Result<()>` - Returns a success or error result. pub fn append_note(ctx: Context, note: String) -> Result<()> { - // Hash the "note message" which will be stored as leaf node in the Merkle tree - let leaf_node = - keccak::hashv(&[note.as_bytes(), ctx.accounts.owner.key().as_ref()]).to_bytes(); - // Create a new "note log" using the leaf node hash and note. + // Step 1: Hash the note message to create a leaf node for the Merkle tree + let leaf_node = keccak::hashv(&[note.as_bytes(), ctx.accounts.owner.key().as_ref()]).to_bytes(); + + // Step 2: Create a new NoteLog instance containing the leaf node, owner, and note let note_log = NoteLog::new(leaf_node.clone(), ctx.accounts.owner.key().clone(), note); - // Log the "note log" data using noop program + + // Step 3: Log the NoteLog data using the Noop program wrap_application_data_v1(note_log.try_to_vec()?, &ctx.accounts.log_wrapper)?; - // Get the address for the Merkle tree account + + // Step 4: Get the Merkle tree account key (address) let merkle_tree = ctx.accounts.merkle_tree.key(); - // Define the seeds for pda signing - let signer_seeds: &[&[&[u8]]] = &[&[ + + // Step 5: The seeds for PDAs signing + let signers_seeds: &[&[&[u8]]] = &[&[ merkle_tree.as_ref(), // The address of the Merkle tree account as a seed - &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda + &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the PDA ]]; - // Create a new cpi context and append the leaf node to the Merkle tree. + + // Step 6: Create a CPI (Cross-Program Invocation) context to modify the Merkle tree let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), // The spl account compression program + ctx.accounts.compression_program.to_account_info(), // SPL Account Compression program Modify { - authority: ctx.accounts.tree_authority.to_account_info(), // The authority for the Merkle tree, using a PDA - merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be modified - noop: ctx.accounts.log_wrapper.to_account_info(), // The noop program to log data + authority: ctx.accounts.tree_authority.to_account_info(), // The PDA authority for the + merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to modify + noop: ctx.accounts.log_wrapper.to_account_info(), // The Noop program for logging data }, - signer_seeds, // The seeds for pda signing + signers_seeds, // Seeds for PDAs with that will sign the transaction ); - // CPI to append the leaf node to the Merkle tree + + // Step 7: Append the leaf node to the Merkle tree using CPI append(cpi_ctx, leaf_node)?; + Ok(()) } @@ -899,42 +1151,42 @@ pub mod compressed_notes { } ``` -#### 6. Create `update_note` instruction - -The last instruction we'll make is the `update_note` instruction. This should -replace an existing leaf with a new hash representing the new updated note data. - -For this to work, we'll need the following parameters: - -1. `index` - the index of the leaf we are going to update -2. `root` - the root hash of the Merkle tree -3. `old_note` - the string representation of the old note we're updating -4. `new_note` - the string representation of the new note we want to update to - -Remember, the steps here are similar to `append_note`, but with some minor -additions and modifications: - -1. The first step is new. We need to first prove that the `owner` calling this - function is the true owner of the leaf at the given index. Since the data is - compressed as a hash on the leaf, we can't simply compare the `owner` public - key to a stored value. Instead, we need to compute the previous hash using - the old note data and the `owner` listed in the account validation struct. We - then build and issue a CPI to the State Compression Program's `verify_leaf` - instruction using our computed hash. -2. This step is the same as the first step from creating the `append_note` - instruction. Use the `hashv` function from the `keccak` crate to hash the new - note and its owner, each as their corresponding byte representation. -3. This step is the same as the second step from creating the `append_note` - instruction. Create an instance of the `NoteLog` struct using the hash from - step 2, the owner's public key, and the new note as a string. Then call - `wrap_application_data_v1` to issue a CPI to the Noop program, passing the - instance of `NoteLog` -4. This step is slightly different than the last step from creating the - `append_note` instruction. Build and issue a CPI to the State Compression - Program's `replace_leaf` instruction. This uses the old hash, the new hash, - and the leaf index to replace the data of the leaf at the given index with - the new hash. Just as before, this requires the Merkle tree address and the - tree authority bump as signature seeds. +#### 6. Create `update_note` Instruction Handler + +The final instruction we’ll implement is `update_note`, which will replace an +existing leaf with a new hash that represents the updated note data. + +To perform this update, you’ll need the following parameters: + +1. **Index**: The index of the leaf to be updated. +2. **Root**: The root hash of the Merkle tree. +3. **Old Note**: The string representation of the note that is being updated. +4. **New Note**: The string representation of the updated note. + +The process for this instruction is similar to `append_note`, with some +additional steps: + +1. **Verify Ownership**: Before updating, prove that the `owner` executing this + instruction is the rightful owner of the leaf at the specified index. Since + the leaf data is compressed as a hash, you can’t directly compare the + `owner`'s public key. Instead, compute the previous hash using the old note + data and the `owner` from the account validation struct. Then, use this + computed hash to build and issue a CPI to the State Compression Program’s + `verify_leaf` instruction. + +2. **Hash the New Data**: Hash the new note and the owner’s public key using the + `hashv` function from the `keccak` crate, converting each to its byte + representation. + +3. **Log the New Data**: Create a `NoteLog` instance with the new hash from step + 2, the owner’s public key, and the new note. Call `wrap_application_data_v1` + to issue a CPI to the Noop program with this `NoteLog` instance, ensuring the + updated note data is available to clients. + +4. **Replace the Leaf**: Build and issue a CPI to the State Compression + Program’s `replace_leaf` instruction. This will replace the old hash with the + new hash at the specified leaf index. Ensure the Merkle tree address and the + tree authority bump are included as signature seeds. ```rust #[program] @@ -943,85 +1195,97 @@ pub mod compressed_notes { //... - pub fn update_note( + /// Instruction to update a note in the Merkle tree. + /// + /// # Arguments + /// * `ctx` - The context containing accounts needed for this transaction. + /// * `index` - The index of the note to update in the Merkle tree. + /// * `root` - The root hash of the Merkle tree for verification. + /// * `old_note` - The current note to be updated. + /// * `new_note` - The new note that will replace the old one. + /// + /// # Returns + /// * `Result<()>` - Returns a success or error result. + pub fn update_note( ctx: Context, index: u32, root: [u8; 32], old_note: String, new_note: String, ) -> Result<()> { - let old_leaf = - keccak::hashv(&[old_note.as_bytes(), ctx.accounts.owner.key().as_ref()]).to_bytes(); + // Step 1: Hash the old note to generate the corresponding leaf node + let old_leaf = keccak::hashv(&[old_note.as_bytes(), ctx.accounts.owner.key().as_ref()]).to_bytes(); + // Step 2: Get the address of the Merkle tree account let merkle_tree = ctx.accounts.merkle_tree.key(); - // Define the seeds for pda signing - let signer_seeds: &[&[&[u8]]] = &[&[ + // Step 3: The seeds for PDAs signing + let signers_seeds: &[&[&[u8]]] = &[&[ merkle_tree.as_ref(), // The address of the Merkle tree account as a seed - &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda + &[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the PDA ]]; - // Verify Leaf - { - if old_note == new_note { - msg!("Notes are the same!"); - return Ok(()); - } - - let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), // The spl account compression program - VerifyLeaf { - merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be modified - }, - signer_seeds, // The seeds for pda signing - ); - // Verify or Fails - verify_leaf(cpi_ctx, root, old_leaf, index)?; + // Step 4: Check if the old note and new note are the same + if old_note == new_note { + msg!("Notes are the same!"); + return Ok(()); } - let new_leaf = - keccak::hashv(&[new_note.as_bytes(), ctx.accounts.owner.key().as_ref()]).to_bytes(); + // Step 5: Verify the leaf node in the Merkle tree + let verify_cpi_ctx = CpiContext::new_with_signer( + ctx.accounts.compression_program.to_account_info(), // The SPL account compression program + VerifyLeaf { + merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be modified + }, + signers_seeds, // The seeds for PDAs signing + ); + // Verify or fail + verify_leaf(verify_cpi_ctx, root, old_leaf, index)?; + + // Step 6: Hash the new note to create the new leaf node + let new_leaf = keccak::hashv(&[new_note.as_bytes(), ctx.accounts.owner.key().as_ref()]).to_bytes(); - // Log out for indexers + // Step 7: Create a NoteLog entry for the new note let note_log = NoteLog::new(new_leaf.clone(), ctx.accounts.owner.key().clone(), new_note); - // Log the "note log" data using noop program + + // Step 8: Log the NoteLog data using the Noop program wrap_application_data_v1(note_log.try_to_vec()?, &ctx.accounts.log_wrapper)?; - // replace leaf - { - let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.compression_program.to_account_info(), // The spl account compression program - Modify { - authority: ctx.accounts.tree_authority.to_account_info(), // The authority for the Merkle tree, using a PDA - merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be modified - noop: ctx.accounts.log_wrapper.to_account_info(), // The noop program to log data - }, - signer_seeds, // The seeds for pda signing - ); - // CPI to append the leaf node to the Merkle tree - replace_leaf(cpi_ctx, root, old_leaf, new_leaf, index)?; - } + // Step 9: Prepare to replace the old leaf node with the new one in the Merkle tree + let modify_cpi_ctx = CpiContext::new_with_signer( + ctx.accounts.compression_program.to_account_info(), // The SPL account compression program + Modify { + authority: ctx.accounts.tree_authority.to_account_info(), // The authority for the Merkle tree, using a PDA + merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be modified + noop: ctx.accounts.log_wrapper.to_account_info(), // The Noop program to log data + }, + signers_seeds, // The seeds for PDAs signing + ); + + // Step 10: Replace the old leaf node with the new leaf node in the Merkle tree + replace_leaf(modify_cpi_ctx, root, old_leaf, new_leaf, index)?; Ok(()) } } ``` -#### 7. Client test setup +#### 7. Client Test Setup -We're going to write a few tests to ensure that our program works as expected. -First, let's do some setup. +To ensure our program functions correctly, we’ll set up and write some tests. +Here’s what you need to do for the setup: -We'll be using the `@solana/spl-account-compression` package. Go ahead and -install it: +1. **Install Dependencies**: We’ll be using the + `@solana/spl-account-compression` package for our tests. Install it using the + following command: ```bash yarn add @solana/spl-account-compression ``` -Next, we're going to give you the contents of a utility file we've created to -make testing easier. Create a `utils.ts` file in the `tests` directory, add in -the below, then we'll explain it. +2. **Create Utility File**: To simplify testing, we’ve provided a utility file. + Create a `utils.ts` file in the `tests` directory and add the provided + contents. We’ll go over the details of this file shortly. ```typescript import { @@ -1130,36 +1394,41 @@ export async function getNoteLog(connection: Connection, txSignature: string) { } ``` -There are 3 main things in the above file: +The `utils.ts` file contains three key components: + +1. **`NoteLog` Class**: This class represents the note log that we’ll extract + from the Noop program logs. It also includes the Borsh schema, named + `NoteLogBorshSchema`, which is used for deserialization. -1. `NoteLog` - a class representing the note log we'll find in the Noop program - logs. We've also added the borsh schema as `NoteLogBorshSchema` for - deserialization. -2. `getHash` - a function that creates a hash of the note and note owner so we - can compare it to what we find on the Merkle tree -3. `getNoteLog` - a function that looks through the provided transaction's logs, - finds the Noop program logs, then deserializes and returns the corresponding - Note log. +2. **`getHash` Function**: This function generates a hash from the note and its + owner, allowing us to compare it against the data in the Merkle tree. -#### 8. Write client tests +3. **`getNoteLog` Function**: This function searches through the transaction + logs to locate the Noop program logs then deserializes and retrieves the + corresponding `NoteLog`. -Now that we've got our packages installed and utility file ready, let's dig into -the tests themselves. We're going to create four of them: +#### 8. Write Client Tests -1. Create Note Tree - this will create the Merkle tree we'll be using to store - note hashes -2. Add Note - this will call our `append_note` instruction -3. Add Max Size Note - this will call our `append_note` instruction with a note - that maxes out the 1232 bytes allowed in a single transaction -4. Update First Note - this will call our `update_note` instruction to modify - the first note we added +With our packages and utility file set up, we’re ready to dive into writing the +tests. We will create four tests for our program: -The first test is mostly just for setup. In the last three tests, we'll be -asserting each time that the note hash on the tree matches what we would expect -given the note text and signer. +1. **Create Note Tree**: This test will initialize the Merkle tree for storing + note hashes. +2. **Add Note**: This test will invoke the `append_note` instruction to add a + note to the tree. +3. **adds max size note to the Merkle tree**: This test will also use the + `append_note` instruction, but with a note that reaches the maximum allowable + size of 1232 bytes in a single transaction. +4. **Updates the first note in the Merkle tree**: This test will use the + `update_note` instruction to modify the first note that was added. -Let's start with our imports. There are quite a few from Anchor, -`@solana/web3.js`, `@solana/spl-account-compression`, and our own utils file. +The first test is mainly for setup purposes. For the remaining three tests, we +will check that the note hash in the Merkle tree matches the expected value +based on the note content and the signer. + +We’ll start by setting up our imports. This includes a variety of components +from Anchor, `@solana/web3.js`, `@solana/spl-account-compression`, and our own +utility functions. ```typescript import * as anchor from "@coral-xyz/anchor"; @@ -1183,9 +1452,13 @@ import { getHash, getNoteLog } from "./utils"; import { assert } from "chai"; ``` -Next, we'll want to set up the state variables we'll be using throughout our -tests. This includes the default Anchor setup as well as generating a Merkle -tree keypair, the tree authority, and some notes. +Next, we’ll set up the state variables needed for our tests. This setup will +include: + +1. **Default Anchor Setup**: Configure the basic environment for Anchor testing. +2. **Merkle Tree Keypair**: Generate a keypair for the Merkle tree. +3. **Tree Authority**: Create a keypair for the authority of the Merkle tree. +4. **Notes**: Define some sample notes to use in the tests. ```typescript describe("compressed-notes", () => { @@ -1193,7 +1466,7 @@ describe("compressed-notes", () => { anchor.setProvider(provider); const connection = new Connection( provider.connection.rpcEndpoint, - "confirmed", // has to be confirmed for some of the methods below + "confirmed", ); const wallet = provider.wallet as anchor.Wallet; @@ -1203,7 +1476,6 @@ describe("compressed-notes", () => { const merkleTree = Keypair.generate(); // Derive the PDA to use as the tree authority for the Merkle tree account - // This is a PDA derived from the Note program, which allows the program to sign for appends instructions to the tree const [treeAuthority] = PublicKey.findProgramAddressSync( [merkleTree.publicKey.toBuffer()], program.programId, @@ -1213,19 +1485,23 @@ describe("compressed-notes", () => { const secondNote = "0".repeat(917); const updatedNote = "updated note"; - // TESTS GO HERE + describe("Merkle Tree Operations", () => { + // Tests will go here + }); }); ``` -Finally, let's start with the tests themselves. First the `Create Note Tree` -test. This test will do two things: +Now, let’s dive into the `Create Note Tree` test. This test will accomplish two +key tasks: -1. Allocate a new account for the Merkle tree with a max depth of 3, max buffer - size of 8, and canopy depth of 0 -2. Initialize this new account using our program's `createNoteTree` instruction +1. **Allocate a New Merkle Tree Account**: Create a new account for the Merkle + tree, specifying a max depth of 3, a max buffer size of 8, and a canopy depth + of 0. +2. **Initialize the Account**: Use our program’s `createNoteTree` instruction to + set up the newly allocated Merkle tree account. ```typescript -it("Create Note Tree", async () => { +it("creates a new note tree", async () => { const maxDepthSizePair: ValidDepthSizePair = { maxDepth: 3, maxBufferSize: 8, @@ -1233,7 +1509,7 @@ it("Create Note Tree", async () => { const canopyDepth = 0; - // instruction to create new account with required space for tree + // Instruction to create a new account with the required space for the tree const allocTreeIx = await createAllocTreeIx( connection, merkleTree.publicKey, @@ -1242,12 +1518,13 @@ it("Create Note Tree", async () => { canopyDepth, ); - // instruction to initialize the tree through the Note program + // Instruction to initialize the tree through the Note program const ix = await program.methods .createNoteTree(maxDepthSizePair.maxDepth, maxDepthSizePair.maxBufferSize) .accounts({ + owner: wallet.publicKey, merkleTree: merkleTree.publicKey, - treeAuthority: treeAuthority, + treeAuthority, logWrapper: SPL_NOOP_PROGRAM_ID, compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, }) @@ -1255,114 +1532,118 @@ it("Create Note Tree", async () => { const tx = new Transaction().add(allocTreeIx, ix); await sendAndConfirmTransaction(connection, tx, [wallet.payer, merkleTree]); -}); -``` -Next, we'll create the `Add Note` test. It should call `append_note` with -`firstNote`, then check that the onchain hash matches our computed hash and that -the note log matches the text of the note we passed into the instruction. + // Fetch the Merkle tree account to confirm it’s initialized + const merkleTreeAccount = + await ConcurrentMerkleTreeAccount.fromAccountAddress( + connection, + merkleTree.publicKey, + ); + assert(merkleTreeAccount, "Merkle tree should be initialized"); +}); -```typescript -it("Add Note", async () => { +it("adds a note to the Merkle tree", async () => { const txSignature = await program.methods .appendNote(firstNote) .accounts({ + owner: wallet.publicKey, merkleTree: merkleTree.publicKey, - treeAuthority: treeAuthority, + treeAuthority, logWrapper: SPL_NOOP_PROGRAM_ID, compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, }) .rpc(); const noteLog = await getNoteLog(connection, txSignature); - const hash = getHash(firstNote, provider.publicKey); + const hash = getHash(firstNote, wallet.publicKey); - assert(hash === Buffer.from(noteLog.leafNode).toString("hex")); - assert(firstNote === noteLog.note); + assert( + hash === Buffer.from(noteLog.leafNode).toString("hex"), + "Leaf node hash should match", + ); + assert(firstNote === noteLog.note, "Note should match the appended note"); }); -``` - -Next, we'll create the `Add Max Size Note` test. It is the same as the previous -test, but with the second note. -```typescript -it("Add Max Size Note", async () => { - // Size of note is limited by max transaction size of 1232 bytes, minus additional data required for the instruction +it("adds max size note to the Merkle tree", async () => { const txSignature = await program.methods .appendNote(secondNote) .accounts({ + owner: wallet.publicKey, merkleTree: merkleTree.publicKey, - treeAuthority: treeAuthority, + treeAuthority, logWrapper: SPL_NOOP_PROGRAM_ID, compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, }) .rpc(); const noteLog = await getNoteLog(connection, txSignature); - const hash = getHash(secondNote, provider.publicKey); + const hash = getHash(secondNote, wallet.publicKey); - assert(hash === Buffer.from(noteLog.leafNode).toString("hex")); - assert(secondNote === noteLog.note); + assert( + hash === Buffer.from(noteLog.leafNode).toString("hex"), + "Leaf node hash should match", + ); + assert( + secondNote === noteLog.note, + "Note should match the appended max size note", + ); }); -``` - -Lastly, we'll create the `Update First Note` test. This is slightly more complex -than adding a note. We'll do the following: -1. Get the Merkle tree root as it's required by the instruction. -2. Call the `update_note` instruction of our program, passing in the index 0 - (for the first note), the Merkle tree root, the first note, and the updated - data. Remember, it needs the first note and the root because the program must - verify the entire proof path for the note's leaf before it can be updated. - -```typescript -it("Update First Note", async () => { +it("updates the first note in the Merkle tree", async () => { const merkleTreeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( connection, merkleTree.publicKey, ); - - const rootKey = merkleTreeAccount.tree.changeLogs[0].root; - const root = Array.from(rootKey.toBuffer()); + const root = merkleTreeAccount.getCurrentRoot(); const txSignature = await program.methods .updateNote(0, root, firstNote, updatedNote) .accounts({ + owner: wallet.publicKey, merkleTree: merkleTree.publicKey, - treeAuthority: treeAuthority, + treeAuthority, logWrapper: SPL_NOOP_PROGRAM_ID, compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, }) .rpc(); const noteLog = await getNoteLog(connection, txSignature); - const hash = getHash(updatedNote, provider.publicKey); + const hash = getHash(updatedNote, wallet.publicKey); - assert(hash === Buffer.from(noteLog.leafNode).toString("hex")); - assert(updatedNote === noteLog.note); + assert( + hash === Buffer.from(noteLog.leafNode).toString("hex"), + "Leaf node hash should match after update", + ); + assert( + updatedNote === noteLog.note, + "Updated note should match the logged note", + ); }); ``` -That's it, congrats! Go ahead and run `anchor test` and you should get four -passing tests. +That’s a wrap—congratulations! Run `anchor test`, and you should see all four +tests passing. -If you're running into issues, feel free to go back through some of the demo or -look at the full solution code in the +If you encounter any issues, don’t hesitate to revisit the demo or check out the +complete solution code in the [Compressed Notes repository](https://github.com/unboxed-software/anchor-compressed-notes). -## Challenge +### Challenge -Now that you've practiced the basics of state compression, add a new instruction -to the Compressed Notes program. This new instruction should allow users to -delete an existing note. keep in mind that you can't remove a leaf from the -tree, so you'll need to decide what “deleted” looks like for your program. Good -luck! +Now that you’ve got the hang of state compression, it’s time to add a new +feature to the Compressed Notes program. Your task is to implement an +instruction that allows users to delete an existing note. Keep in mind that you +can’t physically remove a leaf from the Merkle tree, so you’ll need to come up +with a method to signify that a note has been deleted. -If you'd like a very simple example of a delete function, check out the -[`solution` branch on GitHub](https://github.com/Unboxed-Software/anchor-compressed-notes/tree/solution). +Good luck, and happy coding! + +For a straightforward example of how to implement a delete function, check out +the +[`main` branch on GitHub](https://github.com/Unboxed-Software/anchor-compressed-notes/tree/main). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=60f6b072-eaeb-469c-b32e-5fea4b72d1d1)! +Push your code to GitHub and [let us know what you think of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=60f6b072-eaeb-469c-b32e-5fea4b72d1d1)! +``` diff --git a/package.json b/package.json index 0e8656f1a..dfc6948f1 100644 --- a/package.json +++ b/package.json @@ -43,4 +43,4 @@ "contentlayer2": "^0.4.6", "prettier": "^3.2.4" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 31f18b656..9336a24b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4642,4 +4642,4 @@ zod@^3.22.4: zwitch@^2.0.0, zwitch@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz" - integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== \ No newline at end of file From 0ebfd1e722bd1dbadc231d0e04814d35eb946a65 Mon Sep 17 00:00:00 2001 From: Adams Mercy <56839431+mercy-wumi@users.noreply.github.com> Date: Mon, 7 Oct 2024 04:16:10 +0100 Subject: [PATCH 40/54] Added argument checks - for better error display. (#475) * Added argument checks - for better error display. * Update content/courses/onchain-development/anchor-pdas.md * Update content/courses/onchain-development/anchor-pdas.md --------- Co-authored-by: Mike MacCana --- content/courses/onchain-development/anchor-pdas.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/content/courses/onchain-development/anchor-pdas.md b/content/courses/onchain-development/anchor-pdas.md index c728fe7e9..6df47a41a 100644 --- a/content/courses/onchain-development/anchor-pdas.md +++ b/content/courses/onchain-development/anchor-pdas.md @@ -592,7 +592,7 @@ public key, and the movie review's rating, title, and description. ```rust #[derive(Accounts)] -#[instruction(title:String, description:String)] +#[instruction(title:String)] pub struct AddMovieReview<'info> { #[account( init, @@ -639,6 +639,16 @@ pub mod anchor_movie_review_program { description: String, rating: u8, ) -> Result<()> { + + // We require that the rating is between 1 and 5 + require!(rating >= MIN_RATING && rating <= MAX_RATING, MovieReviewError::InvalidRating); + + // We require that the title is not longer than 20 characters + require!(title.len() <= MAX_TITLE_LENGTH, MovieReviewError::TitleTooLong); + + // We require that the description is not longer than 50 characters + require!(description.len() <= MAX_DESCRIPTION_LENGTH, MovieReviewError::DescriptionTooLong); + msg!("Movie review account space reallocated"); msg!("Title: {}", title); msg!("Description: {}", description); @@ -668,7 +678,7 @@ We'll also still need the `seeds` and `bump` constraints as we had them in ```rust #[derive(Accounts)] -#[instruction(title:String, description:String)] +#[instruction(title:String)] pub struct UpdateMovieReview<'info> { #[account( mut, From e58bbbb072d8bd838ac8bda9978ccb99099962e0 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Mon, 7 Oct 2024 17:14:45 +1100 Subject: [PATCH 41/54] Remove remaining hidden lessons as lessons are now updated --- content/courses/connecting-to-offchain-data/metadata.yml | 3 --- content/courses/native-onchain-development/metadata.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/content/courses/connecting-to-offchain-data/metadata.yml b/content/courses/connecting-to-offchain-data/metadata.yml index 2400160a0..90ac8ed84 100644 --- a/content/courses/connecting-to-offchain-data/metadata.yml +++ b/content/courses/connecting-to-offchain-data/metadata.yml @@ -5,6 +5,3 @@ lessons: - oracles - verifiable-randomness-functions priority: 20 -# Uses out of date repos -# TODO: Superteam to update -isHidden: true diff --git a/content/courses/native-onchain-development/metadata.yml b/content/courses/native-onchain-development/metadata.yml index 95dfd5b64..9d0a23347 100644 --- a/content/courses/native-onchain-development/metadata.yml +++ b/content/courses/native-onchain-development/metadata.yml @@ -12,6 +12,3 @@ lessons: - deserialize-custom-data-frontend - paging-ordering-filtering-data-frontend priority: 50 -# Uses out of date repos -# TODO: Superteam to update -isHidden: true From b96fa87a0e2023d2c89ac767cd0bef2b985e10bc Mon Sep 17 00:00:00 2001 From: Jeffrey Date: Tue, 8 Oct 2024 00:31:40 +0800 Subject: [PATCH 42/54] Fix a typo for the 'delete' instruction (#551) The description for the delete logic should be: implement the logic for the 'delete' instruction rather than ... 'update' instruction --- docs/intro/quick-start/program-derived-address.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/quick-start/program-derived-address.md b/docs/intro/quick-start/program-derived-address.md index 49ff4ac33..4f826ad36 100644 --- a/docs/intro/quick-start/program-derived-address.md +++ b/docs/intro/quick-start/program-derived-address.md @@ -599,7 +599,7 @@ The `Delete` struct defines the accounts required for the `delete` instruction: -Next, implement the logic for the `update` instruction. +Next, implement the logic for the `delete` instruction. ```rs filename="lib.rs" pub fn delete(_ctx: Context) -> Result<()> { From 07ffcff3a50767e54ea547efa00383e0aa048ae4 Mon Sep 17 00:00:00 2001 From: migi <64385774+0xmigi@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:23:21 -0400 Subject: [PATCH 43/54] Update installation.md (#552) Removed confusing misplaces 'the' in Anchor CLI installation section --- docs/intro/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/installation.md b/docs/intro/installation.md index 26a4653b2..bf3ddd1b5 100644 --- a/docs/intro/installation.md +++ b/docs/intro/installation.md @@ -265,7 +265,7 @@ writing Solana programs. There are two ways to install the Anchor CLI and tooling: -1. Using Anchor Version Manager (AVM) - the is the **recommended installation** +1. Using Anchor Version Manager (AVM) - is the **recommended installation** method since it simplifies updating Anchor versions in the future 2. Without AVM - this requires more a manual process to update Anchor versions later From bad7254e557738197c4273bccf5ada87bb773e9a Mon Sep 17 00:00:00 2001 From: wuuer Date: Wed, 9 Oct 2024 11:02:19 +0800 Subject: [PATCH 44/54] fix and improve token-program course (#534) * Update the Token Extensions Program link * sync token-program.md from main * add try/catch for async/await calls following CONTRIBUTING.md. delete unnecessary "!" for generating a wrong explorer link. add some missing instructions. fix a wrong ts command for the section [Create an Associated Token Account to store the tokens]. * Update content/courses/tokens-and-nfts/token-program.md Co-authored-by: Mike MacCana * Update content/courses/tokens-and-nfts/token-program.md Co-authored-by: Mike MacCana * add ".md" to the [Wallets lesson] repo link * add filenames to typescrupt code examples * fix error filename --------- Co-authored-by: Jeff Wood Co-authored-by: Mike MacCana --- .../courses/tokens-and-nfts/token-program.md | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/content/courses/tokens-and-nfts/token-program.md b/content/courses/tokens-and-nfts/token-program.md index feb79adc9..cfcd7e625 100644 --- a/content/courses/tokens-and-nfts/token-program.md +++ b/content/courses/tokens-and-nfts/token-program.md @@ -28,7 +28,7 @@ description: - Creating Token Mints and Token Accounts requires allocating **rent** in SOL. The rent for a Token Account can be refunded when the account is closed. Additionally, tokens created with the - [Token Extensions Program](/developers/courses/token-extensions-for-mints/close-mint) + [Token Extensions Program](/content/courses/token-extensions/close-mint.md) can also close Token Mints. ### Lesson @@ -476,7 +476,7 @@ and `freezeAuthority`. Think of the token mint as the factory that makes tokens. Our `user`, as the `mintAuthority` is the person that runs the factory. -```typescript +```typescript filename="create-token-mint.ts" import { createMint } from "@solana/spl-token"; import "dotenv/config"; import { @@ -530,7 +530,7 @@ npm i @metaplex-foundation/mpl-token-metadata@2 Create a new file called `create-token-metadata.ts` -```typescript +```typescript filename="create-token-metadata.ts" // This uses "@metaplex-foundation/mpl-token-metadata@2" to create tokens import "dotenv/config"; import { @@ -617,7 +617,7 @@ const transactionLink = getExplorerLink( "devnet", ); -console.log(`✅ Transaction confirmed, explorer link is: ${transactionLink}!`); +console.log(`✅ Transaction confirmed, explorer link is: ${transactionLink}`); const tokenMintLink = getExplorerLink( "address", @@ -625,9 +625,12 @@ const tokenMintLink = getExplorerLink( "devnet", ); -console.log(`✅ Look at the token mint again: ${tokenMintLink}!`); +console.log(`✅ Look at the token mint again: ${tokenMintLink}`); ``` +Replace `YOUR_TOKEN_MINT_ADDRESS_HERE` with your address of the mint and run the +script using `npx esrun create-token-metadata.ts`. + You'll now see Solana Explorer is updated, showing the token's name and symbol on the mint! @@ -655,7 +658,7 @@ on a wallet and our mint address, making the account if it needs to. Remember to substitute in your token mint address below! -```typescript +```typescript filename="create-token-account.ts" import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; import "dotenv/config"; import { @@ -697,7 +700,7 @@ const link = getExplorerLink( console.log(`✅ Created token Account: ${link}`); ``` -Run the script using `npx esrun create-token-mint.ts`. You should see: +Run the script using `npx esrun create-token-account.ts`. You should see: ```bash ✅ Success! Created token account: https://explorer.solana.com/address/CTjoLdEeK8rk4YWYW9ZqACyjHexbYKH3hEoagHxLVEFs?cluster=devnet @@ -707,16 +710,19 @@ Open the token account in Solana Explorer. Look at the owner - it's the account you made the ATA for! The balance will be zero, as we haven't sent any tokens there yet. Let's mint some tokens there and fix that! +Remember the address of your token account ! We'll use it to mint tokens. + #### Mint Tokens Now that we have a token mint and a token account, let's mint tokens to the token account. Recall that we set the `user` as the `mintAuthority` for the `mint` we created. -Create a function `mintTokens` that uses the `spl-token` function `mintTo` to -mint tokens: +Create an empty file called `mint-tokens.ts`. Then uses the `spl-token` function +`mintTo()` to mint tokens. Remember to substitute in your token mint address and +token account address below! -```typescript +```typescript filename="mint-tokens.ts" import { mintTo } from "@solana/spl-token"; import "dotenv/config"; import { @@ -776,7 +782,11 @@ associated token account - we can just look it up using mint of the token we want to send. Likewise, we can find (or make) an ATA for our recipient to hold this token too. -```typescript +Create an empty file called `transfer-tokens.ts`. Then replace +`YOUR_RECIPIENT_HERE` with your recipient public key and replace +`YOUR_TOKEN_MINT_ADDRESS_HERE` with your token mint address. + +```typescript filename="transfer-tokens.ts" import "dotenv/config"; import { getExplorerLink, @@ -803,7 +813,7 @@ const MINOR_UNITS_PER_MAJOR_UNITS = Math.pow(10, 2); console.log(`💸 Attempting to send 1 token to ${recipient.toBase58()}...`); -// Get or create the source and destination token accounts to store this token +// Get or create the source token account to store this token const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( connection, sender, @@ -811,6 +821,7 @@ const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( sender.publicKey, ); +// Get or create the destination token account to store this token const destinationTokenAccount = await getOrCreateAssociatedTokenAccount( connection, sender, @@ -830,7 +841,13 @@ const signature = await transfer( const explorerLink = getExplorerLink("transaction", signature, "devnet"); -console.log(`✅ Transaction confirmed, explorer link is: ${explorerLink}!`); +console.log(`✅ Transaction confirmed, explorer link is: ${explorerLink}`); +``` + +Run the script using `npx esrun transfer-tokens.ts`. You should see: + +```bash +✅ Transaction confirmed, explorer link is: https://explorer.solana.com/tx/SgV2j2DkaErYf7ERiB11USoZzGqAk8HPEqVJLP8HWdz9M61FSFgyEMXJycHQtfCooCAPBom7Vi3akEAwSUHQUsu?cluster=devnet ``` Open the Explorer link. You see your balance go down, and the recipient's From 7722e922af04a6120b3cb6631fab4c1f25d514e3 Mon Sep 17 00:00:00 2001 From: Mik Watkins <63586831+Mikerniker@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:07:37 +0800 Subject: [PATCH 45/54] Fix broken links and terminology in mwa-deep-dive.md (#554) * changed private to secret key * fixed broken links * change crypto term to blockchain * fix prettier format * Update content/courses/mobile/mwa-deep-dive.md --------- Co-authored-by: Mike MacCana --- content/courses/mobile/mwa-deep-dive.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/content/courses/mobile/mwa-deep-dive.md b/content/courses/mobile/mwa-deep-dive.md index 08d232afe..0ceaa2fd7 100644 --- a/content/courses/mobile/mwa-deep-dive.md +++ b/content/courses/mobile/mwa-deep-dive.md @@ -23,10 +23,10 @@ description: ## Lesson Wallets exist to protect your secret keys. While some applications might have -app-specific keys, many crypto use cases rely on a single identity used across -multiple apps. In these cases, you very much want to be careful about how you -expose signing across these apps. You don't want to share your secret key with -all of them, which means you need a standard for allowing apps to submit +app-specific keys, many blockchain use cases rely on a single identity used +across multiple apps. In these cases, you very much want to be careful about how +you expose signing across these apps. You don't want to share your secret key +with all of them, which means you need a standard for allowing apps to submit transactions for signature to a secure wallet app that holds your secret key. This is where the Mobile Wallet Adapter (MWA) comes in. It's the transport layer to connect your mobile dApps to your wallet. @@ -41,7 +41,7 @@ app-wallet connection differently. At its core, a wallet app is fairly straightforward. It's a secure wrapper around your keypair. External applications can request that the wallet sign -transactions without ever having access to your private key. Both the web and +transactions without ever having access to your secret key. Both the web and mobile wallet adapters define this interaction for their respective platforms. #### How does a web wallet work? @@ -198,7 +198,7 @@ transact(async (wallet: Web3MobileWallet) => { Note that the above example does not handle errors or user rejections. In production, it's a good idea to wrap the authorization state and methods with a custom `useAuthorization` hook. For reference, we built this -[in the previous lesson](/content/courses/mobile/intro-to-solana-mobile). +[in the previous lesson](/content/courses/mobile/intro-to-solana-mobile.md). #### Interact with a wallet @@ -450,7 +450,7 @@ app-wallet relationship. Before we start programming our wallet, we need to do some setup. You will need a React Native development environment and a Solana dApp to test on. If you have completed the -[Introduction to Solana Mobile lab](/content/courses/mobile/intro-to-solana-mobile), +[Introduction to Solana Mobile lab](/content/courses/mobile/intro-to-solana-mobile.md), both of these requirements should be met and the counter app installed on your Android device/emulator. @@ -598,8 +598,8 @@ provider should generate and store a keypair. The `WalletProvider` will then return its context including the `wallet` and `connection`. The rest of the app can access this context using the `useWallet()` hook. -**_AGAIN_**, async storage is not fit to store private keys in production. -Please use something like +**_AGAIN_**, async storage is not fit to store secret keys in production. Please +use something like [Android's keystore system](https://developer.android.com/privacy-and-security/keystore). Let's create the `WalletProvider.tsx` within a new directory named `components`: @@ -1625,7 +1625,7 @@ request types: `SignMessagesRequest` and `SignTransactionsRequest`. Try to do this without help as it's great practice, but if you get stuck, check out the -[solution code on the `solution` branch](https://github.com/solana-developers/react-native-fake-solana-wallet/tree/solution). +[solution code on the repo](https://github.com/solana-developers/react-native-fake-solana-wallet). Push your code to GitHub and From af3aa317ab901d98a22787c2c06f74bbf2e3f525 Mon Sep 17 00:00:00 2001 From: Akojede Olorundara Date: Thu, 10 Oct 2024 04:10:23 +0100 Subject: [PATCH 46/54] Updated Intro to Solana mobile lesson pictures (#549) * chore: updated lesson summary * improved lesson overview * improved intro to solana mobile * updated use cases * grammar restructure * updating in compliance to contribution guideline * updating in compliance to contribution guide * more descriptive variable names, handled errors, avoided repitition * fixed title * updated conclusion and included useful links * improved setup section, removed unnecesary pragraphs * minor update to ConnectionProvider function * improved some functions in .tsx files, added comments where necessary * improved basic-solana-mobile-connect.png * improved basic-solana-mobile-flow.png * improved basic-solana-mobile-transact.png * fixed content styling * replaced cryprocurrency with blockchain transactions * cryptocurrency to blockchain transactions * cleanup * improved intro to beginner-friendly standard * improved clarity * more restructuring, building a more readable flow, especially for rust beginners * minor fixes, resolved conflict * prettier:fix * prettier:fix * prettier:fix * Update content/courses/mobile/intro-to-solana-mobile.md removed redundant comment Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md grammatical fix Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md changed "units" to "lessons" for uniformity Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md improved structure for easier readability Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md removed quoted and improved tone Co-authored-by: Mike MacCana * Update content/courses/mobile/intro-to-solana-mobile.md changed grammar structure to improve clarity Co-authored-by: Mike MacCana * requested changes: removed redundant sections, typo fixes, improved clarity, used further contribution guidelines * removed backticks in titles, resolved merge conflicts * fixed broken links * revert changes in rust-acro.md * prettier fix * revert image changes * added improved images * added improved images with whimsical per CONTRIBUTING.md * Delete content/courses/program-optimization/developer-content.code-workspace --------- Co-authored-by: Akojede Olorundara Co-authored-by: Mike MacCana --- .../courses/mobile/intro-to-solana-mobile.md | 4 ++++ .../unboxed/basic-solana-mobile-connect.png | Bin 25908 -> 43888 bytes .../unboxed/basic-solana-mobile-flow.png | Bin 52869 -> 83181 bytes .../unboxed/basic-solana-mobile-transact.png | Bin 59744 -> 158008 bytes 4 files changed, 4 insertions(+) diff --git a/content/courses/mobile/intro-to-solana-mobile.md b/content/courses/mobile/intro-to-solana-mobile.md index 68a5791ca..03971de57 100644 --- a/content/courses/mobile/intro-to-solana-mobile.md +++ b/content/courses/mobile/intro-to-solana-mobile.md @@ -335,6 +335,7 @@ refining your skills. Be sure to explore additional resources like: - [The official Solana Developer Docs](https://solana.com/docs) for in-depth guides on Solana’s core libraries and best practices. + - [Solana Stack Exchange](https://solana.stackexchange.com/) forum for troubleshooting, sharing insights, and staying updated on the latest ecosystem changes. @@ -371,6 +372,7 @@ environment if you didn't already. This Remember that this step is not required if you are using a [Framework](https://reactnative.dev/architecture/glossary#react-native-framework). + Ensure you have [Node.js](https://nodejs.org/en/download) installed on your system. These will manage your JavaScript packages. Install Android Studio: @@ -1201,6 +1203,7 @@ to fix them: That's it! You've made your first Solana Mobile app. If you get stuck, feel free to check out the + [full solution code](https://github.com/solana-developers/react-native-counter) on the `main` branch of the repository. @@ -1214,6 +1217,7 @@ the client-side code that interacts with it. Once you've completed this, you can check your solution against the solution code available on the + [solution branch](https://github.com/solana-developers/react-native-counter). diff --git a/public/assets/courses/unboxed/basic-solana-mobile-connect.png b/public/assets/courses/unboxed/basic-solana-mobile-connect.png index e54494c0615bd565bcb4fa5e520679e713aab790..6f9c619119cb09d7ba85c009b00ffa6d37f29f5f 100644 GIT binary patch literal 43888 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 bu5hm`aSgoZgs}e&1xHO;OQ~GJGWh=i_mb!U diff --git a/public/assets/courses/unboxed/basic-solana-mobile-flow.png b/public/assets/courses/unboxed/basic-solana-mobile-flow.png index a030480d605b722e3d28f6e7a1acbc2d9e002d0c..950cd2c5c0fc73403708aeae2c91ee571f0101b1 100644 GIT binary patch literal 83181 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�jJv zWzUi$P=oZU%ri1)>9Djvqeg~s7IFU~1_(MAMg#Ew8GbV;Kf%nLA$I+->2CS{n|07r zSgK44AWeF$H1d{!PT=I$n$@!B4tJL;%)-lLa2%ECnb-}?VM=A1Vu5JWX9<2sfD#iP zH)S9_$&=!zC@F*Ec!2lBv$q=vDX#n~2!S(ln`KA*C%R^G{Ptgf)r}72r*#A? zO%z-Ou=^P11`OwMV!;JI&ZBh|b41};LVYBHGr}0$g#0jVhaZl^@=c!KkS`t#hBXZe zGnyD^(M4s`;VBzRy`^PN70FAdMC6|bfF0H}w7S+no1+udzF3Y0!JdgO9y&V*r%SWc!9n^R7uJXh=?SLEzMk;44F{_vsFyLx9#-Zo>kF1)$Zw+0 zD0V0>|6|LJSO zF32CnuM7%^x_j+?L+q(T%Xy9w#rriKIPT?xhWr^{<4$ls9KrF32yz@?J7@VoFg)rQ z+HE`}GkKhgnF{+r2xDL)Fkt*YJiUcml->6|EZyA=Lw9$>(A_Crf|PVicMLsrw*n$5 zNOzZXr*wC`DcAbxyFqu6WcEbvi!B;)mG5u9GHFZGjS)1{ZADRwn8gk1y=L-5~&v}|Fw)7Xva(lkqr@TK_x|M=;{xq zkU+`Ho2d4`^q?M8FxvhwfB^<T9gD|g$bFj9}aMvzR^6YKz(7emdURJUjzRu-=|StpKeDN>3-GnD{69B ztV2X}u~*BS{ufR>AmPM9cOS{ExndTACjLVngEaa6Lms2WHUVOA7xtjM&s10)|~9(i>aT31wS-?YZy1++<|ME*~#l4TS5Ey*{1j|2Q$1CwP~)(GPj zn8!h@iaV$0I#Lpo3rA<|HR6A#gaC^+GWiup84Px)5dP*6mXFFFA|?x~U|vSiU?R{i zKG_pm|U|w>A#3h`A;Qvq!%7}kw=w(+Rj4JF*ySFQ{r+#N1szCqWQ%CQ#>(h_9_nCpl$ald=>I(pbTPt6U{(K*VUjIRxIa+XC06GdF@??YNfuvyLg7Zson+VS)hLU3 zKZ!aDvf;4Bk{GQs_qNeDBX}meIJ4pfT=5M35<)Zh1-Ye`$KqRmaag?9|Izsc;s4=U?Ts+E z6cFCSBTpqH_NADcWA}-Cs`OC(*AJ+my!U43B#Rk~RTLPI5DS(IOgex>cl{S$TF@M? zV|h%x_d&=};>o4bL%483f4dViQKg`I{ zck6C-wUswpKEGQ{XqeST!7)|}oji`1(cE(O7=9i+`n#geS58+q;PtaHj8Os7-Bo6f_BY#{g{9J9L*`BD;++n(esVi9tkAol`Dyeh?7R=y4Q)at@c6h@F|3KrF!i_25UF7c^sSB|VeSE1axR0EA0 zZ9$e;GDu$`^9M^jZj9s%?WUa#fiV=jkoz#J?CBygI_e&sw7KgENXgd(!raq!7Rg~F$GrkCzC*%QpkjJL7s3SEzIS)#L0hJCxY)1 zkct&cxi%Y7Hp|?ej3>5TfsjKD=zDwaijc<{EQWW=OuhE+!JEd zK{>8J0#i)MIEbx1YyYrzceB>x-pmqgPpHy_pt%=pl=~iq79O z!P0988KDJ}i5!3UTx)EVprkRFJSF@3?Ev<(wI^>i08INp5`UaLzJs@p2HzzxvcLegX# zlCGU?C{+6+S$rKP!d8vKmQ6ytp=Wu^qqZV-WnXic;^#s915zN3M(>R;#vBntrdPqO zc%~NpD1*QCuvyHVU=!DqLzw&2cxaFm`8p6UTT2di6mNM>GJqZlb5&peAMS8`sQw}b zx2^g$b{u}G^gkeHL<>Bq(K8|@WFJtw37CUrmvA797`Aq_@3bIzV^ zbz2j;`1@}}<^`v(qkDHmkL!2`i2H=^gX@aoM1Vme~+ zFiV&yKVk{v*o zuf^r)(g1>D@Oma9c{q&a(3BhHgMhB+RISWq-4q{X{M}Bym}QEaI&D|%J=}uZ#gF<$ zlLAOZXJtSNLA9I9Mer`!0ip}605oavABcz$JXq2|dA!7(Gq%&ilax&DV*^|ChJ%{< zP1!N|(w7r{Wf<=x2y0OHpwKrTzL$@NyQoM^FVRwMSDKtfW_guCzP-O?$j|WHGlM% zxfgz+w#PkJ=A7IjK;WQNp71NJ8H>#NSuLt*n2A$w2zx$i9rar@j{d2|WxS~=I(#fi ziTPqYv9qwM4gbd3>l1b~tit7V%YO2;5sfAn?|h57xL!%isQ+c4I~lY{V|l24bJ|$S zyP@FG`w#kS_lp+)Yx1vO36s!pscmEtwLv1&Csk^W?5f`!Sn8EaJ21sM{ zLnXxyTYw*-+JIqhz~_kiparVqT#Fl1auk^sQr>5tMaW!2-D11|0vYO)?GNb}DYu@S zH1&I|EXZ3K@Lk#t^;nD)8M*i%sj93wIE|Y5IU)0R4_O5ng>{n49E`WSQYj$v#(%VB18bDX-11 z{7A0X$R62`LQ2HS!~!Q~`*ZxC979~l2OYEgigBAxP0 zt~&!yzFj;Hgbqi-?0OO{QpOhD%1MZHA0f#?%T<*1O2>}Jgfv1UwcBtNF^o41jB2Ap zq}uT>j7gR*sw3*eJ_hJUgRxGCs5RUB2w#k$V=d?aF=N)Oz6cz{WioUjb4JkB{V`Rd zR#n+sxrs@dGF?lANz-qLP7pR!64O?6W3%Gv3`{>J9rRJB6^PS#RB>zvckNE ztMH30nbhQcbER+;<>Swsw6O;?o#OWy!XfFsG46i*rY?FNILACFh?KtYiyj%ZqSc$` zjS=>^vK(w zNm-IK+g6H3IMoXz7YBEui>>eSp%gLNOfvbq-f^S~pfl`cFBXP<7D3opA7)mUnJ9uF)y&^dA5W!RXK_7#+DXPt;4fP+@M^+ia)UL$c zfH8`3P}Mt_JOhzr+}GbMaQ)XSl6QJ@mh;{Yq^nUGU8+Z`CWdu+WN}Ut^0aKegJC8~{vD z>W01T+^!Wm@uNw3%_hcT8^&0P`^3nW>1g|Nc_ipWquZjC&^6$&@Be85VDjT+ru0n- znN)_n8xg}vm>q2z+~ZWQp(fMEHuyGQYSj{V-t8Cvv$zlRnug$9&lDfN zWAn;=niyfzRi{~gqyiDOxJ*AODvWK&@wnOBssHbIQ>eiZPv^>K{QBn1b>%{_nd7h7 zvsYt@uh{%phe~uDLoB3-)o)JlOnr-Ggv!OM^^FKzOTc{L)}| z+WCivH1ti(MMVT!&M%)+1{q6aTI=ME6`9jMac*a`=kwuJeA9Kz2c4hLNM4a#7?~%{ zO2kh)O~9CrNC3Nps6oV{a1T{FPt_MVkM9!XEtB(ejiOY}skMBsA?;EtIZV4;IijB_ z2T&Fm0#zN0sUvTO;@uNxpZv7Kms&&o4rRskU81d~OdEX-H}%*Yh>$8k zz4xgKF^#j=;9Q$l*!g1{x(o?380))`!6)vH_|#rwXUVfc>!DS%L~&guj>B;8eGF-P z{20|Tv4)*=h)7tzAys;hgg_dXcjFRuu5;r+! z-sKgPAjKA>Z_i`s=Scshy2E+IpXp=8!&PzF0>0>YxKmS==q|*GBq^FQCS4@ck;?xX9M1G>0#Ep)T z|M0}+#g|7cmInc_V;4KC@9kaORm6_C9kDxzsk7*5!Q}piM53=WIz_?9BO&+zcn3%bI!22_7Yjk_%3n?Q`^v5Cft= z`Ys!Zah40K8e+nI*y8psn*1xZq;*$L{MW``6|0l5~!2wtk03oHV*FIuHUU z1~TNQQ@zq=Qy|}}2-Kc`c=nOeF0%^fGfkW^tpZ)U5;4nR`XNN%+-ior?4NN=*HNYY{VS~V6en{`cRDf;KRVQ=i)dN7wc=C10|4c@vkX66ZD zT7VG6@%AnLoQWH)(T*s^r-jOHwND|YlH7Q6zapB5yVmue&TA5}MaT882KH{!r&tE* z22FM$lWO@{C3S*qo|ZndAyF!td?p%TTo&#v90vi>YN&m28cA-VF{TmB^c?{ODXm^Jz$6vIfa<5AgaoiF&c9k1k?*f@2&({h+t z_{(vhA1^b+>-NL-3M*^nHs)|^j712rw#>(y+~Ax6!D6rW)a$PoT63R{mR|L|yNcoH zSSh27W8zQ;sWH}K8zN9Y%bv7+$vaz&h)T6J^(D&=e8Nj%BTQVHE$bPr7VMrU3)ne0 zSws9OQv?az2|x6Xg8yvPw%~aKtvGND3>OSOR1cqVt(0W2Inp;_QICh*W`bB5fhPx4 zs?zESQ@wQooe?co%w~mwi@&6f>CH|5Owv84%*e15^0TQ3sYDiV3g%Rsqb%n<9d&gy z^S(Ld@+jUQ;Zs?av4=!F)^kyCraj*D0HqcG3c9n9We2^+svre)_5;JW2AJ8;{xI5~ z@8((XDZ%!gMh9^4R#HIk-XVU9?4FiO9cd04EW+z`sq(IJ9n7^w#jOoPbc6E>< zCO_jQw20EDpjf^1t1d^^W#AO`z;seNfoYfz8HW?hY`RXwZ^qE6;;5d)TqVqaF76MB zhcocDx^IeT)YFS&9~dPWh0l#wz~W?>cBp^rqpEQTfA|~o=6*xkvHrOUj2(`MTbBJ6wV^?b+GbuW$_ zW{QK@n%`bH7MWe=@jT}K3E`0HnN}E7#81d!-^U>v7Y2*as3=b=8i<|9W*Wb}t$7#T zyb1c$vfFc+G`hF=w9u*lHix3_S6OFqDRQHvx$Y1059WNoaeJLg>o(bX5y(|ox_H?r`;1e`5ZqS z&LoLPnVN@e;sFw*z6JD`MCr2-bC7N^vCCrT)O;J*u$B?fFbe0{_z;JLBwe(OQNl1Z zH@6L@_PbCq*(d5hQYNh_1|rA5_FNFMknlivBjY8^X!{D$`854cZy9V8kfn)pwSA7THBDxoYQCwGO={>qMP1Ss(Q8_ zB!tZ<=<}%G^1^6kb6_?cvP2j(!BD1+wLG!*T<6lSRZ-apW>#(W;3!-3L-_vtCD{h!O?#rj<cQV4NFm8HHfO<1ZfX3Y1d48W8|f zsX#34mYZZ6OAj&B6$Qm<7=($)T|UI9cHl)Gi}V7KoHLKb%3ViXX2Hy`pHth2 zBa*|@OPY@MR`L5v+>`IheT!mcS3k}GB`f2)<9yP~l@;kuVnxPz|3kb@A+$Wkaf-OR1A+Wr^ zdJtnf{zh6=n!0E2rOu9pLIk_SLQwl-@E(HaW13+?5)FwAA?M@yS za2OLeHvZc_5ni|WdCh;h<7v$r#;-{2gt|~&h7pX!O0TtQ{Y9vg0&>qn)QtEQ{zZEEJaVd4hd_q zk~eSvb>*;dzVr`dLcXMK1mDaKOj^Ck;cw4ZlwU8`FpMr%nUAqZ35V|18s0IlJq=pz z$SsPBGp$zU)AK;_aHDw=fs$C{ftyaULqkR!D>#bmE7ysxx9$GI2@=l00$ywxy)x-P zSJ*~e81IVb@forii3T(XxVEua;`Gp(r8lXmt?r@=bD!$|W;hN_Fk3OnYFe$kUC*oM zM%e1vh7?ZQU*49Pf{e`k>`KpnrYl*d!he>F$3mC%8Tibme7Lhs29-9p<`-;(z1;t2tPA_>9 zg2@nyJ3f@!^hezNbbps%=h^hlYC~K%7Ec#z^Rx_z9B?+^KP}0HxfLDS~kSLIf z4%NZa_castOwHi=AYnCoL{-cw#)3@S=367~gIJsEqWjGq&M-$_Fd+3;j=U?42JEo+ zsRCMzNoB}eCeoofZ}(QlLxY`&M-j3`cob1O(MWv34CxmiJ=x(Ep6t_K4Df|GubJ4T zE|zfIrYM3N6kOs+OmCIF)AZK`%q}*0?OHnWU_7XeKobt_EcMpvgx4UQl2!a^Td1Et zzWbi0S8jMiXr^|M+8W@i$U!59o$B~4VoVSD+={kj9Xne*c3^BGyS@;y%^yZsH_^_D z@W584uja!;{vJ$a(d8*BD~$knhT5ieiIOxr6*B%SQ(1PuRFYBPe$^ERGBn?stAx*I zyEWtTVB>weraRPyiW>CHNc^s-v49;N zplI6M$o+LWH7D#kzpJ1Oufmjpb{-g&2w56MkRXh}eb$Nc`}f2f0}*)1+kMF%7az~^ zDZtBoRj|133fx#wl0Qsf`t{TOI+Ana@*dLYD0`pfs2;T_Hwi&*5V?)0JG+0tXRIQ! zPrIj5(Lk#sUw~-JT6J;?w(FWs#=oync`>+RCphqA)t%;(JzTAvyFMHu?cYl^9>F}_ z_Lt(&Z?1CM3a5!q2i|mm^1$J0zaQY=&z491KSt@RM#Y+!YG-^D!J?9kSCCH~yV-Vt z6d;M6+uPAQoTux*G8bZ}!gc~WT)E#K9E63*u@oTV83$ArK8C-s($JBK_@lbvv)Ll^ zDe8<&jQ8?yyboz6M)?c-J#efB9LLK?C2rj(Bp3rl{a4D^L1T2>Rd@3RW;%E^+NTS9 zwT-?4oBrP4sU)JsrU5{4V%U732RF=e`X6ztKWztH8ZVdUPMOM0G}Hi%3QoXYS+zl{ zvKPN3rehdHXqQhc~_?XZ-2Ye71n!m5y%LsVJU80kMrhrl`Im*$h9q#!ftr<8MRn?WBV5tma`TjbWAM zQNO7OxlE(KCH_=Pq0tAZ@z8s)G$m~#`k{iQu+=Ciogir6Y&#{$yq z^uSg?a(y_L%ctg{fgL3nm$Wec9IA(ig%_(QhE}Q=EbNpO0`VtttG!+G^)ZCpsMMP{ z78_ZmQZ6;Nk6$^dBe`3tu;52^#Uo4lU|I5>Ir!dRnAe|oQT7G7j=O(t9UqUbA8DLi z-sSpTY|gE?s-V0vyx+ej>i;@Zd3b)dSSof)%1VxE-gt+l2)N_D<}-MF5b5jxNwLK; z)~Z|Gn?+#SpT6-1>(iCP@5yua>)J*aK=X+n-cSG@#Hm*MaZoW%xokkDN}bMYJ+pz+ z?alRH%* zQW!lg+qmC@q%s7Q(0crtwk$okaU9EoC?R#ldFG2o>|PRoLl=L0<4-8J<(_pnq@O`fO^@wy_U$rPD_&b8md;+hkKDOO zRVqS38{kc04}%Pj5&JH&hh)>Bz0B3Q!J!7Mruw>Whu#WSir3W1~AD-1#eaE|m1A7Bqh=r9p13nsIa9|)5i{q55_?1~D z$Kj>I9g?guBCAyCz?CF5C#Oi+QY%Xx*ExQ){8&`lou*%yWwP4XiI}u&fH9X6-4ma9 zL+~nP@^(`gkFpX;A9-yc%ZPO!eA;ai{qR%6AocuUnUxcj1Zo<*D79wZ&%|IvdPV%K zJiaK$&PqyzS+eAitank8SuZE+kB*7*K9trU7=oh_$(cH5jK=s*t{W~Fe+L=2S_yk( zo7nQLBoouV7h`J&eG4tEvh9euq$rcU>IFf3hKsN0tOdWcbG{2gW<>_4;vBF06PO16B7)!yk)v#FA12#>qW=X8jz^^~FAK34ypnpJEXVqtw}6R8 zut8S?LE?Rb`9Yz~?Ac+zoI9~16G;b)@EkD7@#FAN4ygF(kbNHEygxq@r>3WImi9(9 zM0{P(M2a7A&hx&bE%PN3winnJd-(wU`YK7+6s!Ba9*Z+kEClHw{#4yZ)kYVc+GR*R z{cJXMSAqt*^3&73O22_Ly!l}PlzMeXR!1W>s;Tik{O|WIf}8UN1b(&F81Ns%Z}=yR z9T>G8kK|V4VosU=R`5H z<<4E$G*q;tR@xq(zqf8{u8Qp0%TA~p0yQ*{_?#cf8hIOAZp$1sg!2Gf7pM}mK`Je z&NfKDM1N`ID)v1_p)YmOipKl7Xtlu7wPUwiDjqvzXxCHIip8Cd+fyffGo>bF(MTu8 z2kK;jv+=uAk$Z5T-!@RkFm42^SneD^%V@_ zLp%tV{SoM&pC5m^*N-}xjWgq-;|`6BjdZ7%X1NqhwrF6|vt2sL1Ck2B+6Sfb!n{I* zv`ix#z z05@q))KXc`apvN^LvD8Bz}~qy%z7EdjBb5LPk`o;8>+yizgU%L@^*g@Vqs=>D3+fV zBDI!%;wM)ZNj_>zU(M^^$`L~=QiLD?&Ev&YULH?xyyYjw66MZ!RsY9vI{%x;ck_#{ zf!?2+AdafYA9cgP$UNAyGz&JgTuPbTwzBc$283^W^0!|@4e5Knv=(z-cl#|mv{|c( zPWo%uh`Y0M|75MM=-W?Vqy<&N3aN|!GeH1AI^wxW?E9-vWwmb8^0SfK2 z8-&lye9^t5rg?&FzB8L1DR;fN&@krv3$5H)3+b@jo$27v((qpEPbtc_3+RmRZ?aMC zqETDVj)qm?+i>?pg8t$tgX&-E1qnq8ri~&Jo<}KyEcRv{u<*zyut65Z)Jv`#kQ}f4 z%3a`0OdUi@T^`JedlDw#m8s{3L-f@A1e$ zRV3?so%%(a-{&oF(?>TC{yURH>`e6;MX%?h@d_EI<1kUU;l>gFHmp2s2A;@7)AOoP zY9gv>a$rJn{@TEdLyP_@wfZ16bho@JTa$X&_&|9rU?R|#_^RjYt#*6a+lI9R+TDq? z;KDIX=Ev#WD&}wZ&qbqi9w;{R`o8S+N+ipjnZ1^}1YobS;;(442y(!afpr zjhUnT{x0!nrJo~}mx&)xi!Sw&Tc{o}s-C~r{*&G%6(hM^P9nFYF73A6xbH=I>=qwC zJO{EnSX%>`a3oIP%^2$1xc=}n3YQ$9)O>@rDZo)pPGb8N;&*(AT+T=_K~+7&1So~< zynNV<>)5HU2BAA@2MuAAg&HyHM<)#4_ZnQZBbT;U8gF`JEKlS=2(=Bc8n_VG1KKIm zmi#r_W?oy%jooW8p*W4;&U za}JG=#dRvN|3Jt{)=tP4=LIS{revb7KlVJ3HZDbdWUoe>mX#I>z8H(W{R#dWuk~`d z+I3K6VXL8*fBPF`lT0gQwJ<31%yLf{?O3ZCFW>)`^-t%mn1B1c{^Nr`1Pr!DF#$N6 z1Raa|-_6U0g@>E%Z9mG#II}2yu@(!1GYsfR?|# zi|oa6h}7%$y0*aC9ywj)2~jFEG)Eo=PzXCecvPCmX^nCVZr2{7S+Ho@{6q2<%FLD> zw%PU721zC%puEKvkUSI{Hs*ic`OMch2$W=V4&YoR25FX0n=Kt3T;6vIgG2TvvcEg0 z>)xD}px^7_;_S450umQ8aBXFRiLUUe@x0 zI8pyk3n2EuCyEkm1B_XUTXskLVw(;_$IlRaXy9@^?T!n@wU&rU^#;adfJqz2Weib6 zW}W5D@_o5@-VgwRdQ+!?B|NOBSZ{uEzg)|j%L8#-w|9zVUA~HM7Xgw|G0~iB_m4)n z1I{J{^2#w8p&2Et1-BI0jmhy6LEYw2dd~pm%8ysi0^O0V`afa*( zalJb*a$R~%y^}JnEzZy9amtv65J0&$jxe(OO;W(bsNW*=UN@Z+Zj?G8k^_3X;l!O~ z{XKEkk`{~mJpMN(h>6!zH?{5uWp005hFnghIaK%1!k?Y~V!ums-C|C*$I`JQ;Y<*R zyRjY~bJF4J$dXKs} zwS7)hsv=H;xY1WGgre{!D(NK%v{rx!DttLNB&D8R2G|1nL1tJ_tQygrW;!g;6L;o) z%1_nd3~$}RC4b|b6WD~xhE`%WKiNOTUj(m?mdS1b6$yejRLVa=kTy-9t$F_S?ghx& zB{$i4GtL2^H>7#{@r8oRij|4AcY9B*QL!Xx#OiHEXX?*!o&BXqnU&958XBS4XUzSv z779rysFUEA>iBHPMp(R`_nCi28_aInHbVrUIqsGeSdXALbqdt~pRYF#otSIO=5tu=C1 z@auEkBtj|f>;2B;k~xlr<>!)!UNJ{ zvs3SQcynljD%KDZHWP#H7u!p-GRN0FdefZD`HsUn*4%nhdSOJO<+19@oh{1PO2dN@ z<5&d6S%@*pP$k6QC1lk}E5BPQCiTwap@l^$0<7Nvw`Btvt#Jj9`GB-HI)Z3W?L3F| zK>D_b$c!zbQ;b@;HjBk?#@$cbi*_m!wET1VJCM{C=z>5^vrg_IrWKh}@ok_gRQOWK z)uh*{X)aI9y9MJB93=!S@m8{K5uyV<_D{WUd^Cv2NIV_fn?`D(qr;kqIPAE{$kTZR zb-@fBAe;>`5x(>p(Vo0VoiUx`+Ca5rv8-YNJl+JMwE=6Nxg=Bc5@&83Imnx!WhaA# zxi^OeVDpbrs;6Vd_+vCKRN#~w(SalS!l2}-9C<9}{=;7a#N7vvQY-|;FO$Tn?>Z|L z3{aRN6B(s%>Yvo-zY9SGFbnDyJMtw-(JWYu2{CxGCB`ISEyB`7Ak9Xj(B4ruFok01 z(r28~n4NLE&Dl#t;vJak?wGiR*i1bLt>3$o-ot{JyVa{~W35Q!SAZRurD(JksL>~U z$OR2ap+MY|Gh7vJ68v)no%60jYUr_aZc z6Hhey-U+{8;sZ{>E^aBot3%bDd=L>IcReVv{Uhz->FPp z36Qea1f+}IAzHlaDbkW=52eCHCTvIM2lY>MAgHS^5?T(gi@ijX3P+gqDx+^_ocYNNA5ueu)QEz+yeXd3o_hoHEU1MCk8(|>3cd>cfOySK0m z#zAOO}I6qOAY;xicdW0uLYLeki}$gYw8`sn&O(3IwvPoxlj~N(`#w8(!aFm7FP}CZWiAXm#vZj_2!x6-UUWQN|wkeFSdi?;a z9QGPI9&CtBrt(FlZo2s=@rWi@>HyuvDa-PAQ_xdsOD_?f0~k97>)Quu0W#_(u*2&0 z#prVRL!sa60s!Iic~EM7fI50c&Ezq}?0tkHakEg*&ov=2TKX zAv!*~Z$#$W02x#~7;`+tvynVvPi}Na^W%q`>$8fM%i#C|Cm?bp?@#Be*wdYYwHS4G zcQ6Z6ZK-VRb(vMHqq%&~$0tYg%idJVpIfIG1@p%SeULrVANPSwg2`9z4s%C87Ybf~+kFncRMv;eJQ}qCF)c@Rc z1Bi~oO$m4{W}YoJ)8PdQ<|zN|&rn!E|A;IfVTj2|{o#vqHnN`nJEBAW-PqmVw(m9H z`^!wax-Dbc&rSr1GrMf8BoIYM- z;ZfID<}s;z2u(Zp+>n5UF!~ZGeVM;VvtR$Er7f9gFu7=8BQ9RgsbwSkP8hPYT`aHCcwh)qdi9aiYuoi4Kizv0!ZpTf5bn| z7e`UNs-CEB-iHmG%qI>JY{N>>;84AfezEG*+D>)Can+&3K0~#%7E%$_#6Clkxw-95 zdTbsa;HyiKtsalu8@KR8mjm<{H2^FFu#h`FbmM7O2@(z&Jrh&&t6i`9q4CR42!o6j z6f>;(LS*a$VHgYrk;QgFQuZ0PC-wvwxALGIN6Den-|Ar_OVCBU*!?M`1-Xwhhkpm1 zAbq0i>)XMG@|GkmFQWpIzJM+KvkJ=ST@>zeKKbfjsJs{LD7e!N`gleziy~^0A++wy zyA<|L^BD9taZS|gRU!-l8Ms+#@zNu8A&k=<#bQh4-@8)jzh_N~-A1iu_|P;GN!141 z`ch+0gnZLb3kt_R=vd6a~Tc0gO7 z6*WcD#e@*1L0b^QEe&b!nFE$ohf(#IA{N}C%w?y{;9rmZXDvs0D?^!{ni64`z!1wf zR=flm68+3yXH^Z z`SLi}lhcGm_}mTKnN|oTIaDl0x)hn%Kq9IcwVF9^%K@nhj_c%*b3@wA-f&W)S_2|E zMdI*Lv^3PeO#cE8UYu-+qMZ*_vQMIs#DXNv1q}4!ZPCxW_j^b^P3(6;Km0f4jprid zkkcrZ{H+5WzA~$S?<0#2xG+1CXu@C9o(g=aujA&=c6`TSsj8awtn9CbOf1i4>IW&C zhs8{XU5)cjz2(FGQJkdFBBYYEipq!OML33N@z{>sK9kdz9LeY61=lG+B~|2V`5ijH zX7p3q)nG8gvb=nskxddHoJXF`dXNw(`i>qprx$YcN{pYVj3lIbIf#v=7*snDZHrvM z+*E!C9G0RHN6ljbK|F8d7 zO}{GA2|5i~pueq?MDjGdv@G3ro zo4sk7s|$bT4HKw+wuw_*0co85n4h%R=%&teIU^j)3#51n2*?#t%9_ASBV+yeD)Ll9 zw=N%{2XPQV-L)yz6RDLh*2|>prnfhzl2zbxKEz4%nwdy zhbs~^y*A{6)UmL((AH^jqy29d72*W_1hiGQ>7)G(Usp#TZWB9QP(Y;oOLOdV7%?>D zTy^47(jnQm*Rjrg=BI7^Q?+VL>FhbZ12e=RDK`KOLMa{ncJ$zZFj>m}PUV4VH@i`D z{ahXx0~1_qaUqyB-gE})QSuYH{XJ#y3K^v>#hxZvo(oBy|owNh=Yw$~G`tJNGf0>7!sg|hwC>Awi{6Qs( z)iw>1wQG0zY#NI-Xw~8??T<_?RxuYbN0~Yy9oF*%+TrXHk1Bnt5%&B*GK0Zipn@dA z-$&c9^Ul9R$el&Ik)+YRrO)k-s^w(mU|+v~;pCr^KVL@y0^HNwTL#hsGgDvL#lUg4 zdT2!9u|1XroJ5y8CUpDfkE3@rbbSICB(dgDRAj2BpS*Ri@A@5V6Kxq~=b{LQ=jMKB z(g6~}WM$i}{Dp_aqqgd|H_-*si8SmduWfTfv4RU#V{oqmT4}h@W8qQd3XHo`dDY05 zqw`}wo3Mu+K)8H^k{=VFw}B%>Z>yeyAgNd9woEfftjChn)RWBB>`)4AyH~EmUHXAM+bIj#(fCM{X>tT`ZrTRQ9*X4GG?B$HGHP$m^G)aUJ=f(mI*s za_)N3Q_Insi#8SL_v^W&>eE05PwenHc&S}ZwPQiYlC`%Fju5oUXZ(G}^9G(eg=sdl zy{^DLT%NJI!c2_$8H3#1+Q*F|@I!_&qzn6Cp2q;`6=ikNg%Q#Q1)hqhUXv_*A!}Hf zZ9EmL(*G!W9#=izB5>E@-?WkNH7Ey*4iX3SB(h_aXyP?~S9HYiT8$ z@qt};A6)(4Ztvqn3c{~E^i&4sR-A1Z5$EK`uZ~F0w@01%>tX*t?%w*Vs^<+G76b%T zIuxbz5F*`uki(%5-Q6kOjnWN*q;z*kiF9{&cXtUq8~uFWwcfR!zu@`B#X9aiduHys zX6}2g`I=PP3>;764#E_=K@7M)FSI_HOY*wp{%uNMaj z5Gz*7l$QSr6!S%eCF+Id?{79TWlq;EJ`y8YH$S4pqH>)DCc2*lg#Ia0>7Op&dZy)m z@p_X z>Fzgpq-8KjrSfopZX<1i5bg1Y6gNm#pLKa`KSsq-hezIcm?CHD>k=6sJ{yl= z+Y65+A$x6@>+u7FQTqtfi)<->=)kQ27cE-#C-Pt2;$1)rU)Z{wMC-tB20&`9U@BXn zoc~OgY^0F9Q`cLYM7{|l@zmu|9FNIE*ZI{T8mhjESrP^aOd+>a5-jcAqSvWyDC*2{ zPjjgl&y)dDItat{`&4e5!5xFyjJvfKlKkp@e!h-@v{S9tRutK@!9V=pyEPD*J1*zi z@s7`*$vIUJO;{7?3{nu3HslsP-^vYKacjf4Nz>`Rw>^4D!d$L zQWgzZnyejbQguQxhJ>Z|*Gqq%xukb0~se^Z%mYWEfs>-U3SWChHGTql6LKW~Y`6Xwjm%OI29YvKv_0SSc?$ z#u$s*;st6*$>?$Vh>{M#p1^hxux`*9dJy?}IvQdFl)qM)l1U)doDpdUoSvfgCwh7{ zZ18Qw#a&;YSszbyN=f3bS(I29rg_vGkF|ae49TxuT32uczln=et~gC4Y`A0&_>sVy zOkzedaDVT9vdmYzlJT;$5=IUSa(|d`a=vSm5&YaIrKqh`h^QBpkgsmld{U2uiyp_` zR2GgEdpa!@DzsrIUL#Dq%ULA)wChu!=WY<^{_;Fex zG9VZ2;8S^OZ@Og|NEB&~Xn7~0Uv%IhPiPO38;`HM6e!m^GJ0>=Rhw9SyR!2`pNaLO zfAK5-iZ?6C>mkB96+^@eQ#z+mN*4PPAj`17WqU{$g?av48buhW3FC~vy6)S^W8OWH zn?#AnSie38Llb-CPE>}y-q1KE-bz!Ty8MdtDG~nIR@(}!4fHeZf9&bSB!WKV!@jRF zg!(|n*T zv`%I!reVy&;kS}}X#GRJs@6hjh;qnpR@xLOW{$p*JXYGiDOC5O#PtyQxN7y%hfL+m z?(>>cqQtZ+9|)7uciD20=SvT?c^T`HKZM&~M6rl2DP}A`(yE9@3@6 zabdldBqgxg`gu!d*2BrS-)PSK22oU{%Jwxgb7kV_I&jLRTE*<_!bxB`dumZw4T>cn z4fg+91L^np)4|bS=Fpw~;CvosD!swRbF~ko*JWBPYL-uKSb*Gw>T9EFhKrb9?a3ld zAT43p;i50b#2gS9T0^i9K}Sm~v^9G>}AaoL>3$+D)a+!_RW#{`1-<^O(nldrXr!Nz8H3dZO^+XDXIm zI|mwd%_Tc*53o0{VDOJpDrP^A%1+-RCuroER{ot#R_?j%tH2lrFecA}fK!Zbn3Lm>aB0Js3zMidbXeq% z2k5zqzfGQSNB66kiC|a?-dtbEiF7CCejbejnMt5zki46|>#j~tJ8~Ia&`xreEU9S6 z3)L>w{)*#s;k(FH;6F(;mdnsVveM&9tIkqvz)2WGioQ+X_mXorVqX~vP6=Gr_E8|0 zC+6ivVw@y#JE1Sb+84y{KYk<$q{PSkA?z&I8_}cJlCXSi1V%lO;b;0L+R5bbIwj&( z0u_fWoO##mBZ%zF%A=+yyV2q7>_;T#`;;l`H+0sg@jbEyQ+oG7A}UU+By(RLq%jAx zwtqC6OHo*giQ1!N=b1?rV@JJ;pOqW>IZj@JI|BhT#?OnY3#iwu|VqXk#$*5GQO8mVffV z6}E`PcZRy1Atw%Zg2B?7b0@TENN4CMxk;Kvhj~Bj6mVaA+%zf5x1PK64F0eL^7(!6 z@jb|$54D+486S*Qb-^9q?fq#X|6qInPC3ua1hFCK4rR;4yt(G`^L|5SZ^uM_=6%3g zrN1a%`|N3VfaT2AuqS*M!$ANX1CigbEwY0p=ktT5$v5x2J}mEX4qiSS(xMr=HfR+? zFn?P>b2t|%-lO2+VdimEf{EKo71Ud^ONuokOWKKRYphwwHadj2B`m3`Q)k;V`;qGg z@Ov@T8@1&rgZ0}#yP?~p2E!e3{5}2%9Dc@!w*=V%B`7gHudpd^i?YGQQ=@<8&9jQH zJ{4hPE{(p4EKQc>mK65^wiZP?nisVFjzF9>(=uEnNhU(m0}qkiJuf;ogY@n&D^JDA zUhZZzgYgpsoq!((OAR*C;PZrEpYINqLJCZC5xq6hwS-TL3mPjQ)G`AdVUjzHKDK|WYa|!skz0BTI4Vzl;eS71V8uay1b@Ag= zqFhy7aEb(Bt^w^ETkG*}Z=$Y5xojVR6X!z0%tIE|G|zE)6Laq)zKdD41mQ#^voG#Q z3wvXOET7G-fYWNtbm!p&w`O2d()E+B9lE0VMhHts&kbXfsn5=X-){M>|J7tZbf0jl zTO0w!*9NZfC=8fnOj9pNSAN+lV<1(VKd<$kLA&?hK;IqTm&X2lR@Ig z{hD6RUq+zkxf=GrT7V*&>l*m3v=4&EF25k^7>F+3IJbJxqaLxFrRvu#qoI&;-+a~H zn#pw6W~2fYL~=Ep78qW(#*H=MCBMa)+GjFYpcs=%qF(m;H(Hf*Jpa%uvERtnf$ch; z3|S_i>-yH$C12R2eo8v+UH%%yT$~`#H zbEbEaG`D3~IbjSC`uSI^WVG%t3P-$DU&XH2zM++bc-3J5j0cH3`@_mH%RGV;dQXL& z!bsJ)R0fS|{=U{0{x9zN_Jsu~1h7!*nsH{~pNI3AhQJ&tU`3zkdLIf53$X2)1_Z(f z7rb2PeY;tss|m$k?KRH&zaBo%f%a{> z0GbQk?rSu@H-Nd>>)2|t{a{NR$XitG!}j3zF3zsD}OT6VX1 zj^q!Z<7}-=Jeg3xd^I9v8?-I z_rZh&&IB>7V!a{W|>6=F&{R&FZHS^Y^8^nh_k%0?|*LV)svJeUr^l2st%>+ zu3>V{HvA~sn%;G1H;MxLN1}K_QjFze-$27SYp4J+hpFj@!{sP4rC`~nTB3#{a|$(+ zcR{dF{*Vkz_MWtSDO-LT(al>uYO;js*S$qVqRlRY(uW|1ueM9un zB^Mt%_(l~)vx0{(bm^;HgrygRFp5MOY`H*=_n8t}J0wr-0E{LDfnK0U>>nEu`xLfE z|8U%{>3mSD7dQ9j6Fe@f}n#i!W%Q9&Yl z)@WapeX;PGa9z^kSJr2RzX7Y9f^!$vC*~c_gCmvMF|sH!Oj46j^iiS^UtZZSw~~Zt z3_U^%<(>dg@ZmU`7Y#B*P)1LT4c-#1_{#1V-xR1SM@sSp-vy1$G+cL^!qF6zFodbPti6Z>KOyGz#ogwD{t z6`+zY3}Wb!n<>+SPy>M2=ZJZcT>MmbDk(*U4RAOmLw302`^84#o~Ui$%GBnn*z(Qg z4N>nK{&W$TG*|sr8+lNVWEp1j3}gxLo<`l6AVbjTjd2;o$@;_jpRFvy6MkVj?Rob+QUC>exAdXDWGbC{Au=Tlf+INvM)zcCF{F&tt=47u zA)pV}8SlH&=xnF}+9I8t@}qc?Jpj3n7WK zsHN&1K)_TtZt;*_1d^ee%6@Zbe^3~DYMs}R&&d#Q$e22B5cOTsDoNswM^oifU6rLw z#8Y6EwP@8x1iBy$I3ne7`(6x}{cxoKhD7RR4eRdl8k6RnRGN8(N&Vb!PRak7i1c$f z;8iIkY}(pM;mSq$g!(N*ED(QmP5?P@c8mmx*2%<1#|p21b`#xOD#pVMZBl<~fjS8j zR25F%9FsvzE9VA&>CgzjqKqI+L=rGzAi5FG{XQXWU~pwSN;q z_UsQ*zR*d3Clp)auIDl153A7@t0Qi-3sG!~SD8~16o<&#oq*GAx}^mnh1K8P)!V;sjoK1=D~97^mi>C{VY&V^sKkMW4yxw&PDK_%L3<0OC7 zvVMQXP{6*eOVvR|Z8O<v7vH7=ie3MYqfXM$GpGxVy$!kH>7EsLSBOz2A{96C~>S z%oqp{z`ZU=V}ZZWl*#!R*FzU{{BFG?Ff19pH}b|+E~FbPafL?(ptI_ZRa%r?6xrd! z{Pu#(&xsekKq{LGESj2E!HO$oKS=tg6SH}>| zo8k!?Rv`l*B@;9$kzZzW$9|e`1)o-LPuXtWvjmC)gKJr2& z(318xInWz?qO~l@3Q*^fE^duq#2yqx;$cqfgWW$3No>XPU>|R@$M7!S!^g?xY+tNY z_kZhyF2@{!1xt(tU{q@27Wu}1bZuy4Bt>4^aDAT-#>_H?zPkoO>pzTq?RE7Xx)7R$ zSFbAjAQJ(QUdvXCjqO4}REeV=0AZD1hY8&g*9lDbd6e);R~F64=CbIky1tZh+3q#bJ{)ct%>_ET@lVmtCk zC-35rBE^m4|3aTSh6&xs*GT~?wZ{ZY>x$QkpK$n8H?7KlTj;1e)(4IHwowIT&nmymx${DWOcbvBLk-EF9B(5n8?2x=dQor4%BOcM>D1dp zuIsC*y;9W))a9$2WLM=gr&i7y?Ja2fb}S>0PcF$2w!^>W9kh^Wpp4&v8@;}H;JL2zb9Z>IUrNqawKF1CS3dZ;Hpy05)O z`ok9m!8|cYWw9N&iuVKdJ~>^pBTp>iq(jhcWtKAwgi~`z)IS7ju}YK4>21*RV#WDZ z`nHnB!4Mi50{sJ`0y{L#So^y73%D&K21`BzNEnC_Yt(YO0at`r*I^f5`a=N2T$4PO zxBF+9g6KlO*|61E2msYqnO|fn5yJ$nN4?VtIAVSK2MJIfqSNxdb6w6`Qk1AIa7G!7 zm^qex`$W_eX_0ZrFMJG?!cY7MLDSFs6{cDarWAhB&xKhg7k&VJ@IT=+p}|dcy;}v+Jmu6W%qjj6T)juS`F)X zmO+o4x`W7=+CTQNe!`H9VNwA7tdJAw;nQLu{K*nmYlz2(@K#Ma_js>MA2Z!LQzz)v z)V0{MfEDq^|lN*MFd zy%`NTu5=HtJLU?NU3){z|I{DJ2aWPpfk5_Lp_*YNR}A0FsqMTbs(;OdR)R6?S|ZCT zff$|JP{NGmQ&yQj%osTQrt_Oabmlsv0z*-We;Nnr+-IXI|j_=q1)Xm^2 zi!igUs@H(p5d}6?+v<#?A!6%mto$D!FhaIx9mzeOfT}!f81%kP`UKsqYxg~Vr!Ky! z`zeSwaVvCX;U(I*Q63WC9CABqC1Y7SSUExlmANdt1fDHfb_Y>8aO;&Ei7d>JRs;8q z&skfBWflrZmL{yrifZmWz|AS{{2VG8ytXkO1x`CnGgHYx?_H?1nkMI`#iGNR3dC>@ z;#lWrhGY#&_c3sNu!HX)H;Q+P{x7EUBETtQdq@IhNc@F*LHuMdwI7oQ&tc3FyJ$?l zgJ+g<0d%BT+2X4R!0%|QO~y+~9+CTuL6(dmmQdW4Y$lLGP3)$=og$O;_w%xf$CFOX zZ8d~#^#D)z4y;hco3+tMn)9=l2A_}EPyIdTf^ZtJ0`8qETrh&_2k<*Ev$b*|rXiA; z6*Gxv)g&R63A)UQa+Re3FnOa3It~r|U2;$J(}q18cd_=&fCxWwHR-?23V=9K9`gwq z8hp&KHD_QYirgN{_fFYuabX_HC+oV}dj?+|cZf2?=F8lnR~-dvjaW431bCJ#=m$DJ zXfhNO;YkH~15LKsVM4T-3YA{jc3Io0we- z;@klFM?g+i!E8XO*Q}7s^fXOTMT9FKZ5d$1@{Eq+8 z+1UsnaDU&H|Mls)Y=%s!HdhVHx7hv|Gofw3vi~?UN8D-N@YRaJ3+-N4Bm|D+_(R*8 z-{uP&1Rro%npY)6$ti~w<91AIYKKY2z_F_Ze0z2=Pq)<{CP^YqL52wrhP_hCV;W_A z({}H-LR|}zA$V1Nkj>3bmVMH7d9{g(${cTAV)a||S9Ol1zqq%YSl7p4xZ&N_ zis%6;F3eYb!Q`ag&)|_s%E;hN54J0-RL`um3bqx3haF8)sS&$GS@%P2H*l*;UUUue z(%EL8&X%BNeNP#!VTX}H%AZl1!3E#%7?sn%RNQa2e78}sN#)V#?89H6RTLC9p=J_b zmf0taYdcXAF1h5cwVG0PubO~8JnJGl+xSyh9qmm~bst@9Qq}w1iOIv0dfA?(koyIr z@Ai%U)at@GMp;#LsXX8DzPXB}mErNidcyhhJ*g40mM<}1bJb`d`b{V1T0hLL7hwoK zgG0Ct7OZtVzoDTK1^)Unm?_x1M4hoBR?Z^x1LO1{LWt60nI6eP9U$~ASMLUa0 zGXX8Y&AN1A}zT+mQj5Q0S zfWiy*{nLAE8RWD+TbS$5UCll0TP#^W-kbgC`a2%vb19#};Ne-;$LYZ@m;nSy)$1u{ z6AzSRY?o_>gN}~^oCR>r-O4w&l*NZ`dRGE{!5}JrO{s_2YHKagWJPvViWjs!y3k%7 zN`Col#h(P)57=Rj813zCckM#G9m2*E)&7fs7{tzx=6C5iP{& zkH#S*b#gEY78GICC~{zFG;R z*T}cDn%uutyC0V=Oe5H1Yp#MGdEms23PAvPjZhWD#^jjHxY`Ex;32%*QzfwM*ciX$@ zhp2|L^4!wD==LNL1G8S^chDLcr!L;sWfLO&wJ=u&l43#+BQ~>GP3%Nv6s>sb{0jI$@9WhDOpC@EJ6-WB=uND~ zTWu=wM1>22{c)w%tK$ziGagjWYceA#Tnbl&d^BDU4onT}q<%rt7wUlqbg0$kOlfh!fNPjQsEc2w#QQ}d zP|qDzQaEIHGR_B*^oapkL?PH8Y>h~&skif)Zi z!N5XSQuB>hiNsXrPq=ow6Ocz_u$+S+F!XqR!F8Bpo0?YU%$k+0G+Rg`otQcOVWV;p z75P0wE+7MwO)SGK{(iytd*B@^sGuy%hP5CEqCh%xb>MdSzHnJ*DTJ@2xx;!=6MN;_6wbMs{-qcZVoxN^i_Dtq*{(%I_LXFZ@ew)W=nis3g|&6&G{ao2M{pd zDZgSAdh%9+zaQcImPV+{`E1?A(waYIec{K*DPxIvWYiiY$%F@H!!{v`c6yt2A3}c; z#Fw9a7}TeX#iBOfOgRBIv25KZ!8oaff5h(SEsmq z>U7$C+cL=Ou?^c)V5S*XEgMx$FKcl zye*9bYbtRw(L{L@j}NUvlaCgO7>DaV^^Kk`RQpP4*%q}i!MfAy;vfSo5A{CvqK3-P z^4!jA=MMYljjvbA7#B5=lM}d8@thU?-e{Hhrw7~E@F|DeV-@4nMY47fhnmbGONY^9 z@5dT3G^mdQIf0{AA}Z249mdk6^UP&uA8Id^S!=Zi`6x9~a*Sa5JNSGfTl)>Sok2&_ zcQq9C-=`;Xv-eKOeu8AOE4|E9n(gcR+>%fTQt~L{C#yW51xHkTx;JXiWnJt2iy9CmG*!G zDT8~Nnw0142sH`fWQfVOsUfWjS44`7(y{Bx#(&Wq(Z}BDJkd!kJEJC3rk&c@n&&Ju z#OylpIZl6{rQKr;vg~wX7j>pMMj6$AUfAY&6$8{CFY+&##v z$}4SOY>k@rt||)^rY0QC`aBvp|Lf?)$aWDsv{y1a#`MF^7mD)z4r4mi+tpW$TB9;BT<`y1kce;!}{e6+0PpyJ{}{i#W;$FUB; z<}+|M!6WsCf5oxUVsLM~cn9^9A(bi(rkA4HvjGD1M(w>BQfmE*);`U$Kag6>wGyX1~J$sbE-CQ*mOZ+v8z+x($z0X0 zg&NPTWQqJnifTosOi65|8dzkIP6?Ce%wP)9I*5J3rud0KHIRp~O*Y%lO3L zk}f>}XhNn~+Mt1sF%i!eXv}zbgF`^pLCP{@KYWdS%;;UHqOPtGx~ZMYKNN$p4T8H{ zx*|3nnrM4;Yhlw(i!7<>g>v7fH_fumR1pmQHPw5$Wn)PK=~gH$51mPT@GRP7$Cz)|RK)?#VNWFW8R zyf1`GBP)Zv{xlvp1)K9vLc#@vq{`I%p_v2zNHDwCq1BCW$|6NEy`5xEG= z9|wfkon467gJ16AA}n|l99L<<58XGlLU(Ggi(=PsvOHk5Vu_2#xys;``nQPo7IZ>X zqJRV~Ac5trqvnl_pn2u05(0U01J75c!}%TWB743b^s`VDz!_Jv$WWZ@`1*%lkc=C_8?;R6VPnSW#$a4OeT)dmp{&JXV^0oR(HRKOA!Q4phXYowD zxp01MBOTz%)vPFB?Oh2LHOHfQ6Uw+2#JD9+zf-=!6VzJKIX3gKAu#woQ+e@LHf~~q zf%v^@bIg@Rtz&dV`B{y+nIDrmNI+odxyN_#UjXvV#~y(hGmD}X=WguKC3-K1UVRt4 zFcHfPE#Fhx4{revKqt}C*7Celj+?5C87o`g=$h$E6@~7nqc^>6ZTAcJt_0|&$+g0t zEMnOX&KD;FsZ5>c)*S;-RvvjxyX@rGtpu*nI#lJ|he6)HwTiRby*3%MmJCV;8NQ8e zKUVwW-y2P_USj`shaRU}Au{@X4ux-QykG~gOrsc(6yM*r69WK}4Tmmj*1RQ|+RiyD z;+fi%Ncpplf!1n_j@=BEu-fG5SIdL^;9Vmb=eJKH5G2MTft=WUY^chNC^WN98}zR} zK7q3R!cnYZ9&l(A0$JzgArZ^v__nx|5J8=QbrdSD5L7wV8SS3{A27QOB1hRJO=~k{ z{`{hL5leIN(kwV`sDsh)RVM3!ydDADYaQg>={d1@GFqPvKz#HvdH4@aV}u?>6&{@H zz^tmBN{8(}U@IB?QncO^-a^gF5K(qFPW@1|o+=zUst5uusd@UGw*Ue&VHU+;eI=pC z{C=$LpSG;dNIX8}>@Q-JYz{5`yK~R=9gHxHRnMiv`xhL(n& z5%inWISRx_S1#@`ImM;ou{Y!EQh8=_= zDLPhV$^b(V%EixjZW7+WZFk&2X%Fe8a@-KdiIpls8Ekje@r|XgPv8FA<7e=1F}8b1 zvd2Dl0Ip)`McoVtfMy&H1&y_SgdP^gsT1S`r+qM7v_54ko0$^-Am3ZKLceGQbjrF9 zP^3IggulZUz$Ah6RQO0M`mQ2C%bC^#6e2VS@#7uer!; zSY)g|E}Lag2*(}*-~|m+2+FO-i~n!gxsSiSLqds}L-AlSGeP|z1e>0a3i2q!K?|7p zm@D_vs!+~enuMKY%4lVFkXJvGf#*y`1#2#f`lMr!cg8DwL0b#1 z6^G3v8Y3d5eIt7UV_7)BoQ%HA1at?h&)p&iI{qtTWLlXQr(A~uqpZ|9Qco8;sw`u5 zBo-N+jgPY+{MXjfli-_hv#MUpM+2Vsj*x?glHjDS8x{f;NU0^#k zbI(GhT4j*1Sw3p7ku69@9nTjBoe}5zr+;04fzXHy+h>J&Bmg$XfjwokUc*1r9y&k0 zk5d?g^-`HxRiXKXGZ)2E``+k4`|ivp`SG_^?pWB`lbO@2sj(brLC0CtBC+jKL)yfi zu$OoYrcwX-Bf&JtnAAV1U_kJWX(8hC%2lrw_eHY6!C30k?~}sJAC2Yh0eP02LdfgeM;aUFrw(xzpq0` zBN>3N3Ia#F@(XeXac3DB89vVu&i#G={r~s==!?!d%NK9LyC+e$kZj|U-W~53A;Wkx zb9MjCiYu68&KHNLN0ze);D}Wq_ByG4DO)q6M{e1?LB(hL@E+%%iPZqs;j#Sv+VfV@ z<9G*QVsgTK?P6+#YOgf2)fZl7FqEVN+mS%@pGdn9DwrUqW@eMb4<~uM?+6w`V}s!g zxz~QSx%PWMC;z+Xhy-+S^hL?nNB+BloPmU58_xZl8X}ivA_CsB))*FY$ccZ-r~f9J z$;aXqjDX;s!`3}|b+VnV(62#(gJ~j`<==m2@(F$XF#m03Mf7J#{(G%WIS(COwd1JV z3$y7+q10qbp*wYxie6{Qo4Q`r*ku zo`bLcf^1h!Z<6e(9+o*0A*c}_7s0=0kgM=WAS=QDRx#<-&xC@9N|AkW($Gm3{)Io1 zz9*OK?)}e%1WR86-{p2OdPf22DWVn=`7cCH0@JF2`EL|YEuP%q3PrA-!DZe3`^}*4 znGiG{9b@6|3)>C=Ox2gdDq_L~e{_dq&jEL&tyI z?eD&QArx$U&Kmk8J{@9O`r>?<#wEn|ukbT(vUpF&B@SZ>Z75d35z}7iVFYy*w$I^+ zQO6AbsmyJyAO+UQx0sym4vxhTM39d+yBe^ds6MMnZ2^GzCz-ats*eDAXhs5jlgsT! z?fJqu@x*&nu4NCWIU#+q(vn7<;m74Ui_xi7u0kil>(A`P0(KTatK2LX)S5e;yhyIpT_UnFSC6eN+V*yU%zwXmp zPyjoxs9=Qn@g^bTgt7tg=7#&s_-i3h#Lt4_i!-!s`d2W389z=5vLk^821xIh^oPtI zT00ni-|g2oO%gVJIXzWbTzf41;5I};5VyOxH!(fFK6(5t9*2&D!>NwY%F0T~(lUi? zBo7J=j!XMin*5$e$#36?@6D@%te@S4l?QF{WfPO*w7D^ttt(|_``1x{Jkp+?cO1l* zl^XKubayvBLqqc7pUdaok}rIHJ3cX0n4d3qd8L5P(lgX2FC^sIYX_~BN(HRiL;1I- zSJVB<*6ng7$|1$U)o3?nn?rw#o15z>0x0!#j1!Q{@DU;n``(!Z$vZzBa*xAmr;CAD z;`tV^{Oi)O2i_Az-(MH0)iq zMF@MEs#!-ci= z*p{}`_~ly7xad|&w$O9B(FYeMzlMin?^Kr+TsAh2+L+o|q{1)_#kHDw1-CH{q0+ig|YlcfL<;mn*w z-4AE3dh2FtX=1c@7jK1uT}@_UH!^r1FMZr0MFnyo3b^2jr-*Wf~Mzz z)QA4J%+Bd04Xb*VRS(l22J*zM`kLx~9>MWljhcV(FP{bXu&7%^2M_!%pJCs$9ZaM) zAJTM!k?KNaG@{>Q!)d=%DXyf}a@J%q8ZhNTzgs%Sj+ocZ?pS=V<`N1nWn|!fiVN3T zPeDs0p;#!m{MUKs-R*5dzL`a^2d$O$rbQumuBB^I`Am>x4BpZ&R*GX*P+7yi2wY~EAAS0hUG`uW?TYnfF zQuR=;Kb^rlhELHY^1BP3z2Trqrg`*5n&*TqUJb8P?&l=)kg%zft5i+Krq%?a-IhHdt9LF(Vup=-yUM}`N zBPK#1S{20S>R@Yw@P}N-=g_}rX)mr;z+$IrB2dlzB8uBSO|xPZ3}7z3H``C~df57D zt-yai^FHg%fp37s`FWqUS<7nn;h3izf?wUNY0?0jWAVg7gtD?wCdDvB)3R1Ez7dS~%;t$6()cW1O$|j4jl42bTR7 zp#t{1hO1`jHL)kZ9I%yEo*luSX%E0$?%nhz-i3VXw_zldbd03j0Z*Fn?g=`fJJh@Y zb>0nJja^*6NHY^RbdX5UW06xLWYS2o_&BNf$k;t(XN~uqi|cT}%E^)LgDPc@P_V;J9LM4$gVTdh z+t=5hFQh(|aJNoyly7i)-OdSIOvenF&K0vy_0I_GeBEK0r;lE6Tx>ZA)<;+WWH-ac zQ9mmG?J0ijVf9;DERtT?H<=H?0$i0 z(~t-ct@?GqzLdI@R56d1xOmXZto^a$cJl)V(GF2m$FjRG3=p&dJnjT{i9c zqFQzy;u#kO<*A=1bRTGKTdX`pL|~)?8o0Q)jz9klpDyQYQgQh8_|~+OGrqAGbsM0b zIATnS+oDhUK7S~C`Cgq$*JGQ8X0NQc(OKOkf3Ej;1lHH>7x0&rI@*qWYTkMRZt^(; z<6GF&=D;Rev2bj~C>G)8--iUuWn|_CfGQi&Y$HFZGUH!W-uFa?Vd?hr5POzT#>EAL z)sA|&(;F2Eiz}j))C~=1mWsVonp(PbS0aW=2U8DzG5C~o%v^rrJb|FWIzzR_5J5}s zcTJZ|=1wUtyC<}CnlKnTZ*|=z2=g1V!_!Ex9F0`qhG4?rs$O#ovpcp`2N<}Np9Md z$JL|eyCMPh8im|}{sfnoc)L^0b&4^+QZm-UdBI{t70nN)5nnIH&lpYHE+!}9@Y4!c zM7}z5Y3u_QAoLZHJwo6jndec$^TzIVkxGq3WB_T*ulvD^N8k>&=n22KvPzrq`Q&Je z+U6x(1q(0|by`Cn9KMI~GuOv8nQD_0EMpzG0VQ>dg8R#PMf1|?FE7!tbldt$n>RXJ z_z(E+&SNe%ree-d3$4voRw=Fi4|`wz6=nCm{fM90(Q_Va_l2%ndcpLO z4IN$vg}l%pC}mSa!aY+Ez5h$1$Yb1}jXul?h`BtRt}}M_>Ds7eG1- zwp$XG{geDFP0N#$U9U}@J+(?qL4~%U?RsB0WmWV89Ez!Yw$0GscZm6Np^MS3;VXt` zYVTActu|3=T51`)IfG+CagMVm5db*j%1zh3*r{z$rx1PvlaHjLgMITIxTStq@^@$G zPApaX&$T{-^$8t>17+Q`0_)NK*)!mlEA$8IQFYtAlREDGF&uPnrKc_H3X!B^O;-oF z*o^F>m9Yk?5Xs_k&d8RSQC;tYBxXv*>=bg(pVvoS`0>X}eNeGL4Eo7ACx79KKP*Z1 zBm4YYsh)bDIqd&9*DlT^%zlhCF!&=o&6%dNd+w`FSs7o{xExDT`o~VIv_s$qKu+Ep z+$a37@)77{zO%@w=H#qPf;*Q@XK8AOS@SG(FoodJG77+FV`d59v4x-@_@y3D1j4Qv zmp>*!_BC5^|8#q>(#~79fb> za39R`9pgFCBhBC!SZdy`#~U-=#Yk-{ko2s+pt`h_0f#e z687wq3^l2OS-kJ6>S`Nh*5Hv?e1;m)KGgaYE1m!YmKh3#Lhk0Y;>;+RCp&oj2q2%d zTAe+<5ZSEcorjcLNBl5DY!Nu>SRaiew7Ox8ZldtXOS9$j4A6lHhNpYS`DULy*K z9A8If6qeYH41AcF*xfmc1VEo2aoN->sNK6un^6>l}q3iEi9hr(SR|qGOSagmsR25gT(`9>9=4lvHT6ombYtg4Xb(qdzwclMbQ!{b z5)`SXu8x;r`RCVsWxsmDW^5CmoLcmF?c%wPiS5{Se_=yF6>qD?)hdx(yr2YdUS}Uq zK{{jTL?#T}@j+LfsI%D0>z|d7Gru*9Xk3;$#ST>d#{^*&1`5aO5CPPXe3hL%MfEb3 z=>-Tr_JV<8Gp3K$OkMLkr}3&yRp#0OJA=hk2{kyokDjz>w*_5C{?7tK%I3Mwj$QFv z)X@FPwITtly;L!9$H0<;NKR+6hdLqQeO+u~*hAyy&;6P;JJ|w<%?v3u(|9?_c0pK? zTh3zF;o+ zq?-7)&6$I2eUivBHNTfFL6@8k4IkE)w4yU2<^ zdxkqRE<1oTo_GtVXWL2h>N&>2XPxjhY^ErPvbB(jV!jVh_2ovO=|9wy6JwTc8^H4h zW~^%pVcQqSCG$DXS4Z=&el7U`#lS7MvR}>z!4u>3o1vE6o5gjVTLF`}nTKDQJMU^> z>-j)FNh7Q5(A5h|887HZjQR#}m*hkRrFor#z3~x~(Pm{_9*{TJiGlRa&1!ol`5w<` zN$j(IK@3QjYT+MCUQyn7sb)-SzthmQ3z00Sk8pnp%)F{*hAdXk4z7NEgLsKZnAr9H zIS07nsMm432Bg#Tq|TI*$GXj|b@s@=shYPu=W6RY5FCUb`@O6^3z@IWdY+TwQhAcm!-iB-*Cm^bX95@zhK zN^4Q+2X8zyCu2?kNJfMPIV_oeZc0bTf4%+=GUVl6iOvls>IcR^Q}J-ODpsIrWF#V{ zRr4RW)W7w=Sf(JI zE!Bc1V+i%^k^T7vMbK{oyVZqUmxn)!ox0U{8QDb?Gz{E);4>aeF~3^9`~?pdS!%D6 zMe4HHfd#FZ&^C_Rlm$4=Mh7WB!th-zAq||4FV?_41E{YqnFVTwnW`U`#B+Lkuj3Z- zngw}zZk%abo|ed01;HK9FPd&(O_xjaGlqylrw#T&Xn6)6PJ+GTNy>FPL$aIx#ZF}c z)-qnX@{ac&;3E$L`%Y~?s}yk*;m8{ zduJlsIicqc0X0m2V-6Tp>{XZe5@kdF;`|OCctgRW;Js1uZiH6W}<>3yCBuGz!^PjmqAC?`6i<(YfR1uDUI zGaKPuoG2RN<+;y(8}lmbEvu+n(Xe&E4oE^`>jXAl&{@@hMWio50`gNk6u+FT;CG&% z6#Pva$}!RIaAC%1G*~;PZOXn7+7~^EOlPN;AE_}OR!fU;pX&gEPq*I*k1y5@}H`o9K3TW&p}6TP8915E_3Q>Vm;;;A zJkE})Afx0bwew2;l-0$@v!3SI`Okl!TOaANDwZ9{eU*YgL9GhxI&!J8G76wd?js0e zDoI`w^?)jxYm0vFr@FjWxVR3kLT&~gQ`irA6&3p2?-8(JF+5-4RXJ?`YK#2CO!3Hx zK8&~zVrZ@;=JNoXER|bBXMCJRwn!kl{|h?Pv>^ZCZ4!O`;x_e8Y25UXNq8;iLW@3( zS(=HBY5e|;OIO4GyvJy;)2Y}+Z5F@d+Ti4wh=^R>R!I_&C{1N4Zpvz#sRLJriIyxSseamQ91OIPkoaHPC9T%X}%8J&_B_qRSp?Td$=)%9|G10dqe2`e-jTCW2Rcw zE~VhwrIWHx7K2DAv^OfkTzYDs)ukkkZq&Tbsn?bDw}k-AWFfsBoEHe{w<)Ll_VmSa z?Vn?J%33;=A4KdBh>Vc{!w15Z6>ak*ZpdDvyY+z_ud3yUa>n~GPLK6xNSFQn z^=++cHw4mq=tps5-qp$6I{;%v=wu(PJ-o^DzFM>2=J9%1#a2mpBIl)PxY=9OsDw{1 zHxW&ov>Ah#`WUlJwF3CtSU~5i1bd@m-llovl~luC4{6bZj}Bo*YaS<*H~A+b=b5HC zlo#YSvR@?&c}Ca{`0eI}-WX*ql){-|P8;LAC|EH=UVT053ejw|+IW1^r!;lax8ha0 z&e4)Zc(AoHlMh)t=Sv2<$QKD|QMoM97S@Qu!a^Y=XPOhdbD)e;tJYVv3gBY3a-CRr zMFdVD{}yrYJv$DN!_5)~nJ0+mQ?*0WOIy+>FOPY~x!+X3z`0E>1@meu5GUO@YQ!2J zD>(9FE8DD&HF#gbu!OeyVQfvVr~3>@uxL)AT~tr6Q7?DI{aAzR%Qp=d3!UK3gz`Z? zt%qCX?y$v7^_;etqbY~23f6h*x^>)!Y1E=4^(UxfbI4@`r)^qNWCa~2Ip6IWSN zeij~UE@cYOOG}N1v`thBllss!o_Uj2b=x-czVmK2P-#mYG&v9cOg`wMgR}s4;B8-< z5N&u>HZKKt9Dvz2z!XGol)22WXst)l2osKsy+sys-$7bl`&Y3VYUu-3H}-P|nNuMA zI(4xgA0XwWL$7zWwo7|Mz)ays+-HW=9j22Vfju+wAJwtUC48ga5B*u_Oxfsn7O@lH znGVWnBkEIs?i{QH+o)xEW!L*FKOY8}9-r_p)_bI`Yt!zrbbof$-+U||wqAxcIMLG@ z8(f-n@sQFJHB+YN9#c8K9@wp|Gil(eRJX^3@OlMwe+W?nr7iq?AY}-Y1yW(ae$GNEusz17g2K;=yp z{DXz*1sQlTW4uPL1C1!vdfs$%;OmB}iG2g@v*=G~`o(s?bcQ0qhJa<&3g@DxQoPD} z*1btbrmWuoXur0X-Kz31Cu5S;b~1U|akUn$dS3oa3|K{fmNqM{_jsYM2gInrO&{TtM3`7PFfR)Wq>EvGt}%m8x+Mo`FvdY z`viu}5q9m-z=iI<8Y1TO5I@w>r|rJCch@$v3Af5C!4uYKx^er`$!V}`tp1fwmEQrd zlXf5fHlvZYboU}k*Gxn5&b!X*Q68TdBtp}5I*hqK0BNbG4JX44=hLsH)|Zz;xBl$N z<|X2Tv|b}GC|rgxSk@A~B0?(Y-uX;@q^EdvNDP$Et~o~%8GsB$SQ23C>9?=+b^zT? zY@Ume$g_1u=~9)dmFG=0N8pnO?pHvf24p{}>&ab3o5KP?+0B||6zOaPk$aG)59?(+3bH9~>=U@dmR>zrP04;i^>Edv)ds7f=;MqI=wDc3z_(%MUGk83G zuv{?z;b%|C1QSD>Ik%JG&t5EL8}%*`O0SRJHzyfjqE3L3GA3w6JaVl3!c(6cRkEzHOD6Sy$1S^811{-qq zfJ-chlbqFILp|q~3*QMb!0vr3m5@=;Nv)mb4G{1ptgs^B@;eVjP>D2X;1_dACn94s zhB~P!-ZQ0L{}yjqfVF67nRN^XO3sOFQpYKx=3+e~S<$uIC*Dwqe1TbjA3B&Rcr1&m z9i^!`JcAk82$5B@5t#WgATUapEa#;H5kp3Sa!yWgb@(mpzP^@oRbYA+T4|2> z=jNQZs@9EGUG(VZ%cn{KI>*E3hKM&hNkMD+9%`^qlDyl5IU>8Nd_@RYjsgtplb-q6 z^gNNvDX@AV1{=?4E0n*?lpYQXXY-e_DYe$&QHbM5jw?>v1GxlOEpMyM&7+=8+q&yu zeMg8!c^mXzslOkNZPQ6OqG$?ASq2KyxVe%2WP`Q5yd4Uf21ptC8Y*B4?Kw-uIq-&z zjSj>v;N`2p9mRraHoZs+ZK?-_v4 ze)vgbEOLFS)p^;DhNGizybZ1!Cn{pJ@$zDoF0+S!-pNMnj$crEXRCNaD?$VopAkm@ zwF_A7QISY(mXNxF*%J1o^)$&ebUTjwg27XG-+H@Zn-BX1SzSy)A#fY8SY+dwJR%q| z0;-yY7tYT?Lu~?vEg8r+XVT>ZZ@dhKm;Nd}MfC5AIweNH60)qWoxw2g^7>&CXh|4M z6LRjp$T2bp1RcZf*husXrU1481CMRFY0}@3p#sXC6!y|}XeH{zO91;Ec}VHKibkBk z_DR;l=54spmie*wV126l^(Aj~bPQ^ymwo8lX7Ki2@LnCIN5LkzQAORm6=k?p(yyMh zGdCJdW$Ie4+*q}pW?QQ_>YccOvc25;*@~1GRNosji@7}MHb=e^GEebJKSo$PpU~fo z^UjZhoNtsWk02yXzyg2TMe1I?KB$0vyIFRIj5B6kJ&7Epq;q=#_rD9PgZHthQW&OPH#LMseNEp@xP)J6cr6Qz1ni z+nPKZtlzxzUIsUwC@o^?sfhRt91&?h`$x#V$QC4YUBQCxaho>)@}hd+?L^k|*I6u& zOx^TrV?--OzqO`~*0m z)3&KlUSvZP`@;;$^p_|gu0ls>VlmSnuz009{ja9V*T+Z%8L|4Fauo9T{`@< zNQmh2XUg_t$du1T<&4BSfb=$31$M?0T@R3(cxz_ON@9jSJkxfb8Zfg;HDiczL17N? zCY(xBpJ+mXrwlVa*wqItmGGx?gO!9VM!olaZq-*KO&tm4x1y#pIbcx{lC=HT&oVPH zJ4P=mJ~y~l@gA@@9mBDh7Cud05)MB)@kFN1dt9jz2DKyq<6eYjs&NP>dG6lA_sjT@PnCTy|uy z&3Z}nq!73Gh4BrzNvChgd$2KgurcxX+z^8F_^6|32~Gy*y&=k_6)7m5my1J*)3E&E zcRVK3h&4GRfmzS0C)C;X&dBJ;N7Y*oni3~wbkBPh1592PK>POFpqlB98S_v*q(ho& zuSN`zt_0pDHVl*oVOk>nkY2xz0xoR?s38kFXP*GAJJZB|evoo{WWuYm5_bRCQf;(d zJVQj}l)d42RbU2kd3;;enT7TSbdL1M0BD!N?WR=$`G=n6l;5fA6EDmnmb_}z&3ICN zT7gCIvTc-oy;VVYbTKO$eB57aJUM*24WHoAvCUgdHw9HxRF2J@Yworq%BC54jwdDt z1VC$M@RCgP8j(4{w9v=EX*le&QgJKnNdY8drzWZVDEfaDPD6tjF;!RgB5MnWxT2}| zfyBTo>^o)2ZRMqYY)}+|&h$cb`57RLjsvt_w=$K_#kWNnArB`CV59Ff+$zBQ!~WG5 zt*-&w{^}C#_aW|jHjt9yUHDhWED1;pIa}6|V!%_&a{%#*ro9ibzUw<)qUBEI{Ei6XEKe1Gbb1yZ$RL1zBdo!|Rwq#V$l z#ctJF_y4zA>mM{rK(3W3*MW7Xsp4b=D3jPn_NNkIi+=o|JJl;Nxmdtr`lwMJ08D6U^x|Y{v$NqQwF{x>p63$F}zj3ao>tG{zubC6$S@f zV#cKYP6J5g3k>Q-+u@=jF#p_9`n&m~J#t|Jz5_7I|EPj4N&X)h-7{$&U?o7D`hT^Q zV!)uD7`fRq0`niUykkGW!|MSn?JK9i@5}@Esu%X{`u>m1@lhrqvy0$)!G0%V>`Da& z_26{_AAwjPF{&cqR$Br*TpzHKOP&Ja?jOLaHa+;?%EJG;hxLEmHo*jlv4kGe_9xzdq|%+u~5BnyL{VkP|=q3Atcin2dVMyAx_Yh zb4V2N6XwqAmixCX;}*iN-v zKDRcThaz*?Vs@1@zo4S+%9-lf5TJ4AwiAuJ5WxBOS)#vof$w`!ik;snngNjCZoYJ~ zJw|ziLQv*2QzSZNT_8Y}0q`jcAwohTr5p z3%R@cueiUhl)!Hv18pfL^FN1P0_IH>x$RMLsbGOwAC4sKZg$2wj*&fO>4fihcPq7j z=G^A?(y7u>0E3G{2g8c~_B;Wj*aj$GHS^s{|K(F6_d$cnb-%V3xtyD6BxrCobfGyQ zp2^ybNkuO&{Ri5=fh55lEBpMp3XNw8UQ?q5ub-v}4wkFk-3^61<#UJ_m?ycoTsApw zInu4H*;6iW=MLYtPYyW)zjgj=O8#`!z2RgdHow?*ESogQ9>a`Hap7+_6kwH8yDgbM#j0{~kAbnoJq#p+g#8_-~*s-iP#Rn-FZEOoH= zA;ZpRob++uDKLEZ;27#``L~IT^0sx)S8SEwUk3lrJrS&_f+&Ti_)KTvYw=)kRZFfz z)F>({JFq`OJ?ZVyrEW~axejRe3I%PxKboQ_vyELaVWJ5t@3@s-qOQ1?#OC;i!&}Jn}uu| zm6y%-e}fi~cN*C7j@b3xB4YF+9?Ks#J$cVE7aJ1tEa3fNYtXdYM*oSL&g5_5raVR# z$GXEHve)%%B=Y;qyu?_?vTDS$G}UqvbI`X z-112j!}TpQQqJQCB7~7hSHi2k9Ue<#~dMk@GMsl9%~7 zD;BE}lKuKKnHb!LYz03oKD<+f^eew(l&VZ4lW<$3poyGpeoW4in)}%In2r!5>z?0R z65Xf_L%JFg{p1|(2gJD*R8n-VZ}Sdzm`3Ryh z7-UhcWjJ9d@wexOVo9s2eibG31rkR}Y@qM?!WCQBIlaNh;S4kW(h~a@DT}FZN*JZy z1)`B?$HSr@E?XWo`|Vukz7a|FX%{10S%aT*Fr78}<3yczNil;5BZB^2`n=>fD9wF}*_AI>tKk=(9q0f9F8-KKV zQh9C%)r<~)+fXqw`6KP5QSKtsEW_~ajAs&FFoltiOmd)}Q1n>Jh|9dO+rLMHH-<9u zX6nyMjhb9MJXed|LkGsbDly*IhoB%|j;POt>(iwoCzu?4dWJ>>374m@Li>Ay{WSUp zwuh;I{|4;x>oL|Lj4CTb!ElRyakJ*g&E6qNIG$Wxj_Y2}V*@QqGOFj_t$eB9&NzMy zhwBzj$+`Ap)0*XS2+&~zF!OSE1_Ya~eE1uD%er7vJgUSr1I;#GSvTT66Fc5^S z=VS$+yGS-rBr6vC9kDL#qi9O#L^66Z_^phwR30N8C==o$$a0@$yKwVY`~b%9#5%bqFj%Iax(!(NeW_Np3K7pnlYuI#VHz%gb0RX$5$0E;v<){aIyPw1&3Ld< zmnGbNBE)*s{Y@zy=hpt5KEjllnY97A1-28s2P8*bv6b-^9QoNCvG3uSnqu&mJj}x} zFR{2QSPhzWbx`kqMu3*yd;k4e72szT_{b*GcnnNDO3!|)uK#;uj?k;2<7)=85R2mxNueu9W%5rl^`AIl zZXQoKhea**Ri!{bcUz*Uv(9?@K}}-}_}g|*?z~|K^RSzWPVIYHHC#)QdUe*tt z);ml|UZ;NM$SQvNX6;rdN;Smhqe|U#iITjUXNY$5Vy2dSlOAzqaaN@YfV}ca|3Dx|xf@MGt&3+HX$mYyr^_6&adn;X+{MiO zvGOyt2JVGA+38HP(+++4)mBnh--rIPL?1(I(bf*W15%vSZ-}V zT>OTm*={-L&o;^Z%wFiLNt7=Z!ZWO`AZm+Tzzt~Y_f$z$Kxf)UrMKl&%pHKUgntq$ zy{oo;s_!ifJMw?;GyuI{u^L9RGL4s_iy*O^KK^_){mtn(8dWbzYt%m9Kof#eZ8w>ndV_2_qy=VQ4gr^G`SAlsruyAM!_3LMd!!5=GHqk^edG2t^aXpt zBx1!twz-f_X;qWjtQ}pPgTv%yaeya^)GMW{>F@tmwEEB z4XUt?M`D3Dl5ZL)5RY)!!@)Dl)OK(E_1!#X(7MZ&@G6zSULf3ZrIDD!U986rS?@X? zI4!E*Cw%fsXD%@}c5$97V+~kT;7TVC(#*{KU`}Vt46g&p;6^;~pH z72^*-%-$Rp2uj`hs1celjpw^M|2SG-_Od@uG>Q1U{oCP~dDojjR&SR+oVW6E^&!4 zi}h5wr<~$ZLihH!!$$0Z;&}MyVnF@r8D+A>X=# z*z#aJQ0*tBY7t?Qsk%&u$J=MOtMvH8!WszGF=y3xjxviU`5)~4z z5ui@0n<)ymd6@D(i9lHTkJ(2HLck>cpF@44Oncq7Bo`M3W7Y$@aoIpyl=nNe;KWnj z6;78#ge0o*#7*Iva9PJ-?~n|WbDSVI1)Q!W-Iw(L{9dShWl%JyuoOJAGe|3Rw@&PDaJi)_FffN zB~e!};!}-y!^CRKOc((BXDa90tznZXJkU>?vAdj#EE>xU7)%6un@2vWVt0pT5LTx4 z#OTmifq_tiv`UQGOKns0{v?EOX1!Ze8-zN^LV4b)5 zEY6gfpTH%i>Vf@|r5t__?>5umqf;JfEqDP#LHs$r-kC!sJ=s3Z-lzf)vUVPVX?+APfy!h(@PMvT=OrwVfz z*;Bn!O5!I-BIeV;#*D|*0|*uCqf?FObFfziUe&7_AF^OlwSvv#7e$6y%~72t7b)LX zbImk(HvPJR);8s6GgeFTt|`xfcwelFgBiLRW1Bch>^==c{g5qi&kk^DaRF~*@o*%5 z{u_u=hJZegNWuKep7ispdxQZ0Q~`$}vC3WR*GYl29r-;VTn30+W50tc4&f?tEueT>BUcc_$3yTuP z#``D5_@EPFH{g6dFNIn;5Zb*U`entFmc?EGl@AK<71{H9$`wdPq{&9Fi+a3ZZChdF zo+0)2#0i$2U{UzGpGsIt>A~fRd^XcNt*~EMPxG^-ITBu6n`Yc%~Fj_`R@#M-R5%Gb{ zAltz?7kk0|g%+sbT3Ol-JgMDI4fRjcv^jyTCK>s?lwuqOu!|om8e27MGiax4h1)I% z9}RT-UbtFqAcsM`3oO=TKh?KQPLdD9_F3_$?%`FNqG>T_(oRZgc^8n12;xcV#8e3J zd?m2=x?C2Gbx7=b6+{O8fjP>2P_kHr_WhvEkjwZ4cx4ZAEU*Lq`SEi+zo=Km-zc}X zf5lCF67-b%5;na=uKBv=AADIGR4qICW;1UUJfo5wc>4#=E9UlxL_`_&zw*(efV({S z?51S@WHZ2KfB()MypYE+)0d_-Kc^mY1%|Gb#* z3ZeYKa^BYSxZW6S=)q;ZIW0_9vno}^{^jk`pVG{^fQ@KZlovz%)#;Y(q+O9vc{_6s z_USqr*2KU^ux}qf)AW9Mvl8GGCJa6W_{y|d45izpV^U|=$@29clJ9Ie4mLBoIA4Dy z_^@y`0Eb?pq^t0_@Ni0ty^Pc_velN;5R}a#4a50R^|pox#!Tt!-EvZ${cJvU@$N~N z`AZ`Y3wx$~q&i`NGzXP{fEt*kaZj3?NUqO+8Lw$>$lrTN0RPR*Gw`OpPNu1Y`d!Ho<-` zko|*nxExi0weW#5zC6!GWD#{yl!%s6$jJcvH>8bn!h7%IUKVDrh^bY|c^(P~9S`B) zC8`kYVXfot!6n-kc|pI-QYw+iB_~AfVXNUUbI`r~auBWFm}D=loi1Nq#>!2BJ~YYr zuV(TjgKeH5nVrXh^*R6XDL!^%Ni$Wo*Bt(g#fVt8iqRa^?;(K+wo2YyaAJI-o;fhJ{ zgkLsu2=6PC4mhoZgwebSJm^22vmIx0E@~6JIdx<4@~mJ0QA_Analyz=bE-l+PsLeY zIr;7R9mz2_oShIwFXF_+F!S~-z=S;(c;%{x!nb&E&G#b9ffujP9kt~@bTOlg(0j`JaDT^nhF=wf4_+rVtm7mP0i*^##C0)SW@&j zwBHHlz1NOSV-(1zI6|mwqOnSuOya*1`B)V@+7<+AEe<^rO=NB=FL^(%G!2=9IfNBM z&EX(!BxQxR2D*V;^`pvm*AmUbeV)(C4f=B~e|Kh>C>-~>q6H7xH}>ZpyEp0gLDzk| zQ!ndB?K7XRU2VEsMTJpUPB%6bVvEeoN5jKtZbo8>H21FNlT$|$vV?nL4L}{qvicri zIl4LF9O*3aVv*q=gH5whAX&qqeHqWVx?XO<0}Cb_yo-|GHu8NelwZ)Lsmd`;vcim< zws5KG$eu0~8*Df0u(@>*-{*=E=g$)wiP^;pRn608TKHZx66@t==MoZC2Rqo?@J~m2 z(o&wq0V_SR-YgXpyC8Hz)EsTz3}bNL54EjGr)zcA8M%6$-xc~hPAX7Qr)5BBgLBE1 zRum2R#K}>q0ixbc;ztWtBAZ_r63N-Xqqxc@P48waZY~E%KIWBV6tV{u+k#tvbdES@ zO1lk|x#c)4%RC;MhFe&bkEx#hSyIy!Ko;`Edet{!86alkr||)?aho4k;rrIfWJa|R z0v5lhH{JVh?&7p7niflDE}0=+=GXlV>;}GxbW;!aIj64#8&NL_*EAFy7w%)ps)|y- ztu+8*Gx=J2k~?@=I{XWW5X>^?Zu>epUhjLl$B(j#>;7~4=;W7Z!F+m|VW1*RCc5KC zjpr7)^ieYzt}Ug96#UBI>sWMy54Y2#<)}qh7C4r^T*CgguOqT8Nfkj3?j^-!T1+R` zuu!q&rqonS*T$38t7`J9!Ne}~A>~*8`h_4e`kbMusaiGbwd{0NVX(f(A3qrCXx>c& zn`mNXEfWaWxeEyj(Z9?hr~E@^hpz%wkRLvyccA$c5G#u>a0s8tP|8~Q$ zkkjvQcnf0C^>;8~AY@I8qj$GwF=B8a6 z4RSmQlWsL?iT9Z#u_3#S8GNhK0fY$i%g9m@9J1$_VWP+QU6|A>D#gbOF0#j%Ol23G zW7IUV22@`%CecoZedMF27aI^y*RcU;fAW?9G6Ogzw$XFh~u}7n5o3XIR8Y3A-`w3-Lgw)|xqLck!e~j$_@! zg^M}?HLOKd>Z}oNCm%igH0gquet!%E#{F-mC-VZ^S8KzR*CzIhZF9mRN=5#;8|?t@ z?PwiI-Rxrla8s$nt#{~pJ-bo*!Op`d5g$_Dn%h^$u?!5&}JP=*DYoz zb6hg%C`X{t`m}bh{-e(~Qz*=%O}bY0GHcdwOnSpPQ&GCRW?sjG>UIk}S3&=Vr|fl6 z13oP)kPLjpMB^kKe({Nx1hK5upC6f8Enn+NqAxt>OG&x@5tSRcE{#hqv6#t%q_&W> zyU%D|R&7@d^|wC_#A)(?3Czv##rhFUt0X8FENm)7wUIMPZnXwyzCMC|!I1_0{tLQ; zxb)egwGiJ_0d=~$RB}o9%*zm5T$!i($9dlCdQZ88T@08qr3=uBBLa;p1m$({148~{ zp$*Y@I?r%ROWQ(t6wrwUeD7J@9--CdKPvh8MB)`LNu*an_&w4@EK4#+Rl zma}d*o1jnPGv(yz*a$fCm z?~3{-y9SCuH2xUHyoY&UVacm#(gvQX;^5oKD0m?Q zSd!C5bw}#XyAmM{M&%Kx%au-8sad=%_G*7Q#by7w+2QGhM8tb)39IISM+b4S@phas z=Z+aU&#HFk3kp@+ZQ55ZezYQ)J9_+DtsbmuSe-7mUWo@vjKpibq$AFb9R$KuSlZok z?G(MT;m2_ipI(v1wB&CrFC=f`9|@-LuF#%s;66KT%Xf%wT*BHo4b>w*JzD@fF`(mD zF=tBirq1i>20o6(=uEggz3d$Z3uAsG;wEB(>gZYc^5!yK6ik`olU2N^$t2eB-|y@V zRMeW9zy2w}`Qoh_8Npr95RFHFfUoS){H(D$?Y=+4#!}zZNue$|jE1JWhY90!t2H{8 zwIn@@J;>XoK@CFmCPar&`F+9J!NcDD#xpu*Obho6V(+6Cwee<#{jfJLm^9gq#zgqEMo=!PrT)nP4HbLpy>jLq)<{ed?UzBvkRp}P<<)6%u0nuYD zCLqQyHaiGBuhcFH?ZH}AiK+f?qyBj*n>-nOmWY&R1_5EY5h8SLJVDjCc zI!|Ez4`AbHH!~6PDIid$P5u79yb=88KUx6Vuu! zxRx0U2az!OzExhvT%BB9V%Jdnqz*}De$P@Awf6LfHJz1xgBo=^n0rA5#44ud z{x}BHM54wyTZSgr_AjX=l7BDZ#4II!V_AtH9t7J+yh|99sew>S6gAmI;8W^rf16$+ zdsYWS$5oEZ>c{WMR2InASYZ;AsJ*w%zuVrlyQN6}8Q^GRtyUBJ&M>lJ^D+b#lhB_I z^0<#luS0&^vIl7@ajvic{L62no9_DG8QMHS0FXAJ?4 zN*bK9g|U?%xfkBfQgwK$oM()^4;vd_Q4;P5u1LQ&qU;cf6STf%jG}P-E^TUw?Vj&1 z>4-jFB1s$ghB99!Vqlm9XpKmruhT$K)8~bn>)tn~mFH*UI>Xw`RVeA#MB6hjz=GU$ z&$wWJqWaa)o_z4aEqq`5-~+g1N_(ZzR_~}&=aCsIJvKzhKzm6XWdEj6Y7S3_lB<$I z#XMx`uMbpH_|Q^I)F`KuewY(|=6&jcGjkO;U&BC7EiqzC5x3C7prMgIW+~=qph?&^ z?^L@Bf5g->1%ADNJRw>d)&h^kPDLHOe^2r@leC=AQG##UYam0pa}bWf0L=3m`)ZbhF zu`vMIe%L}%a_xcxO+08JMHMR;UB0_%JZt}{kx&eke5!YxcSvD*zzZB;&ht87&y@w- z?&AtN=aNblpFIS6i!&f^Yx+VS=2WXT5I85lz?1D#G+|hs;uhga+L5T^jo6nkTX`EB z8v6U?4`i-1(;MrpuG#MxO$v8_5`fM!qC8=$PiXUmO8J?tMV}2e4hJO3#TnSC8^sL@ z15ll;H+v}b#CdUV+^14%7C2~Y3!X&wY%$Bb9oVD{u`Z>M z7NUEl+|k@*QT4}6-+re5`TElEtGF^l*28&$%#63PsLa{RsQqYP+^}csCp-5hsD_SeG^m``Sd;K z_}U$Ndno?mcx2KLMkBzLX7_x%;htdmxIg~(@E8Gw@sG~{RI}B1%TYLTuP$G6P}In# z@E>8e+mmIP$7gIns4R|_^h2zG=jkcSJ(t$4kFf}rPrpY$#HMM-hL}(CE9w5KSS{tb z@z4jz-!R=R`5UfbK*0Mkm>etWYX0T(8xJx?EIPUKJfC0ydGB5M267S>9jO979 zgMix&`w6HDvGFDRoI8l-tu4)YD=r!+@_##0^=%2x5Iot8oODJkn~ z-at*AtY7h&$9uPA`HsHRe|p4}2T}gg{0z?RwpWS7{R*j7E^`g>=YqyFhJ=D!*iit{IhIbZ(M)Y&_mPX!T6k0s#g2?pt#{)8#qnlWZj-=;9 zQ;}y#cfO1&|Ig!3`ijT)YSjEn5UlqXDW|24s*(|{@fy^(!)|_LQW+KzKlidIihNDz zbnqQ;(tLEv3i9E$eZ6ryM8*Yd@2>*$4`6iaq!QXUasABSKT*AYK!ipae(O6O|M)EP zbPbQ0S^Ba{n0q^>6VD5u?~{S8U`_AnCygPS7hIXM0PV6`x25EyOe#c>|2(| zlC|thk+FnG|3UBj|Mh;G>s;qLGv_?#Joi2K^7-7)bI$qrf-l@ABS!huclUZ8p{BF;9)}?BHPm7E%;A?T9VyDF^_XA!I=tPCSJSnq zlN^RBO>DzYB2YWp%)0n8^TQxBR*oZH{2#c*RQ|&$7YfvXXXCKvU}coqQ4x*VQKo= zvkC9Ev~Dj%)M>}hv*+#HJ(hI~IYD$cIKG4epM(t?fdK$rkcET&&Kv?EZoDhf&L^2{ zzXQ(DXZ@B&P?Ej@ixbs19~Ef7mvi2FRk=HNe5vBB41=X%TtT9oxK)p6NkVZ$u9y71 zS-zU5D&0gJjodq6l9TF;y0{_mxQ||a71Bx8%iAGW5*@EZJAlBOc9$XH?JHQ4kp;5v zOf=*JMZjlDrVi$4%?{zFS*l)#A=iLt%qNr2u)3Q%zLwSct3y_01ub?SITR@Z1AM1G z-6cV&TBRel`uW4Ro?Bo!8QG8Rz-JEWqnw@T&Lg;wcUj+xexmctHq2Fu87xH91Iu+S z`qd=ZL-1-BBv}ZFwImVH;e$_si%gNdNkhY|O#y5amH`Q*fhzjFq!)cy33bt8eh%7f zyeX3wHLB+ZKW)S`K#Zl&aH&y>Vw&#o`2YMv!nBj3))NM-fy{H5bB z5tw(6fVxgJxPrH8My#4w2RAQnzev?tWr4v~A7qQ<+6?DM5}lA+C1N*a6$o+Cu`l>% zb4Y}Mvk7jljuw#NAj#D=S8=mG(k2qsras z9k-_6UAVz9m;_?=6Eo3tTSTI_zq^|rt)yb?+0bCgKyN)W5U3~7lJeU##|~)KpnfOK z9-}iVT_q|&*|W&o9Us~tDja+vcumCZfv^RQvmwHATV=p!P?zp-24*o9XUS%Ew&04V zp)o?%BhodP{5*6-{G}8wBL<$dA+FGSIZnVFBov+~lyR5p$!VJWoJUHbhFjHiO&;w6 zt8kg7ceUYUZ+Wb@BlE1Wb2(lT3cP z$lFnZ{OKYimc4Hsu*l-Fo0?1goQSiKaH7{JXHyrmGQ4hz;Unq zSeo1W&zZ`yb{yMFOA}e&*VDb`^jhWxQ(h9`w+xHs-Xd8k>?;Rjn5tK{iU{Y$Y5vQS z(Ja(wyU44=}o|sdX>-B)Lmdscm!iJ%nmmm;g%mVe*>N( z84_(1^wAaG+0G`aSd#8`hkp2lDJ&?0EI+U4gFIv!Ka*0%p?ZNnAUJnhB%=1#mLK$t z)Jo8*6s3T`{X`jc+7`LFk`c?A#I9MS_qTM^x)ZOb`0`gdQATzWX{rq=|H|@41-e0f1 ze?&_Ge|1oD>7|+2yhA=Fs{?*jTFvE(AqJKyfV356HF_g7=$y~&Ds~oMqS6)P$>tu#M1Iz> zp?)iUxnod2Yued*x)zXmr&tx?8HhRTAo!9up|x1ehvJW~&oDN8rjZE91kGShIu^zq zTcL<7O3ju&XmcoCd(_e&v1zA-6L^nwE!$;aU%(n zl(4Op%6=s;4+d0H&`ut`Z*d#V|G-M;g?n@p8zawOw5+PxG})6ry8h^aAnh4zh>c7 z7uHVumH)wb_W2P@pQf+~P;;~fFnspQqqzjL-*Vw097(nA-#rlAVFRo`gg z&_LZZt1p^%=;3kx@f&LUf~nF)&Zi{PG{2;<^klEHj5;5AQq^vih%lGMOsHJzWUW=E zxZ5UMf^uuH4jwzjYIpczw-tNLEzxD7R(e&EL1gMeFCk-SNk(NQd+Pj2@r*BJS$)Mh zlP_~*?kkG47zwoA_SJU}*woq(dp?>wTS`N_QCco|5iPzXrM;^~G0RP_#z=^?+*APE zliKkSK67G29`gvWvXEHQQ~O~|q>QKd&W~29Q6Nky97Fy{-{S{pMR)GAmbV^uZu61uwd8-A(vZK%j#4-~I;Hjc#nSW+6yWD*e(_IjxQKXza z+G-Gdb(EOt8)ZW=F!xpqx~P!mMKKKp!sQKDL*13KXG^I%mNe zu`hG$7(GVspc)_#cKCuA&eA$ZAPXpVqEBRILjoipLV(g*oJ&^{XVK2?v@w$Q|o&&~U3+Lw4B-saSCv~S(_sm~HAmm0?{ zwYW(>Cw|I(!*&Jfs6HN69UG0#s!ZxMU#^uM)gvoKXKE6d8p-`ZJV0j!xvTh?q8>)= zBc}M`>y4Mhtc;(ZrxPxwQGm%pp4@ZiXS!IJhJWK25)4AFEdl1Al~tDSm*B58u4t}C z3dX6%eLkl(r)nt@$6vyD1FolqA`=D`QFL7o^Hc8@>-aSoLmOjUUO-VcdpEwtW9?Vl zo;CtSqlrZtIfS>9a9^O+Us3E%He+SsI}6_Uu{fa-KFIWSPj}U)>@mUFkXT~q+82Ri z)OjfZVkYj7mm_g8ZL(Wt=wf}#%z_WxYaQa2&+j|j1&$a@B3$=K&nG$@lFoad6pLvB z%fMKC&Os7Ff`SOX9Zm~`b{`bR`|g72k*K+R z_ea>ZA!kcm4BGL6s;-1Y%7w#BfF!&%zbr+-Gk;9(nPG|1dN9G1_Fo{56 zgYaNAXqxafH z#Z#aj8%*rPUBKNg6Wp#siqo8WGQ@7Y$Wa!8Kt=>ypq0N+t4$-367`K|RN?vK_{n#n znt1vfn@-%(7<&zGiE2fpEhx@3*r(1RADz&|kL?$Tfpv9WbDDI~d_Nv&{5YSqGIp_8 zT3+w?>iI+#Ga*0OvydDG5=qSh5wRduAa7Fk{a+ahDQBh8abpvXR+_@jwUe zjfFNj>vOIM|2IC`rx(N5BO{y4ZKY9V>UNKkR^W&8Ih9WFt$dX9%Z$vbNPZNGk4P@> zaeQlW& zp9Vr~7^5K+k~6axhVJ*$d^;UFtcf{?AHKXzwIubNe6eHG31~RAPbM3DiV5+F+`N)R zOX{-R0c;P2vm@hcUG6eCVJ`~2v{|uk2^C5J9;gci%g}sV#R((BFC5GyBFC!E$&A0S zohX}=dWu3dO;&9D3(-#3Gdh39e*RrYNfD5NxiA@$+-KeWbKRv+2-GR}T#|D7t)br) ziQl3D_!KzrwZ(0RoyCIxtjCd$6_M5bES>772855%|926Y!>cb!%55h0A7RfV_$|jB zQv9ga=#%-{Gd%NKynhZ+&cLe;faqsFn@;qvG-n%k1@SWFNV$h>X{@DQoR5S?RCV zUgS+6ZPp@5pP53HW}3>@?#M;GEM;j2Y>ocWuYdZ8I_ch!uLumdA}yw_^k~{!$W-J~ zdi4ejVNk=AH15z*bjXlXpY4~Z+&%f%ZfL1oUKBTv;7_`ji{u|Ke*h%J4Rf0+OupvieWaMyFYaEIHk0W2TxArvj zyivcDB_&`$*C!>70T3Z;QNlnD|GHyw&zNS+^R~pLQLoC%YOA3(qgt|zwOpHilg9%L z3UIh>#`H)YedPm>=k-N|>U}R#wPe`fp{4sTjsnj~IL!p&=8HrEHcFVGEwP`zt$c0y zGU;X241HiV6f$99V)G6IB?;&9UJ1-?^<~xk?G^vm%mU-MMeG!N-n1e(EzzXBjq^D1 zt_M=X?(ER<$E}?jMKLK?(_((qr&W4S;fGJVP#;JxC?Od?R(+#px@z3vEdR4j9bf+d zp{2CBJv$3fme_5I-B-aVa7wAzY@f+Pi1T-~-ZZ)MX?gQDSC1|Lt^}p6Axn(k7@BAh zKTrNnCHrbeF2daPSN8c-LrJ;YD&yt4udQcZkbq65i{3<%+-#-Osot0fk2ENq{i!Mc zWYlk)`!CMGNJ*F@fX#qD(9SLIeFC9$w^k1>{o;KrnNCd=p47wc_@Zqf zD%`(k+CiRVb3KF2yw;0TX%DgRM=~UGU-rl5w$+wmx|l!_}#olheTLZlhta ztoGU-NQg6bXo5o_^&cLpCkE@8{omSm@caxK+VmdINqN~<&ua!E%0MC1L)f7iz}(uRi%8EGJXWV9hh%^ZpD{m4j{QO!hTfz z&5W?7;oKLqNy5l>LIBkFhb)p2&nu_El*_{AOT*o3G2d?*q;GBpzZ-tNAdFG336P~|f*&5{#q(KU1o9WKotd~(pU zREV35-k34vIhL*3;HmFU1S3#ZrPMrs4fBKUceI<_W~*KsZ7tHRU(UHzAaECI>U z&Kp|U?;p;5XVl1$3&z{M#rUJ`SC76Cl-zkuIIG3TT(i;ky27TYl6&@!wo%Y1M#J;w zgBt{yMm0?GN9~bC54C)5gT2_LqKeShRMuiycB{l|r-n<2g)6#4YUIDaqA3zKtwW=# z#P2t{kS6erf}0m}DV@!aGj8(>Ug^k_d;q3zQHPaR6(<+#aRVANsKSvkG*t1@o+~S) zWMKS_<(a4T>_1|qh#j%AiAvlK>w%IPacsr%)f;OLLX8ios=r3%WdVT;ITgjsp~4Jl zlM|e)67JR-x`gN>+|T{Ai`vF|1$Mz-sQO{CBH-+^xa+-OQ5@1Uan-kLt)O z{nXnM#zpf4_QGn7+UOv*maJ;L=HHrgynl38dAl#0@?d3gT}1?iP`Bu3vTxaFJ7QYV zx#aVEDNO8zCSI9lZvkx2G@({~wF2T7TA^;Dq5%d!iG^;v#Q3hFKG?W^?s6?LsWUDk zy`X4VWl8FPJST{JPd$eAGm%n#&9F))_Q(yo@l=M~*6*7JJNe9CYsGg9K9$_)7_obBi&Zal-@MQ%X>n0i*`0|8_{kbq|6Xx(@Ric5e zSlOR#MvXgb%mP1^j#KGzO2_;uEoC6=j<2kg*BS-y$ViiQ)tqBjtoR1ZCroG|0UOgjFXla#xPWBO^J^|? z${&z$=XR^C$0cSGg~!acJyC8v?DpJ0xcQQo6b$O^l7g!MHeyQN$gl3g;ldeGp7bd% z#Rd5^Y_$Pxx!YSEeMm+CA~wpkkLca7)I+H0!t(gw_;he9B1hb%?L2osBcSqbl-l~` zQJv4H{P3T=erCWq3LIP^i3ya^AAgNPMw*va`Pj6@Z}5s5e`qGP*2TJ6fr z7q{Rp3yW|#QTnYBnkVAxrT76(vd>6#|4`_j07Ezl{oCNd;q`$wel;qsPO8X{3s+tv$m2Az!9(1DU0* z&w8|@OPyG_#{Ri;#8*{eH}o=k+VSr|CyeZ^!17*JB#Z3Bv^bxeiT@v5W^PEVtgaG= zfo_FY0!`@K1GChC^ z=BYyyw8AFq{d&j7qwvH#ipTd;n0N(~h1Bd01gE_hGC>Y1&-D|!u{uFV$LErT8V#yw zX989Vn9`-LU|rVi(`#}DzQBD{A<0+L@OQ3UDh5@@mk$k3gynQ<7JqNf468t`g!Ow^?(t2BcFTCguSNL!;z&al7v89$%GzMBVGDu6KlLvf& z?O2RuTKqiPy8B;`

1up1>Ird7F-@erjEA6YKS!L+uq|El(x?pFO-QN>N+Bpq_PK zk;}m>AdcLJDt4GkM|uJT`dts_Se#9Q-U|he2s}bvSeDUR5r{C;XMx5qafkZDo35Ek zP95gUX#6D-D-hO1;vw-Q+>LPzVwV^3@zwnqmapRhj8G`Fpr8b1ps6{Oq^t~e(>Ciz*7|?5mMa59{@yj-T5o?;KuWkmfb(y^55)YdqP@x z`7_TdhW~RAP^b37Ur@mb0aC{cGDO_|;qSX2E0T@QEXgBN*A*%Ea{mP-S7eZ<6ptdPlg1)qrD9T$-`@2s`7U7 z?Om(>5tJGcK!&jc*O{1tcm)Euq{-G$h6X52Xv@=}jWU-Ds)K?Bxp(53Q9C5znNeL4 z1j`xm;_hGohL3eB$zTD|qj6?vSGBnz;*9+aB%O-j{uE$F?=LT){QXBrt3tTU{f<7S zgNWRkezRj(b9;_;kTs4B5yA)<(gPlA)bD28Z*C)#3Gffq0cw2OCf@jgu?U4yv1FpOS*vN!T>Xej`s{RBom`N|uq5H+qDm^4Uw=#~(8I|1Vo56C@ z_{jd`mOg0HP|Ox^+i(&C7sb^9)7- z`Yb3k=p;zKw*saYWkf+K1~f}iKs6~ciqI)wRZtF*7y3T%SP^@1DB2(O9O1S467YK& ztkzm>?N2$A@66jOoUc0$4Nn9tL;+kLxbmqlLCJRRHghq_=@I8~_+k47*Xqk`fnzj&3Y^nN_!TJ4v=qG3iTaR{$mL zWd2;iLc!{OWNnoqljd6t6W)n0Och_01>#TKUN&Ha*sQf4~Nbn3KNVhSe+0x6_iZ3bTx)GAfv~%8ukDJWuv)>l;Jz*8{$@V$3{~hi+k(AncyMheu&~ z)0?z1(-m!!S1cR&EDz@hihWcUmE07~%3{@0N*I+l3w?!Olb>*LqD@6$^9!awSwxz% z*Q?Y&*CXOM!<)g!Kq215P>B(9B@-Bt4EoHFv{A`l=fBP1cD;3-KcNaXEu58W9C8E*86_LYXZaF+1Y zWJY9KW)X0n@JMi_aDL|O!n0-PwRxD?HVgH0y0RZ}THbLq3z`TELnbFp7jJiUvEOg` z(yHh5vn9B7+}X<6YybQ=gT4NR?`+^X^DpCUiMke~ z8Rk8dyF~btedN>s{4=&lA#ajDr^`Fb+sVi6fgjg`N14fSbXdX{!&j$E&Nu4~?%nx4 z`Yj9O0A$Vo*grHNIpC$cB0xd&t{iVceqod-g-B|uC->f(@Xq-z+<=agu9>cxUX+rI z`m4?Gt77C-`P9nP22*Txi6Lb#%y#^?Odk&?1s*|B$Sm$QIy2u7a%DaiVn>_3KB`bE zDb-mjXVw1V{h#OJ+7gjJaE%1vC`~=aIy6?7!fQfz!yIZHk{lFvB>EL11u_H`Qsb&g zw{?vacXs-Dq+F$H^AEm`@70X3k6fsHQ%Npxjv?UpeEJ<(B3{xvH^`=}<+!p?-^omD z{3YfH=Y+Y@`83+0ahD>NM_|L;d*k6JDjTIe<*5Q?eo_8~<(TI zJM;?(fw-=3oAj5cbf~=JH={bqk94DqRvus6Tm@alTw~+tb!felP_yfZ4}EN9&l}g? z+7sK?ANO8vUPMUnl`4x1y6X)bh1!;sbZE{qf3rI5_v=aJe=S3ck4fObIPN8U{F0V+ zin$y}^8Vx355$D;lxI2Re41=6E)ji+5GvUip&0KR=sCYKR(Qv~S57Su3+oE)(gVhZ zQ>S@n9S_#hbnALeZF1zYvUr~FQBuB}klP*Rk6EN!vA8X)NpuChDr=W2(NLeI9`e}r zd3$%-`>ujEL0DmU)9$9|Qn^uAQN7p3HC^^rk^1VsTD|`m6SIlr!>rO5)*;byG`8rT zIUB7{L1*-;_n!OngdT-%Lc39!S^dX5*lQp}ZBDILEvxM3m(P_oT6JH?QyP}7s?;IX z&MR3ewG?lC@%`45ZUatEI8v>tHZLuH+V76b8D<=p*_6_9pnk@*@vSBI7+u~>?V7h{ zU(xEkJ>i&coH)B$)wXQ6v~uno}u~=1t zd7jn>tlOnEu;!V`XPIR2`A%G{ z#b5IuBI+9GnK%FTsOg;ldAoH%&_!r{ZC%#2=TdT2d%s_J7k@TX39iHC!TxZZW|e7$ zStX%w*A?%)YdW+PGm-IKt_?A)?4s!796jpJxVt#pm%c^-!0wpOODQ@vY?5Y+j}< z1?uh7OId@Pv)v-?NZH#W>?T&{^4-F#jg!9h=BwM|1%DUgv%!Pmv&6wrU5+w8-z)DC z?9u3P)t!DurEp^;19MjWKu{CB*@y<5S%4h{r-_D?shk{u2KX8p01|2e00+JT z0si3t{{R5cT+n~N1A&?g_V3pqTOS{iCLOB)0D=H1Q6W`#kkc&KcDebLcdyttM){Ug z<+HFgNB9j{9jo9FI)bRvH#8%Ng+_Lw+BWD+(C-}msEl* z(3xpb!kQExkns@Koyp|{35S)7UaC?wG0NCgo`O@XR+b!soXTuG*!BK;kzv=VN#b?$ z`>LtybzJuH<$AK~dV&YVxaqOV@73ybsm2krFam?V)MH#bEGWL#Wb{}!)W3i@Iq@*t z%Qh4NU#r2=B-unN!87H--(TG1>({->9D!S{A&mc+L>2|`nSc=){|3a7Z$bu-fA+iH z-`WZ)H+}B2$RSh6Ws>KHJ+2QdG(dBF?$QRAY|@YDm0geLO45-7AYd_4Q?{sXCgD7lVzW?j`^fCw}~b~*leQzHVA zT7Q!FT9z|`oIJmwq$zJXS5=Pm`R;61CSR4>cbt9EoEKEz@3n!Pbg$B4Sr$(hOKj8Q zFl%Eas*>Vr;`XR0XRr-=h4SABC5Z||MT?nfYoLn-!NCtEvaFK46Z5JFTn@x6`WmTO|rDc`h!G}r4y-#a~ z_#gLB!-Il8kIta)rfZxFg9%)AgB3bRzE(1E`*zCkStKUFBCCUa)C_=ifFxtXxRu) z?VFEvnrV8lHbH#h?*Uz+ZyX^694aBOKb0h{Yy4$Wea;#dd-sia{}Ui?)LcjT!ws`l zDSz;CaIHpbYNdl-)2tgS&s6fXmN`w`a{Gu<8Wlz=(cDsfxe{%s$cB*wN*TbLf_pe) z$BlAJe^=R;iW&PkkcUcky95e3GC1s#7~4cxh4PjRw=UmTX9`x^v}2s4&^~PO`WnkM z6TvQ`Txj=pc1#-oT4xB(m%Y;B9>w+{$*7rlxOYQir;$i)77yGF>*#*^RAIO`5iApU zTn^b8(rAsi5F!+lG(uQ1?0542=j#cn-G8wYE^t*`;|nV;lTXTWUvX7Uj~e`OWHlIO=Vj-QZ5!MwAJbJh#E*IY=#|+RpJ|ft3fH$w%XAFTWz#q zoz0bw8!ugAhSe%EEQ=D|n^h2qej?q?mrBUDjr(;OBr}alA*+~CTDeU%=xbbR%`PMg zGS>M;j^B2Tvjmq0j8xgI`P)7Uy2@xOIYUEIcJHx+?2@?5_TUf{tfYBXx~=@4xKn+j zr#L1>5GIW(v@}7&VJrb(sTkA$Vp_uf{AToK9;xNAq8*UzVs{gb1N&o(QU5$fu z_w|0WYFSO($fg=vY3-Z2ddkJ=avgGDV_7o<3Hk<+sitaq@;X!^R&6S6tIH?rC3o@5IbPg?|dK&&gF~4Dh(~bT^Dv26krVcY#UPl*z zC@U%}=rgSEbBjKgIFZ}=@@I4#qcMXOlg4hX8M#%4k~k9Z1sl!>+AxI82%KL}tu$Ghuo-`@S`o>%8iS@0tbapt zKq94Jj?*tUW?V^7G9+w}F_2_9d(e{zlV%Myo0ahrFAS%42lZRC8lwSw>UYa_TY|!8 zrCp3djIs+%r^Y;jeE}!#CN51J&CK^eG|`GZ0cbI~sI+-g5m~-+3p$W2k;*HKl5%+$ zIV20g)$=%|(oZH<1XbimAb7Ny#Mw3TX@L+O?51N_w`nqDD`96n1|V%&N3FJU|C`lM z9sUnvmnQ`n!e~O!L$MbW(E=gutzNW9U(g|3OPpaj@6)}ZC|>G=Ivk1en6#}5`eZu$ ziE{#RVv9~O2A*#mR70ZRR282NS6QS<|vFWt&0E;V~`UUfM!&{d&*E2`4AaH;G zQ&-qx*1|BS-o`8erAXy8s^KR_{1m4h1Z> zxP^C ztZp=^e?^@=FN^Xo?!)Uz0e$+^>!$b_l6)YZBByxH#V@Tv%fY7)8C`62XzvO^x^zoG zl`z$k`bWs6v4~j)C;G=W{95^Ry=L8D%sn#jEj0=JH?+6(lmZGB0)&O$|7v&uA3YP+ z2Ltm@0*EHkFt43VgflA#4-gzb!H>6YQ}3|vz()#98u<>W5F8+Sb5P%7TM)UJoAkga9R-hEdB2~Q&2)dRO2}~$`%avD( zqgz$iO;_lW>6$mJ|L$I5G5Zpw}hGifCV>rW39#*>h8C=rbZaTkgp_cddh!lKG0;r z{mhAu$;O0_%?|~hnN$Y_F$Ml_Rs(<`%K#`8NV_8DyFCIquYR5D>-5hqkiKnFiBA|* ziu*GWn5KGh8>&m5e+xl?GjX>Aldfm_85wZ@@q^GDZrvL)X;-_f#$ea+r*YeabUYn? z{d&{V<~2(JKQWdtBdW)6mJb|F&X_1eU)2bSXx+G7uj2Zb7w3Gw|S z&O_jDJ2iaiq#(Ef#rl{AFgUoL!t^8hOxIh8%|@Eum#__YRxq%T(VsiIDeJO82mfv zLv(j=8s-N9Dy`IqbC7|f$3SDE9?BC{R>3NJ=MG$7P%1F!*F2S>mmM@|I&tl`IlN`TH0`@j-GP~di<1UMdJ#wRX9^c?$L z@fzI#bJ;YvQGs{m{`esJ>Nu^z;kq3{hK%Kw5TP*+T#|?!+MvW~^1MkzI71(-NItup zzCYiK3!$*zctZRmU;Zs&9)<{_=ws%4`eIC?QphI7!N%o;?cdCsS?O% z;LMk`28^-&jT#y#6e7WlfM&5^=$*~^#jMBxtLc<17EeT(JSPIh{`S|rzyD?c?D%0I zvn>b6UtNU!pHz57C_me|fLU^S|IYPvv7)7gKDe`~vQh2ImR*%^F#C2!41yxb1==vpoAdVATN%!N2-8dr^M4n{MyFW0R^F1H!(gJt<+vmt(v?CoXufpO!C(`#`Y z_hymQA1NjUAOzV*#F78B)M^98un$W#QG9RWv#UoP6X+l@1`7Na$!-O3PVH5N4&=<% zhcg$Ukw>%Em0Cvi8y{yRJzB~b+`99O^0tekNU4?C4WB?*(1tp!4-6v#S-wTu1d-r> zug`3v+$L%yPv!=?C79s)JK&2q9zsb1W_sdHL;j2U|Hs0+i-Ue1o ze;r5?aufnn?GiVD$F>by0?c5Qsr_N0QVoAz1gp+^=J*14NT`c1xja?@+Q24#PSoTQ z(d+JX?KV)fQm+o7%FMU;c+MhzzIScrb_WGhSGB%)PrYVb1(E~|XK7A#BH*l*Ol@tl z3iew+<<8?ZW6DqdKTT?l^A;X1b!09$k$)TRN$9U=3_;6msTCcjXP&Z4gRta!!G3QZ zeoyV3j(9hxhClKEC`dLh*KhiF=j(0{!|}Jc@>~(d@%`k|=xx7utchuOBhV+u=sbNn zIsEu|-o?ln;M^7o-uo&E-+gXU`SgtL&X=2>pF0lL30=>Z8+@9u1DW1_jkINwZ#b5K zwn^_tK)|96kMX{@lN^%BJ-v9;1Ee}+Xb$I2W;q*&->YC}+;hUrR3ZJ?8U&cmB>MhR zGeLWpdW(5b+(4fOBlbkrn1w!A@)sG4KbFG|OwE(@Rs_f(X>uJtLkweisAm4SM`0wN zOwh`8=)D-6$4Bd8H|QLDxDGS5HE7}(^*6o+!-}e=X`tlfXp$wLh9VMh`Mv~cJlv)P zKKnhX^_F#NQ3k+UeJ13#=j7?h0p%epJCMUAUQTj3$@IC?ERas#vbGPvDEBML44l^= zDYsG7!wgcI8DQ<~`a$uq0Bx&GUYRU)usd1{tQ75}is#>1m?$Cg{|B{-mRvhjzZhqW{(o-CwM92JuJ?Nz%22z0Cct z3Px+DUg|r`oqT+@+D6Dc6q|*Va@&;ANXYNpe`7G3BRxtedy1;mY)?FF;h4E@5|X;1U|)7z}~)asT(<} zbr4!sq_vgdh@;iIH$Yw;ijAR^8ca+bq}buLeg74DmzUm8zEL@gc8Xw+?npQ7ura8MU$Owf}g4=FrRu!7tuquL~Hmvo9z`>f_{a-N-<>5?QS@3)NA;Gf=CfTLBVg z9#to#dI(a51K(PSa;dYyuDfe~d`!8;3Y^ksfw)}$9Q<->Lc7|1fHL) zuh#K&wtQ0Vczm(_Ovx1xT3U8JOmOZ2r)~oNFDZYCiXf1vAaVz&*~NH0olS-3mzmTvhWb@kEFlPxsZ-Ge!|1!C|p%fTRhOdiqn^3BEo(zJvy8}uJZOq^7@oc5YATD zyp4IzC-4^$@*QJnH*#?lI)GEaL>%Vw$CrQpJ4G^ZivB;Q7D_1HfloU!#x7>2pAM+F zAn9@W!{&-rAOuu|M@4NjaeOv6z3e}^WwzchI@HS4^^nJv0=EGvNjB>zQ-cc06x)vJ zrL8+OEBU`<6$ud-pIxCO4nE4uqqX3QstdB(7QNk-pPTNU-PO-|g}=@2@EYvkBZ9=c ze%@cf1pG#~ASv_hdf4~1+d zos;vnS%_upef&AE1kFI(T8+nVA6_tbluKSB;%sRvaK7m{ydT=RLY?CGwHD z$}F8^S_KLlf}k}({*MLAcOZua687V&sw;Ts>E;B|kCff1clf!Th!^I=AfQ>oRM~tN zj5KDG@sRTdPwfnnh)TRrw^Ak@AMBNgA865FC~bwO?ACn(r5eWxS2sb5kdqLFf06J2 zYj!fx=K(jDBF8)36tV*0qq|n3IO^<#Kh-sge@P@UumMB>%tiv=vmi#T0Dm32dKQ$$HSG4;LYXg~IgP4Vk|gm;&?)gMy5waF|3{N#?E)I}^uaE0OL% zN=P?vZ8_D)?s1m}ro!v!V~ z@Jq5S52R%}1xe6Pa%CjNnZTCP31@UM7!SiQ--uc2-k& z$RkJIMot#3kDZ>4Npr9zUQbsiqy-12p4}lo1{8E~=eP_a%UyG({q~D%-Sb7&>#-H7 za`ABnDF$x$Z)lS~8oR{|JgC0-D?{Epg}24T!xQ$(=vI2|X3$cYx54?*MZ&`aL;ll> z2UQrKXab3U?PMfgM0c9z3r}zN90$|P)0uKRm`j(Q7OTC2yrHd)E&@(}Qf`VE!UUz} zI46vae~I^hTfvE}0f(uy*s603I-RWziPlX=vo=k_u?$up{+H+-$8zP4-iF1`RjLg- zo!%ZkYKC71Q9+T$G*246fmwfKTeJMbX73+6it*%<%7#-^R{>`>OmiP)TQJ`vh%bypUKf%&X)~Vk^Lg`1u6v(HMdzNoea$v zD4lH0W&?>INsELAM1u8uv>~DUa_{dby)aJBS3KUA!?j|x>b_U(H>_j}R*YSw>_Mch z3+O3HblUA-({@xf`H$ESExViAjZf}*I-m7CXJzR+G^>>8$9R}_v14d~lQr+$#yfN} zh^}CJTZAQI&Hj}?a)8o7RJ|J~g1Gb;g#R=v%)oZpp<7!^-OSHDD{5Xwk#Zs`)0 zsvR5turpR^?R|5Kz-aS79;NgNts?MpeXoBn(pUFsu_=&kqK&MAoz6Ry5piBijr^z#FXH~Y2F`rF4ouv({38%Ujvvt9r(-Kwd(n8h z)M#?3E~DOu3@;S|U+`6dx`f_4bwC=eBVU+$>}@ateRbcBRv6 zk-%xXHaX_Jx!pHmy((rjQZ69V^YQ~@Rs zi-acyJcwH@=SsfmhfY+~7~Q4hAX<;Cn1158lAKJ-Rx!Df!KBSj)4cI+E}Z0QVv4d) zcS9grB<6g-+TU5!N9av!cbn{7-@IsePMA;i?WIyLxoJ5xv3F_xekB4V0Eit(L)@1x z#IDW2>9dIGKYBXB+K=h8c!zqHu%CW`_G9V%dN^7Zv-gdt>?7MI4UGxAR(YAZOYejA zoE{K^#h?K|v7bl8;smg{sf>E8?1(_ZdnW-qv|&V^`_wj%WkX>~Oe$5u!tvcz(R9}O z>nm-zq`FPd{?R93BGIOMTUt+jlC)2FLV ziC7`|pDWxAxXOxixktGRuHi*m48+jnL=09ieOCCnnnRc9V8L@b{0x#BsYIt+e94JV zRhjMZ6#v7<^TG5-z^Q;zK<-3Dd8xNU5MF7d|SBW6D!{QkD_16q(=G+80E zc`!PrcSh%k110fuVn-7l-s5K2E~BoIWJWc%ut;m5QN(y4(1s%JaoaQs8)MmGkTX>5 zB~*sgsc5{d#WQ@oKe^hMol3uCY#Cx=<^tk|Oj{7*{`)VkW}ibF_%OeViZU zL*9RDq50neo4ISiTMRi`7}l-w{)J!7;dIMpLEo9(s5(q*N6y0Xll6E3fpzqip&&W) zvFRr-IGYTk+B*4pNhbYHU<54Rv#E*%G*9_ld&B%WZOCg(_Mx?p_Rbzo*j2*^x`V z`-`9$KogDg4k;Liggz&x4yO5!L4KYMLR)P;Uy&VQf+36S*8i@?G#U*B0yHA9biJ^h z?JL^Y!)ecV8f~o{@dwJ&8=S* z8~F7g#@?})9Rgw|3Sev`89^`nIGxU4#7v}9tY=HbfQ*v$6Q;D_vx*(w%RyT?cBJX2 zR*q;beXYjtM!fEOQ==C#Nu$3CXd7)Dvo@qU0Z*%MCN`&rN}2}bk^3( z6WJCosCTXHo$?W%$6-w_4JcX8(G8zdz|NRsfa_o+mhJm=oohE6#lIkN%n}MX=-AmT zHBPrDRWl9s1m&3u0R!$!B$!i2=t;c59pt4OHqUTq~5 zdQVqc+TrddK93t$lL{HzOlCs-bmv(^^zmXK&kC1}s2l!ZPgXjrlBF_h)W71=6;{=|DJcfi zXS)?>DJXc4b096t`jhE9Neh#8!l_ZC>Oqgqf~8V&%kmYV(!dP6(%#^?i*wEL)x9Qf zq{_cxAUB@H!ZWE(-_QMbVQ z9TqNlm|-XSHeD6f*QLw79^|*}YU?+9b}1}j`56P|%oJ}dfl|Sx?<_NE_4}5tS635T zkF$`r?NPzeI7i1?I#F)G?WT>rrc+EHgAg+|5KrxaON45;SZikhZ?NepZ`v;)PK`FG z_(-a5^ngM+9D@NC(TgG{%-(n2>%bAmy!78Eg>y!F_?&umsj``)#u ztni_5-J_P_P0hLVZVusTW0$5@?MM3y!~T#9)=w)w`@4Ri2)uQxD_RBHW2M(^MaKxw2YDaz4q}%Kg@8AfBjDFk zTkwAI{>HdPZ+!dVmMu0={1ttm%7Sct?{d%Qb8#_Sr<-AwgLi5F4t1dkJo6}|-}HT6 zZyb>6;-nYZ!z9U53Nn#6mmCs$F=0?E&o9*<8yYkjKJNA^A4y-d32FJYe!H%9Qt0}ZAtVza_P1mi@*q0};*cw8Ajnfn}bprhR- z_EQTUWJnoAwCEcbV!hkk^-J)Z0uV;3S4g_JdGXQeFN>%EVC{XUg z!89KBSgnlqm&fu7<0?nX1esQF^0_n%IGjG@@kirK9q^=K?GG) zU2U%W__2kns>N8M`W5bsmtq!813Li-7xhY%kcK5~yzc6T?_dti%6C}?7rcCTgptsA zV7`KocQ3C99drIV$h}E!XWObkV1yUR#z#)Htjh*|OooIEGFBdt*WBFo;lYcmyvcO+ySo*HWcYCN2Jv3Pgp4!>`Vc>yOKH6G(v_{;uM$n zsWE$=6Wz=uZ;NGV&k_*#YWAqdNgVO5`myl@gO7mVwbkYCRk=5z4-<*WL_L zb`YIeMgi_$W$ZKNM03rWX&d-n$D#hOP=44ilX+S<{b_diZ)cJ~1Sfm-z!sxGNb_fU znLoA+v1;uPCT9z<;L_0;67tOMBmTJJBv=hSb%+=B_QM)MTp&wN=5+^QIg?SJ-+AZg z-s5jWOf3wUI4n*1(%PjEE0!~!%I(oh`!ghkUWF#PQHaT{ZFeDjchLLhEx%Hi+wl2( zX{g6lZpA!_D3HAzWR^>(#91sH^N_VyKXlmy2eoBhc-~Qs4ereH$;-?6cg*9))W(k` zWHET2R90Y4S*Hoy zR$Smjd5mN20GgGK=3gV;gV?S;bZ&guP*!TCc_#FF4WfNGiWkgtbgDo4Ph#{5>EAR| zECH4(9kpR>e5)t$`fP9VY3)_KOtC6)2`8rs(RP}d%{armqh3qx5V?cQ+rM@Q@HYOs6*>_Pz?CE0fuA_?EX5> z`K$CWz1N4?it+SuW^xmjMgMN(VuQhQ>r4mqKc^&!Lqf6%gF=eRGHq}i9Ycj~IDXwO z!VN9gjZ4f$_Mdsz%c6F+nnM_Dw2EpKt>4UOh)6qMBc=}>?R8ek& z%YNzsR16L!Ev5v(lPB~yuF?tqh%5=98_TtmcXu>&>>s7EPwHdxmSMw-$q4GF9#Kr#e_FXmmwabqIDsjoVxEs19a@MLT>dPl8

RDtUEmF>&9C= z8tbxO*J6^QI_OMsvHqT5OJFL$!O^j7nj*qTdct26EFyEn0-wY+%); zC1ZqlciCn5dGrKby-pT~0bZ(F`Ei=oQnx|)_%h3Rxm=K=Io0oi#}-_Z4GO_mzH?VA zuqbRL(34gUo7BaUj8rwIRbhcMZ&?hyc+}I$YeIK`U1B2VPYejv9Z^WpJ6Xp9t0`CG z|Fas8CF7Qm^`T1y$Xu_CsywZ|X>cxETTIjj)5f_U=cgsR>wWj11m37StwM-6Ir-L= zo<7*Hjd{;@?#+<}J`)UZeTk?nxgvlfj=Jrqz!M?=zNE7WyuH}~17E67Eg^3BZi9C~ zcEu8mYxoRqC)&y96+zKW{^{6B<|3ltS6Pa7lwkb_^X<)ku_EY&3tU=W?O48^IEPTD_ctq~S z=WbfSf|Tq&{J?`bp(|hAOC^=rP8x3u$!7_F0FYD)BT061LZ6jFl))|R=H}XoFsOId zdh$)LY81ekvCJ`|Rjn|;E}>X(DYeL)XxmvVPya|`Z)*aPd9_k4k3k+Lepwm4Fqf;&e#{f<7)u(1n56P=OU=;jvcMnvO~C)#WPTE95ev#a6uy|HoM*84)-ND}r$6F}TIBfuv4iWn|{IHo`JKIZLF~ z%b|8i60Gl&1Z4mAyKj?Y1#<8_ur*=fgQ3H%oS&i-xN<`OFGI z3v|qo)!($}`?b@ogr*^YI?&L#dW)-Hm->m*+3k3r57`Wxr2V2#(-HXs@2InFGz_MH zmzFQXY-s&YaRkCBQ#FCY%&_3!wV~xGq+p`J8!QQ%n&H6Szo6kjxLiR$3q6whKn?WI zO!v=HH0Y0C&sazj_3Y+)=&%NzbipTCEq1TqFY#$3Bx=Yr0S3YkdGzT{}7_=nWTT%P8;yLEOWFk=bfD-hmD_&Hc;0WfZ!(HKJ|x#SEW($wQoqgP1rg zqynh03PwI}oyGFS$0L=!dI{JpS82;k1L{vUx%tuL?|)E;JjF7(G0sMr0>rc34IXA5 zN5>`MMSlE*&Y#8tua?*D!W4`X;U(nzUMJWmJ7OR4af2$~#k^Jz6J>d8Sw^4+?X&Up zn9S&xTG2l!;ClrDvyG2Tz@*AY3?PdMY*ptlKk*n<8&z`%i49-g!C0WX)ZMLW;#&mL zNnmHL`5q5e{qB0*I&S|J?aLSA2+qCMHT=g*SW=4UnU+n|!mjB;r4y3iN%r8@b>B*6 zd!E=VZuyArE)8I-XRN@+R3}OCM;f2$OoyD$(#x!EmEM##snUF~TSRX=HfzMeU5z)y zF(#1W3=mAKMqktr+#3|tUgUSLb0}by92c;tmuB>oX4ihkZQbK@?t#rh3r^#u!3uDE^FP|ZyDc$0J7fFcP_o#%7Zb>>y$Wt+n`<*@wZzZ-yz)eUQ;FRj zc?)D}=5Y12-i>ipshF?b*FMM?a?i1$AtJ=;)Y_UVMv0=JPR7U`=t= z#cnWt^H{@d+&JeocCW?_&8J@33#BT=lEvmmj8=Esi5MT0@R|K~^Oi)pE;&-9k*aof zxI_7}_!7TN#)=f^T#7<6-?#apX^jd<&}Py6Wp7rVz7h%lUtBOg>1&I@LF^B(#+yob zCMEm6tw0rl)quD=1rioXZ!p}ySnl=yzPoZ->(65fKDW!j#X!p7U^x2tV4y$Y;741K zDmUu#laZ`ZVa(NDMI`h7Vr7v9TU$ny_iLTIZOvvXwFA3c6bKxZ$TKg5dR-kp8ho@3 z@<=*F65JxV+^!9f;5G508jn{JZ1;9_obmb5G3fy=Y%9y+$34>zRl}`0E~K$=1J|D= z-OwkIMlkaDoh&C-3x`eB+AZOk-R_RVNXQ^bu$470NDMyx;qh9>n~9GZ&PH@*c4E%# z1ri*d>8z8`#o+#Wk6)A*QbJhHJWc2Mth3d;qPOu3ru>qGu4p@W2Y13mCqC> z=Uz_{<3O4O{Fzk_1`P=G%H8Mnx03)c$0O_U@1OU`rSaaMJ~T3$E-!g;@tn4{&&{=! z9XNOWQN#fn&<|hn+B-FQ(!A(wYQ|&{{y(nXIw-5|{T`-6x5%S5y1Th)xasch?&f!RKJWLRcgAsG9M9R;j%#1B);bG<&)z}!9JYbi+LpfxoC$Rm z%NW}6Z0GA03)~F_OI6yx&Y~G|)V24MLxhAG<}inO&dCdqv@kGeKuiY2Sl>6>}(9|HA3NuAmTjizE0%iU7T zy1cvX*sUyTd@OUALyS#=C9{KVQ<_h#%h%O|`6sB4pV+AZwSs{yryQ(bFZdTo;i z2tM@!C&_;X%P9?dAps6LH5{g<6A`KP(A*7+FM zA4rB!W>kauCjW$%Bq)@V(6!u^&-KeJjLgXTYPC_}sPVsz6XUy*a%j>^^i?E51Ce8E zjp1Y!gduDI1owx5q&(V1_~4kOs&N~ieoE8#g;M;Vw<#Q2*z`p`paobjx`vZ=(+^Y0VL!Q3*@Xf>g(u-o(?Qcn0teiF5~%xcEpnrtWzgD!2adEA|FyYi>sLJNmq z8Ar1IPI$at1~#vS;0BwUsF4O0*^s`~>#Wqo=Y6j>J@X{g$Ms;3C&h zA%g}QMervCHM`rzX&bwNsvJfK)*5yA03LWbrNeIU#oObzOn^jOP6K~(OQ#SwC^GN2 zG1vf(jy;*-h?^zJX0)oj;pym!Qz zu?tvRD>N<7T8>NgW4r&oagIX{iKB;sfpLDtg|8@*#03fT>Zu^A8${P-OU2T^-s_KE zC6rnN{WcqJ2=%Z4OTww9s$o3d8C=mv0E5M@9w^N?VORbRV6r4sx;_lIX|qJral8e4X zvhX;u3^Ll!ojS2RAn(m}^YmBiseh-9RjI^Tt@#hg%U zn&6X~&h}Bji{=<6gb~Pie!25;;D4#cAk1mvO=4?AQ^*T5sCFnbs9MF3Ut*Y`u-g|O zHFXHb)doJ_BVUl1zYzTS1F@ZxhsKSy#CG!B&j5q-qY^=OeJ3?s`g&sCk-T3QP_f${WAj!;})54)3@ z)xCdoc)dRG(kbaY{H>u|8u>|K5gsk@Ud~@~RH+aw_-m{~%g6bu?>5D&&Yq;Kl~|fg-PE$>zJ^&o+U{JDuP?c=~T%+XxOg zZA>)=foWIVGpJC*?OOf|D?F|*5DMcP?8T~W)i$5#I9$$u=h9!{35J+9;E8>}`#N1J z86Z6rhpI~8)U$Nv?f0YBjLtA<>}@kFOp~g_aE$d~-XDh4G>IHCt84m@{pcL_Ls$+Y zgZ+f4Xc&j}WT(Lg7K+nm{DH=Cd&G9$E`R?{@D}=+WwB`o+34b2#g+!zjk8h%(U3R{)W}5FyLp2a-UFiP z?^AkTM3D>BN7hvenkoK^@3s6Z%9PU^0r< zXL(puo}8(57u;Y+Y`~#!=1^54PaIXA5+WWe&om1?uU}vVVIroQ1+u}c5k6iY+Dzo+ zB#U>%vZ`v8A&PnFR(koNgRNZ4Xc>m#wmk1nr!*6WK+~0YVh7qZ8jRF!SE~bi!5VCp z8lXKtDV*WdkE!G>wV!Z*)rF+6$g6a|ZRu4HRBGJ;N2A#k=T98{B1bZ(XUI3_sxKN; z5}IG5u5t)DDl9|7uQ3@;TTWZR-sO&C!x>sZ)}ZCR#TL`drydysEvU4zPiyS~g`zt3 zUi;Q!!&MlG-_l48Ko1&K&Iz6}^Doj^4d!t*H67pUOMN3&)7!4GPR=O2j`y>kLQqxgd`pP zB)BarMYtZr=0}98czK^*Q6<9kJ0Lla>+giI0&0pF)VYy`%s{oyr@Ll~{e>4)#M;*8 zWSC0%q?N4Y4|f&Kb=eZRsaH{2oX)2VzF>t9trd2wn}w%NUpC;M_)x1XW+91bDs>x} z{5`}yB#fWKDk(tPf~+-yN5xh__FjygF2=hf?#Jk-1rPrazm|R@!0o}7O57ax22gcI zXWyHU)_76^Ql#tH6PLY@L$YlS_3CNULHxw^&QH(ozEQ8QOp^e}hkl=)5}Y*pFAzLd z8;m|g(xJ20Kp|yU-oWv|&ZI|`=(L=`cH!fQPbb`U(_`|6mkVyQh5BgSq%^BvbJyQe zX5CVh7A~y8>x%4xIY0HDJxgICsXUs$IGxCjBl+S7^KCTCM?TeuxPKfrP~&Gt6_jxO zsc+zwA;XMt*NQmMYr>R!4`NN)o*wWP-hwTDzD@gFCcYKToU}xm>!{Tv!ZH_WmoCNX zf2tn%Il$c_R3V5MqQ93Nl`uqo((=_>mR9J*nOQv)T zenLYZh@Z&|d$=WCFm|VutoarG49wB}c_c@~2`6Bs4JXeMs(HPDOp3-rJbFK$UF?AL zx2>+*4g>TnN%-Jgqr{2$=CVULrTcAooT&C^afB9^o#vcBs9Np=LUMYH6tLa1RN^7u zumCv47=QQY>GRLFi|5+kXK2;y8w)radO7frZATZ2&B;C_ooWyi6 z1j<>eZ5Ff0@17~m`@!y9kSy3e@1y~B%U8<=H*6mxCR)*KF_ZGGVJ2l&Snsy*Y8&&{ zlosOh!|w5$d=VMDBlqu(S(UCR0V!|dPfjZ+>C`f5;l^z?TH^|rU(HCVz^wUi_n+-Q zXpcPH^h5Yq?Mo6q+O6RI+K;W{)`m1 zzXQOY$%>Oq*rJ9VIG~;+vIqzy^nvz?{jruk*WLd3O4kfQ|E6}*YLjVU!Pg)6S}04< zqpp;E^;l14_K}SHp-jJKmsyd;X_tnp`{M?9^F_K}T=ipQ+Um@oOn`zIB?Fy)2s7gf z+qg_-X{C&7|A?CDPIT$vL#x&wW<~yQcp9|G8P%7#h;+izQxaJ&Wrvu4`z+pbU$L_r zdm@m_C@;~Tq?5&>e`pU`$YNzt6+iww9A1G_HSwhop0EK*#NWMj+-_+chnTNfrMf1- zNmX9PQ!01-*Z3-sW@T)-Fh2)1d-AB1449DE87}(e)5c*QWrYmU{fO~(j2j)DEiZD# zegDGDgNIiIzv$dRAn8w~5X^3lr;^S&gRmFL)K}flL|7|=s+e>@gyZgKJT9k`5^zKa z_r63<|9EX5{sI)Uw`-&X7~*ek-;Zh>7Bo=y2s^)-$||-0AV2|`WQYH92myoAv=^dF zOA;$M8~`Pnofh4VB6*kH1FU7ET@-0j^u^A9TQP8SIZ~4a~jP*Oa;({pnc}Uo)rEbvkM>i zhlPryO3ro9Xky=NZS`R~$6Mli7?V=Q$&${I6+w>lr>HfXm4XoV(ns6|qpq5KE?Gov z6iW*ssPhR)2v<=zODG9@<&G;{x>+|)NC{c9X&~3{G`EsRqltVNoJS@Sh;L7kxohM| zEpVkIVco#!qte7Q{MGGY(xM&)t7YMN(a)M-IByhehwrp6dvhDS{QZ-=^N^pqywxN_ zB8f>KD^aWN~&wyOV)aU4Mu(p&cR`N*cV2u zt>4uCdWsoJ1tvV7mm19DX(?w63%n*PiyEh^n(UQE;mu`fniqc%-FhY9r>9y|b(%{$ zn2&~WU~vU+tn?ypkL`_hYv{@t_8AyT*uS9rwW2@Reb;o!UTP=zbofvp3DD>C2Qm-G zP(C{DdJnjMD8d~1=2;(uJft2X#0w(kH@H?S9jf=*wX-2V&B=&`E0hnEF8%uH)!LJA znSF22^4Hfym1!pECQO87t|ZH_q)uue1bNIOSBRh`ocW2z%wRt|Jz(0$%@vw$o9S*f z?P=?iC;M-%(FK5U-eCGa${zizQJoBqE1g=+D09r)cw|r^Gle4y*Q;Pu!>u{jzB_wy zlBi&51v?gu%^AfPXSk3VmU+x< zENF1@I(GJUq(y=%HanFa{R2(kWi+s5p89yyr9qQu`r>|BkEZ)a)f@lSo4SFQr~37C zr9o$d^R0aEIzr%=U;Mu>r$xe@a@cJa(mQX54568l6(X$5ooVR0V}DQ*FgUMS(S^;J zgHnEQV6vX%8r(}Fqe|8@cJWn6+!lXG#w(l3lLeO5tYBK4B#Xq#7OtUYTNL6^_hXK7 zw}q+pIZ-)w{I~?f*1hzPDRkaB0y zzf@|Z*Vnt2_cESuDjq1SWKgTsIu9+Skr{JIq7An6uwjxY+NH-Ex_n<@P8 z>i2ZLpR+UMh0>)GjFD8=;khGbh5=-5u}kFV2QY7l)Mm`_8QNNrIoo)dJT+Zfg7p4m zi|vW8^O0Eou(p?C2$KEp8GyXwTjiFY@W%)Z`B>lAxfLis{R_eOhZ3i(FjG5<{g7W7 z_ZX}#P_CV9%sg4B%ju}fWYj~s-9YHS{$_D-ID9nMSnCn^BJdJQ-&KYAaYm3%p8V z{8Mhn<>x~$qQOw%iN$|AWH3emNtRrYLUZZRTI^UcBfuiX z1DU<^>ETNjo6+XBD|5?IEM2IymwMw)-%-^$e!J5Qs1lkxg7{t&RT)l(&L=3gf78Ot zAbN;|?{bKir66z#EwcWn)zWR+CnCBAw`2(@e|&f>#NWp5qQDKRO2JnZkX|d+#t(y4 zC_6`cohM-yOa?SO7xTs8TQ)^;-Liu_9`AudI<|YbuPkR>@9y8Xeaj0CX{g3@@s5k@ zb0J26G=lHnn9jNq%ky+Os>2DIv+jV`kMgdmGiM6S%x4)txhIs~u_{dTVE#FJaiWy@ zW?unRG1EZ5*6%~MOu0r)T}T24zwWN;q0)$~cJu4&(ClI2X<=6*d~deR;&$XotE1>+ zeA?PBs~O{`4RJBBTk>_-8L&Y-1z66F=apZ^pf0WK!2d861( zvBqyPmS{RCAQ1l>7`W3XEZ8Sh<+np!mGr?t$LeUm!2-m|qc{JOl=m_i9(b7T{Evdo zu_ochS;}P~t`SLy-=!xN6>bCF#@A6Ao(FyGy>S{pf?AEcuwjjA3e)`x^i=ID$8FEo zvX`pBZUZVzEWH2_x4%S0bl$7gNp6X1uD1kP{^T={{Ycm9Yd{CrvUYr(hWFfjvmwAQ zM-91AiRx-I7OgtQD%j(-ghJtJ39AXOsUtFB;o%&AD4(S`fb`0XR=>cZO{>SIKEOdz zfF;?^(je_zzdClX>?aqG4Eb2{ev>ZKexdckI5u`GdC=vM2#f( zwuj5w&x7}P{Yt@(8Cz3uK4#<`_)65RPH7^JAMmPATTE<^Z$B-h8XgT4l#d-}a!7+oM0RuUAMJe7I4j;nG~qoSG&jxj3?avwrefM(d}eV%2QazjTQ~8tw8r zi1sm&Hqa7|UVDQXWs|)_hBz zqWE3}6B`lj;9*OR#_Xv&fHLqKRC^tF00(C1Z#GL_kD%|`SWCFJvDncfJ9|v{&wYsL zBxWPtoxTmaUzNGdbOx>fNaNx$_mzYj)J5lu;xrKSaZtbSeY2R0rE73dIyBe0`~2@? zQM!5RQq_JXM|F|c2IGEQq7D2@T(VDcIV?3^?Iy`0&=X#=$aI9antBt&pZw|50e(anXf9{&G{KqH=sUuCc%S*2{?7!W$ox9%-;J#x zsN6UHj@21R_`Qt$oH<*5X!}*C3c;f%pYHONP#3p5X1R7pb5M<|nA7&qn#aXchNMr( zvyNcP5kQ1{uL>!|p091VdfvN_mM_yG}o3Y-cPK@={ z#fw9h3(KxY_$y&zT*8W9ss&n3!hB#^g@qlEWa7iggMi zTlkOf9OQ5j&irshHATuPWNkjMegWFZ-3v%G<$Yj?VgS^LCsT=Z_dDdT2yUpnfCqm< znn$2fr5{~FdRrZ~m+QE7r|;8U2m9)0g)xDoRAm;WLw@U9#9NbUyN&&87cR>pLgGL6 zOv)RD7A*kt&0?%tn*tr-ztn`vUS9}{K{vClMwbXFY{;qi$4-@Gl2I?wx^4UL2lL&N z1tsA=>Fs64y@M41d+W>%4X<9gz#i#6+d5Az14{4{Sy3=e{}P$rM#M2LR&DnQa;M^B z0Q8>#%f%m6FuLt;3-D&J-&L_KWwErB-|(oZrMsZ%!lIXIhq!AdjCa_FuNX}j%}q^g8-MG?IDl7x`5wbIXUw z!yU@i?JukFDPqtHtw~Z5x>qWkf%N7o$o6iZZy*2Cph+e zI6K~W8+?`L90NjIG{Gy&7ZDXT*=%juRJKY@y>Qj{4A5id$D$-Mz-|iJ82Ryzt^vw( zs3E%cJJ%q2Du}l3HfO8z0C4Io@?){_?N@Smvqw349*cQZ$N^8G zGT{N$86+)Zx;>5t3wz}}S%v-zuCy4w<;|bWP%y7~P(Xpdkp5Bs0J zk*~?U1P$!W)DMcjSKCB6PDzTd;gd7iU&HH%wOlDWJsg5>CS7X6L*o42`nx1Fd#)uZ zfCf~^x%xW`6~Qju(Tf;bhOuW;thH?QX*pn?b9iIE@P3xO^=8*OHF@%`pgn!v-df>B z%Bt=LkacPP;307)6rJwEI{#6IJcjv)?Mc2d?ZKcLKE_3jW;AQgI#!T`Sffr|Gal~X zvpvx1t56YVKJ?QZ#p>Na6PNClh_8+Zg1f;9!;WO@-xxo44?Qqwzfhug_zihVglVb% z?yDdyG9s8nI=d$G0D4MRVYSW!sECD^V?v|Zbkb7k2=sx6RcaCi;(|PL@cEW6=|DZ4 zrH5ewFR@XAZ`F+5$HSZk!NmSY9PFQ_5q#hAZ%&66gMrd!K8_qP)g)q4UBwRSYBpoA zRb_0aPo8K*y>x4MBko%!3?$uz=B!jk_+{g=PTld5d>i|AsFGt#5^qrfRke73gTYE4 z?b!lA(}illB_A2^pCoUr+<*1_uF5Xxi{fC(`gy6v(}@VrizXbum0^+t=TVN>VNUJi zmHcXg6s(U<$>!6=O`P~H&y#L9(-rdIp7i288V@k!D?@$54&ut zy-%#xsmtmq_T9iI;wA4(kDew=Qk|&~@}~V9_R^QTK+{ZN*WYxo>gPKM)!arm$D(?+ zOu-*=&C)7QGU`|O4VSUbAGr40NpIsSyzj*lA+>LUn|f~9!b8%5e$qU_Bou&9K8ATc z5_o%%KUOj8N`@#P+#LCFU+_KkVf?DpFt&|UA@$|d4L!$c|9*z>c5w}J5Xur2qku{zL!aMj_fN)N;7EXm>+d4nDvSD;I=VMMGna1 z>em{NMS1pWM@fv-jn+9mqi9t*pkUh0H#Xgi0z|kkep-hXD#IoaQ#F7hA+>s?Dn~t5 z|FxSZ3XWB~*F!grMNPNa0r&Wh{_WK0ObUzZmr*NN(Emz{Gv1vnK9YS4h9qLx#Q zrH~-KMK`#<7*&eFX$r4@F-)5`((jeVRDc5Lufe&M%r9S;39wRPA@2Y*;z~^@jWLN1 zArsfj`P6CIn(a!ZHMyM0S34C*E9Ei+eAlSArkA?38spI)!-<@_)74X2Fg`kB818!t z6SPC>`?;tkoaNk?OBmCYHu2x$rBt2xFQg+#TK;*%C|DnhQHhPq9Or41)ies|I^8#; zPrV%_CI2Te0>z9f24e+ukUi>iOnNq{f(^0R37{E(5=d=8jzc}Get0^d_mnPq%&fIX z#3Q1LyS~{;A#%YBl0O<_AB%3k*FUib0^0>mXDR$G(`R_avgXA}TnMegQ9TK%*z(47%0740>6z0Le%h6!|d$|;6eNQQ6I%c4#%+FB_eJ6<#`*BEA zTL2wQkpVHsO&!_P%BGi*Wjn`99SJKv6afJsnE%JJ>LQPp+9Hv&=!~U2fKEI1LlL3~ zmvAIgKL0kz$qrzjQ_J;4fm^2De`Uqb@Wb;0OPiD zxzYP7m33_rXbretKi@<5DLv?mx1v0sSTL^9)Ph<<~k{An;WWdRl9mp}kEFF%7{`C7<-Y`5BLzDkWth7O9~- z#8fu9&Uy=Af5?JM8z|8K@TqX2YIsB>0{G2#0C}BOdx~%^Ei(Bp-=+(A@JhS!@O$`x zg|N@{=U`kRdomBeH0={$A*|zzhD4cVR$}}W8(g-vVhs{0Tm8gOAOJH>XeG=GWCgqT z;&dF3LAC@!+rq0MB2+TO53d5B2_p$D7=E^_g+(3auK^+O|5+9Y>~&ZPVMgf3$!GCVG8NrBCMj}~>dfCLF$o;bsHuYHulC6`IHYX9e#mfM*g3`q}gydU6bVFXPu0F#;D z+m;x5r3(Q7KE^V2W#{dBM*Yz-k^KADIzPcbORs-`QzP)`U-wS=LiN{+2)GZUf7S2o zPSCqgo~}ev73)F{@rUQy-ZWxu3nI12b^vtu#wX`Dr5WdJ)hqNpAx@3+8u~fQrsl7C zTN1BblB<#k~Dgvw}0}K=!>Zn%S z)u-8IOqu2nJbJo7pmtKPb%!_=$Np7iu9DIZl*8WV{!rTsS>A-sWb0jfZ+N9L2?AuR zR$)uu@xszTe9R!!%c<_CL%4}V1}?pFCIx>>0|QJw1ZfiVEfTMRf@8J@n5m#Tn`tS~ zsIcna!6p?CbHw#Zvk3cn7OZxgFla4DQU$<$=@``2fJ$v;6O#UeJ($n{vzPI%jRuno zF9vlnxh}+Gz9|porG5Vw=IuX`5IKzVH?BWd2_mN`Cbz^YG$p^BoBrJc=s+2ygvzu@ ztB_h<68$@(%@jbx9DY{K3Y!Je|OtktfJqBz6+JH?pldQ!%9Z&J? z@h!hgyb?+Wr=0<&6SB0aV4AC1{Vj9tHiHhjNNa8 ze2&du0tn;KSK=%w0epIu)2qYF2Fo1XEld(P8yB6+>-^6<#TIak|I%K@k+_}DAEZU( zL(~6oBj#;+Fu?@81EYMzhn=3jmJ;n0}vm?81kWH8A44dj`n>TCq`#X%jFQA=33R z`|;(E$WxUSr^z)L3y^dsu7;7W1Ku_4a}RrI6&pP;3cm0=#An|CK040u{e~2+LpQxvqZq#|?_KPxL#28TSgatQzMUb@xS{4<{tg4{e2MFis!z^fZpf@s=5AJ#glMfKV$WegU}1s zQ!9X>#F|O6TBcl)qT)GsyAo3l-v@JcW64nz2#7OGRgOV-JbURabmQDxk1ifw6<2%c z0N|n9(7b+^=iAA3`>V-GBESt)FD(EXNo1I^J&G?wCD(u~nxf zDpDBr$u4mQhuor!n`7_ph zY#mr2USAed1i-apfw)tC{09ln)!*yj_0m4q&8!pyjP#*i&$J)s)h+g&Yq#;LCmwk^ z2f?z!Fq?R|+N=y>EzeW((^ZXs@2FbYHo9O|ySF7!f_OH77U~ zs#En`J*Fgdm!7*Ny!C|`u6fawoE_H$t`g7TDMQQl$IVmwrOCl}TZ82@sI|a(K zQD66m_~B;rV?h3gouI%=^K}fiD71kv$Ffrv41W50wf~F4}VV)3757nW-x*0cA{s6`dNgfU8 zVO4XgT{UA^={jj1Y2wsg@$0|0;Mtr`FJUe6^ucgICC7yErROsZbHV+Y8#rPkwwFBP zT|?k;yx@Q#DAviblJ#|r-SpARdU&SnqdC4m(1f~edZ~)8CUD&-^4av?@b={2A;zm! zcMnpE2dZ049vlDxy9;7_SmV;UD{-Y;>YZp6D_GsvC0gQutKY5pae{Xbpx4k^?s<0y z$>P6)UDZ?6pr`P>@@h(%6_?rmnX~yB&-^u!o0UfCtZrhy$C)`iEOUbg)GSgfeUCxf zR;O4dr%yQLv6iYkyjRM_s$bL^{R)0t)mxf0I>yLMkwCogTFi5Ov15(Z$NWPKbj|@G zl6i+}9YS6kvBjjVBjH6S_K)we0tu8P0eqp?Wd=uuW(yD7F%<;;M6VrBDz$Lx0&~&F zL^R$tyl(`o<;;#B1qQUWB9kwR3y!C&bB!5FR)ry&3EzZCI{*UHr2+D_Pdu!3F-AMv|S4sctcm4eSIa4t>1eSw!9Gpb4gNs zK-m}J`e06$b@O}4m)yaEZP#5otjPDtjjcPLnpHXKun=1UvUHcC)9yaQst@Fcbma^_0Zn#Usk z4c4)a=TU%$2tdPZ2N1t;uwyl+2iB5sA**MR@boeE&D-x+vkwQ5Xs%-=fReL)E~czO z$1{C&mVA9p3SwQ-U*jg(h6QwU_0{$#a|3o#6<-RRUp-uWv{g{zS97efz}@d;5#ZYq z7lt44h9!aHl$elLJ~6M0=d=7YHEmApe6ae_e|o1U0oWSRf^0CD+^8wE!BsqoJ2xw` zC(JFHuG_KR-NL(si+eN|yuqzV3&*<(w0ZXP!pOIZQrNNx>Xdr}^Zi1mPpwCqPz6Wx z*qyf@w$8ssw7w=xajdf1>VK?^Mx#$q7O@S3m2i@qS4l$di3bK`-}@173-J{g5Cj4oUzFQ@i;jy%fj}dwd{ut+#cj4eV$?kw zC8O=ke55*~%8Y6>@mHUi$4F_pWPN5TO8Riq6K)wCkS^=Ke4hd|iC-T^?X(OGt)Gzu>fNP5_Z>1j7CX*S zN4h@z=nVNL`hb=KHPEL87%^%R^U@-ii@4ilH_q8I2eFL2Pt(cnmyPBqur(j0WPqSa z5Q@sbCpZg&f&qEu=*OduMm?s_M0`%u`eu+1u({xKKEh*ZN#f!OK%v*plWD5b z7@}1YIv3A}Edi7n#ks~n(Qe>tA9aPH3EGm_$ALn*iZ6K*s}A7;l`u zZ)L509eZ>W?c~)OT818}ee2Cp_HdU??IUzSSWlvL%LcE-h=>zSUg0A41ibH}tQ3;e zkcZ8TtUj1ld_WC%^PYJ>B^u?I-sobZJ0L9wAP^Cf{oc9dz@JkfcXS&2x=N!R8E{b! zcS4;Gfton!-CYTf?*hddl`4D#$SdHRT0y8}J1p2a>$G{j0AN*DfDvYD94vhx`3&$f z`Km<7|3h8sxiWM#K4rf-cx%5?J^=koUgqs4G1a=Jr;PSEu+KjGtUtivWk-N{(M*Y zVuFzaO-oo{Yp(?6A0X9mq`Q8OO=LtFJPe7hK!Qj8tWo6vF6GYbWk_ndX{`WoC;wCC z!)<`A^q06LEdaumk>ym;YXU;px3`6o_<=WZbF!PIs(#4aDf2OCbk}-r9a5_kAB1 zm!HOS0gN4+!F9>!ArrRkotQBc;&1yQ;!nb?TbN&)MGDI^N41lZII7>3VlWYi{D0u# z?Qta~=}g(9cYdnxlaVo23tdC#Os2akQDi=br-HiK9{lSO&ia$t=hnivipx~nsU`u1 z$mj}CKANu)Ck%j{i80ZC{7=&RN1OabTGA<-E?!^j{ju)VUX)UkUB+~Y<`ba23kX}B z14X*;0s`Dl_dtJP>n9obeZ2$)5!cV5rl038wiN8|3lPVywB>KQGcA2N_ChV3sZoqyFF!?`PU^OzxL8E?fF_>(gT1a@y?-gl(jVW>CvzbN^I; z@P^IQJ6L*)%I8M)XobR%5EC6pvWjjg6nOhv#OW)LC>a@6vtOLC`XiDby0!tyTso!Z zykMOD*()8QdoKVMwqeSD)=D-$wsP8tb8G)v(=cj{b|jP5_uqckyk6=+PpAPKq z*N9dvv`DHe}$Tk?BRRJmo&Sq$BN!0&!M~#Bb#0(UV@+nf;XnZUnh!?(Yxlq-$NvqwU&d}Ta@4Fn? zWJ>3XZ%cq>Mw>U3R%E~`u9RgLB`~(yOL<-C#)|S zB90*(9HDc8&ktAn@zqjh>iI_Q!P-H-WY4@*xgE=xlF=5E6 zi&{^>qZnj8$IXW1Fh^ET8V?{4KM3P_It_1I4D6z&Yl4m}Lv>`5`-H}iRBV54tvotS zGC^@sBE64Q=!klp_J{myRgl+huFPrfW|NckOqpcZI_*QONVD z{KgQ&sSd-?aVzZUmoRI)_wD0|iWDL$U$tYQ$Zh7+BM(lAfNov2a8wGL_4L0Bo6tDe zREPdY=QFSUMP=HrwP~6$Eo{q0?!yJQMNpVyTU3z==@B2>wJ%>;dXLpA%JW(*_re|J zA5I9U8=I`=y|)+UttKTvmLfNamhH2Fnro$NcI#ayEBa8D!GabDS!qgBaSva+#YOnP zHD!ukHDxUrz7&eol*EmDvfEOb3h2CsysqZj?_dySmg}T1d1nCTp!)%pn|=6zwht56 z0bO3?sy`4U)=nE`SA^T*g%feab8ER{2jfyJQUlhntU`P~f zU=&7oW;d74C!#wBAG8L>f(z08Ab?u4pAVBS(j@R$kP?DmmK9=F+HlpugW15$p%=q8 znFuco34}izMi%kNOv-gY(*XMWP$71-3eF&iQIb){_Jpah3`hWj^5P+0^h|~)(M(uK z;ee6(QTjR5e;H!v6c1S1_F`ycLl6O``3pSMb$|`+6k7BDejlLla~L)HA;#>*mejZF zm>sU;QTzdTLno=AfSKMn+BZg6p?7l@fN|A0(qyXf6W(im$!thZ5r%-Gp*ml2O|?2{ zU#RQ~DnTAcn?zH2h>d<-7cv9dO64 z1AJDa)$+}!%{U*fR(Yfic5p^}BK+J%MwT!+*J6**hPa!o@e1~K9ws{Xgp)#Ef zNQQV>hrSEP`%TfH-*n$@n>t&3_*l2rdOMlsP{eve7wxx-(ndODw3-0r_?MR&GR{$s zZRa*?nV&EkymK3*r2B5r|CSOom4yCJJ-1K25G|UuLE9B=lbb36la}*ZWq&1F+C%=j zzGtW*k0-!E&I2jv!1n?8dTlDm%eE30m?9E-V8c#L#?n;uCqqAJUhsO{gELjVk}p3f z@%h4BQ|)_KR;!@mN<-`%C;m*%qYaLNGjdFB9r+1}o%#WzxDJ5KilOv?!W31F z#4m8b1HY|5PcgLIRA7ydO*9C5N&P%J`ZS)Jbtj~Aup-ETm+P!B+MAVPm3r`MCdfhk zbkgJbZcHz=pn#fJUmQA=zYbr^+ttM&Ulgxay-wyu!q30EANRoOweD9PB9)OL6zB?? zCA~AJ7T#^p4(ewCs~_4vO45|Wx zMqNEEG_tX(DB|uNp3nXD7GugT$IaPN;a$;-+>RXsQ`;M;j-M;U?>t8c;BoJ_L6H%A-VV2(^>CO2(vDbIou=u_TKr+rqMU5d0@5+W!RD1Z}A<}Vwtz~&7s06fe)CcA2p}ymK=;k-=5JiqUC7`6(B*MJ}oC3s442gbm z5ep;77Y~8gt$~@_2OX^pAqc1)@%1Ty@pu3PXG_JFU;Bcf;rHR8bIA>h5ccy=miFcz zZKn#r(uv+|FFVPM;H07#R~Isk$QjGZEuW_b{`2+Qceic-H;8ApLi4LF%^c-9xEZBH zwd}zGJZIa{bl&Egki?U0{VSc2jjP(T;j_4tjC{{AO_JwJnNjF9O8zk zbr^{qj|z$#G--3k)ACBj)s^HvYd&HKCZ5q`{t$d)t`=#_;T!}3^Fv55eflj;&E4^U zmQm(uGmJ5exl%$PDU>r%>L=#?SOWcH$6$^tUPpu5;HA9EehV2b9xLNYo_VfVgzMDw zwH(jf+%(&8YfbeKagpd;ip=X{iF1lfx!TJ^aXe1))!^)}1bxo6ngt(gjRwmlF=9{l zg>;*V9+FOr6MQydOf88`hq`NEm^3#XYhae62yTtQ{4jVOuY>p&s^tnt_P^(t8|3f4 z&;TRpUT;TE&fETwR_RUnt5x|SVioUW7rMSdaV^ZXFIGg;k$|V`gei_?`~77}Xy}+J zIHSg9#UnSr?&rLg)AohR-$~Eo1-E_=XLg}7ag#_5f0oPwjyv;IZlj5DwoA?>AOip?D zz{g@vVssjOUiybst(Nc0wrkU-_1cJ4!7_D{Mc&g?dIhDjYBjTniz;PJ24MV;W8l0h zL`&jo#O-804#eX%6E1nOCb{%aw-&21!E=?W3*C}Z5Ibpc387(`A@^nyC04W2SvV@* za{RJ;URvA+NkO4!-g=jOSj0b`0q2d+wT_)|-LvyXeGdtV@Lg1phl4U+Ipvjr*HO=x zwe?@7M!j~PFXjP!u7&1M2stEJZkV1`=mi{v61J*Ynt8R$IGrfTZ`6ahNT_j*`Q@H&n*B^t)#KcQzh|H2&3djnG#X8&D`L&YJgO z%hTaFVz15&iXGzAajm0LJ|+swI3&K~@8BMNP0c8tiyV6Q?)Mpf$p_WC8@I%uRNIy( zz^Px4W!=_bn|CkNL%d{(0&WWuA!%maMvS(8MIv3lKy=GS<_4reRd&_Da-v3H^|>nL z8x{uh7eKAi59WHpuB(^4R)&FJRXIMuilfZ9WB$60%VL`{#a%^2#`BQdn+48Ft{QkM zx&#Si$oAFPC#P+UmF*{MR~e=^IDRI5FSZifeR!<

>w`gR>5VQC$l$N`Es&7~6#5 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 MM_DH8_M>P23!1Ag$^ZZW literal 59744 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 From 800cd66f8d3335002b740d863391e9fd79967709 Mon Sep 17 00:00:00 2001 From: Denis Riabtsev <68712122+1KitCat1@users.noreply.github.com> Date: Thu, 10 Oct 2024 06:11:04 +0300 Subject: [PATCH 47/54] Update intro-to-anchor.md (#553) declare_id!("your-private-key"); It is not private key --- content/courses/onchain-development/intro-to-anchor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/courses/onchain-development/intro-to-anchor.md b/content/courses/onchain-development/intro-to-anchor.md index 8b30b7ab6..6648bf1ea 100644 --- a/content/courses/onchain-development/intro-to-anchor.md +++ b/content/courses/onchain-development/intro-to-anchor.md @@ -548,7 +548,7 @@ following: ```rust use anchor_lang::prelude::*; -declare_id!("your-private-key"); +declare_id!("onchain-program-address"); #[program] pub mod anchor_counter { From 91ec3d190c3014ee1446fdfd9c2cff48da799458 Mon Sep 17 00:00:00 2001 From: Nick Frostbutter <75431177+nickfrosty@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:29:35 -0400 Subject: [PATCH 48/54] Docs missing redirects (#556) * fix: missing redirects * chore: prettier --- content/courses/mobile/intro-to-solana-mobile.md | 1 - docs/programs/anchor/index.md | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/content/courses/mobile/intro-to-solana-mobile.md b/content/courses/mobile/intro-to-solana-mobile.md index 03971de57..37dcad0c5 100644 --- a/content/courses/mobile/intro-to-solana-mobile.md +++ b/content/courses/mobile/intro-to-solana-mobile.md @@ -372,7 +372,6 @@ environment if you didn't already. This Remember that this step is not required if you are using a [Framework](https://reactnative.dev/architecture/glossary#react-native-framework). - Ensure you have [Node.js](https://nodejs.org/en/download) installed on your system. These will manage your JavaScript packages. Install Android Studio: diff --git a/docs/programs/anchor/index.md b/docs/programs/anchor/index.md index 975568575..f48fa0fad 100644 --- a/docs/programs/anchor/index.md +++ b/docs/programs/anchor/index.md @@ -3,6 +3,13 @@ title: Getting Started with Anchor description: Getting Started with Anchor sidebarLabel: Anchor Framework sidebarSortOrder: 1 +altRoutes: + - /docs/programs/debugging + - /docs/programs/faq + - /docs/programs/lang-c + - /docs/programs/lang-rust + - /docs/programs/limitations + - /docs/programs/overview --- The Anchor framework is a tool that simplifies the process of building Solana From 17cc7d364fba8313e7fa65769d9579a57c1d8256 Mon Sep 17 00:00:00 2001 From: Ayush Date: Fri, 11 Oct 2024 19:59:22 +0530 Subject: [PATCH 49/54] Code Import (#256) * code import refactor, sync and watch mode * husky precommit lint hook * mdx support * debug support * ignore node modules * optional quotes and line chaining * husky deprecation changes * use fs/promises * ignore contributing.md * package merge resolve * ignore pnpm lock * refactor: scripts --------- Co-authored-by: nickfrosty <75431177+nickfrosty@users.noreply.github.com> --- .gitignore | 9 +- .husky/.gitignore | 1 + .husky/pre-commit | 1 + CONTRIBUTING.md | 51 ++++- code/cookbook/wallets/check-public-key.ts | 19 ++ code/package.json | 15 ++ coder.ts | 222 ++++++++++++++++++++ content/cookbook/wallets/check-publickey.md | 2 +- package.json | 30 ++- src/utils/code-import.ts | 169 +++++++++++++++ tsconfig.json | 2 +- 11 files changed, 512 insertions(+), 9 deletions(-) create mode 100644 .husky/.gitignore create mode 100755 .husky/pre-commit create mode 100644 code/cookbook/wallets/check-public-key.ts create mode 100644 code/package.json create mode 100644 coder.ts create mode 100644 src/utils/code-import.ts diff --git a/.gitignore b/.gitignore index d8fdba97c..ac91c0ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -106,5 +106,12 @@ package-lock.json # translations are stored in the `i18n` via crowdin i18n + +# code-import +code/node_modules +code/package-lock.json +code/yarn.lock +code/pnpm-lock.yaml + # vscode configuration -.vscode \ No newline at end of file +.vscode diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 000000000..31354ec13 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..2312dc587 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edcee7aac..2616dd370 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,9 @@ transparent as possible, whether it's: - publicly displayed via the UI of [solana.com](https://solana.com) (located in a different repo) - content translations are supported via Crowdin +- code blocks must use code-import for file snippets (via filesystem) +- code file should be [tests](https://nodejs.org/api/test.html) and should add + code ranges instead of whole test file ## Style guidelines @@ -273,6 +276,52 @@ For images, you can use the path starting with `/public` like this: > links will be automatically adjusted to function on the website. Including > making the images viewable and removing `.md` file extensions. +### Code Blocks + +In addition to standard markdown "fenced" code blocks (i.e. using triple +backticks), the developer content repo requires the use of code-import for file +snippets. This ensures that code examples are always up-to-date with the actual +source files. + +#### Using code-import + +To use code-import, follow these steps: + +Ensure your code file is a test file located in the appropriate directory within +the repo. Use the following syntax to import code snippets: + +```javascript file="/path/to/your/file.ts#L1-L10,#L15-L20" + +``` + +This will import lines 1-10 and 15-20 from the specified file. + +Always use code ranges instead of importing whole files. This helps keep +examples concise and focused. + +#### Code-import Rules + +- The file path must start with a forward slash (/). +- You can specify multiple line ranges, separated by commas. +- Line ranges should be in ascending order and not overlap. +- Invalid ranges (e.g., #L4-L3) are not allowed. +- Line numbers start at 1, so #L0 is invalid. +- Trailing commas in the range specification are not allowed. + +Example of a valid code-import: + +```javascript file="/code/cookbook/wallets/check-public-key.ts#L1-L2,#L3-L18" + +``` + +Example of an invalid code-import: + +```javascript file=/code/cookbook/wallets/check-public-key.ts#L1-L2,#L3-L19,#L1-L3 + +``` + +This is invalid because the ranges are not in ascending order and overlap. + ### Table of contents When a content page is rendered on solana.com, a table of contents will be @@ -519,7 +568,7 @@ a list of available components content - [images](#images) - details about how to include images in a piece of content - [code blocks](#code-blocks) - additional functionality on top of standard - markdown code blocks + markdown code blocks, these support code file import from filesystem - [blockquote](#blockquote) - additional functionality on top of the standard HTML `blockquote` element - [Callout](#callout) - custom component used to render message to the reader in diff --git a/code/cookbook/wallets/check-public-key.ts b/code/cookbook/wallets/check-public-key.ts new file mode 100644 index 000000000..f5b4c8392 --- /dev/null +++ b/code/cookbook/wallets/check-public-key.ts @@ -0,0 +1,19 @@ +import { PublicKey } from "@solana/web3.js"; + +// Note that Keypair.generate() will always give a public key that is valid for users + +// Valid public key +const key = new PublicKey("5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY"); +// Lies on the ed25519 curve and is suitable for users +console.log(PublicKey.isOnCurve(key.toBytes())); + +// Valid public key +const offCurveAddress = new PublicKey( + "4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e", +); + +// Not on the ed25519 curve, therefore not suitable for users +console.log(PublicKey.isOnCurve(offCurveAddress.toBytes())); + +// Not a valid public key +const errorPubkey = new PublicKey("testPubkey"); diff --git a/code/package.json b/code/package.json new file mode 100644 index 000000000..4429cdec8 --- /dev/null +++ b/code/package.json @@ -0,0 +1,15 @@ +{ + "name": "code", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@solana/web3.js": "^1.95.2" + } +} diff --git a/coder.ts b/coder.ts new file mode 100644 index 000000000..ae53d4693 --- /dev/null +++ b/coder.ts @@ -0,0 +1,222 @@ +import { promises as fs } from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkStringify from "remark-stringify"; +import remarkFrontmatter from "remark-frontmatter"; +import { visit } from "unist-util-visit"; +import ignore, { type Ignore } from "ignore"; +import importCode from "./src/utils/code-import"; +import chokidar from "chokidar"; + +let debugMode = false; + +const debug = (...args: string[]) => { + if (debugMode) { + console.log("[DEBUG]", ...args); + } +}; + +const hasCodeComponentWithFileMeta = async ( + filePath: string, +): Promise => { + const content = await fs.readFile(filePath, "utf8"); + let hasMatch = false; + + const tree = unified().use(remarkParse).use(remarkFrontmatter).parse(content); + + visit(tree, "code", node => { + if (node.meta?.includes("file=")) { + hasMatch = true; + return false; // Stop visiting + } + }); + + return hasMatch; +}; + +const getIgnore = async (directory: string): Promise => { + const ig = ignore(); + + try { + const gitignoreContent = await fs.readFile( + path.join(directory, ".gitignore"), + "utf8", + ); + ig.add(gitignoreContent); + // ignore all dotfiles + ig.add([".*"]); + // ignore CONTRIBUTING.md because it mentions the code component example + ig.add("CONTRIBUTING.md"); + } catch (error) { + // If .gitignore doesn't exist, just continue without it + if ((error as NodeJS.ErrnoException).code !== "ENOENT") { + throw error; + } + } + + return ig; +}; + +const getMarkdownAndMDXFiles = async (directory: string): Promise => { + const ig = await getIgnore(directory); + + const walkDir = async (dir: string): Promise => { + const entries = await fs.readdir(dir, { withFileTypes: true }); + const files = await Promise.all( + entries.map(async entry => { + const res = path.resolve(dir, entry.name); + const relativePath = path.relative(directory, res); + + if (ig.ignores(relativePath) || entry.name === ".gitignore") { + debug(`Ignoring file: ${relativePath}`); + return []; + } + + if (entry.isDirectory()) { + return walkDir(res); + } + + if ( + entry.isFile() && + (entry.name.endsWith(".md") || entry.name.endsWith(".mdx")) + ) { + if (await hasCodeComponentWithFileMeta(res)) { + debug(`Found file with code component: ${relativePath}`); + return res; + } + debug( + `Skipping file (no code component with file meta): ${relativePath}`, + ); + } + + return []; + }), + ); + return files.flat(); + }; + + return walkDir(directory); +}; + +const processContent = async ( + content: string, + filePath: string, +): Promise => { + try { + const file = await unified() + .use(remarkParse) + .use(remarkFrontmatter) + .use(importCode, { + preserveTrailingNewline: false, + removeRedundantIndentations: true, + rootDir: process.cwd(), + }) + .use(remarkStringify, { + bullet: "-", + emphasis: "*", + fences: true, + listItemIndent: "one", + rule: "-", + ruleSpaces: false, + strong: "*", + tightDefinitions: true, + }) + .process(content); + return String(file); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + throw new Error( + `File not found: ${(error as NodeJS.ErrnoException).path}`, + ); + } + throw error; + } +}; + +const processFile = async (filePath: string): Promise => { + try { + if (!(await hasCodeComponentWithFileMeta(filePath))) { + debug(`Skipping ${filePath}: No code component with file meta found.`); + return; + } + + const originalContent = await fs.readFile(filePath, "utf8"); + const processedContent = await processContent(originalContent, filePath); + if (originalContent !== processedContent) { + await fs.writeFile(filePath, processedContent); + console.log(`Updated: ${filePath}`); + } else { + debug(`No changes needed for: ${filePath}`); + } + } catch (error) { + console.error(`Error processing ${filePath}: ${(error as Error).message}`); + } +}; + +const processInChunks = async ( + items: T[], + processItem: (item: T) => Promise, + chunkSize: number, +): Promise => { + for (let i = 0; i < items.length; i += chunkSize) { + const chunk = items.slice(i, i + chunkSize); + await Promise.all(chunk.map(processItem)); + } +}; + +const watchFiles = async (directory: string): Promise => { + const watcher = chokidar.watch(["**/*.md", "**/*.mdx"], { + ignored: [ + "**.**", + /(^|[\/\\])\../, + "**/node_modules/**", + "**/.git/**", + ".gitignore", + ], // ignore dotfiles, node_modules, .git, and .gitignore + persistent: true, + cwd: directory, + }); + + console.log("Watch mode started. Waiting for file changes..."); + + watcher + .on("add", filePath => processFile(path.join(directory, filePath))) + .on("change", filePath => processFile(path.join(directory, filePath))) + .on("unlink", filePath => console.log(`File ${filePath} has been removed`)); +}; + +const main = async (): Promise => { + const filePath = process.argv[2]; + const watchMode = + process.argv.includes("--watch") || process.argv.includes("-w"); + debugMode = process.argv.includes("--debug") || process.argv.includes("-d"); + + if (debugMode) { + console.log("Debug mode enabled"); + } + + if (filePath && !watchMode && !debugMode) { + // Process single file + const absolutePath = path.resolve(process.cwd(), filePath); + console.log(`Processing single file: ${absolutePath}`); + await processFile(absolutePath); + } else if (watchMode) { + // Watch mode + await watchFiles(process.cwd()); + } else { + // Process all files + const files = await getMarkdownAndMDXFiles(process.cwd()); + const chunkSize = Math.max(1, Math.ceil(files.length / os.cpus().length)); + + console.log(`Processing ${files.length} files...`); + await processInChunks(files, processFile, chunkSize); + } + + if (!watchMode) { + console.log("Sync process completed."); + } +}; + +main().catch(console.error); diff --git a/content/cookbook/wallets/check-publickey.md b/content/cookbook/wallets/check-publickey.md index a16eb4ac5..287f64c2e 100644 --- a/content/cookbook/wallets/check-publickey.md +++ b/content/cookbook/wallets/check-publickey.md @@ -11,7 +11,7 @@ have a private key associated with them. You can check this by looking to see if the public key lies on the ed25519 curve. Only public keys that lie on the curve can be controlled by users with wallets. -```javascript file="check-public-key.ts" +```javascript file=/code/cookbook/wallets/check-public-key.ts#L1-L2,#L3-L19 import { PublicKey } from "@solana/web3.js"; // Note that Keypair.generate() will always give a public key that is valid for users diff --git a/package.json b/package.json index dfc6948f1..91ef33165 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,11 @@ "scripts": { "runner": "npx ts-node -r tsconfig-paths/register", "contentlayer:build": "npx contentlayer2 build --clearCache", - "dev": "yarn contentlayer:build && next dev -p 3001", - "build": "yarn prettier:i18n && yarn contentlayer:build && next build", + "code-import": "npx esrun coder.ts", + "dev": "yarn code-import && yarn contentlayer:build && concurrently -p \"[{name}]\" -n \"code import,next dev\" -c \"bgBlue.bold,bgGreen.bold\" \"yarn code-import --watch\" \"next dev -p 3001\"", + "build": "yarn code-import && yarn prettier:i18n && yarn contentlayer:build && next build", "start": "next start -p 3001", - "test": "yarn contentlayer:build", + "test": "yarn code-import && yarn prettier && && yarn contentlayer:build", "lint": "next lint", "prettier:i18n": "prettier -cw \"i18n/**/*.{js,jsx,ts,tsx,md,css,md,mdx}\" --ignore-path \"[]\"", "prettier": "prettier -c \"./**/*.{js,jsx,ts,tsx,md,css,md,mdx}\"", @@ -21,6 +22,7 @@ "crowdin:download": "crowdin download && npm run prettier:i18n", "crowdin:upload": "crowdin upload sources", "browser-sync": "browser-sync start --proxy \"localhost:3000\" --files \"**/*.md\"", + "prepare": "node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky install", "dev:sync": "yarn dev & (sleep 5 && yarn browser-sync)" }, "dependencies": { @@ -40,7 +42,25 @@ "typescript": "5.3.3" }, "devDependencies": { + "@types/mdast": "^4.0.4", + "chokidar": "^3.6.0", + "concurrently": "^8.2.2", "contentlayer2": "^0.4.6", - "prettier": "^3.2.4" + "husky": "^9.1.4", + "ignore": "^5.3.1", + "lint-staged": "^15.2.7", + "mdast": "^3.0.0", + "prettier": "^3.2.4", + "remark": "^15.0.1", + "remark-frontmatter": "^5.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "strip-indent": "^4.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "lint-staged": { + "*.{js,jsx,json,ts,tsx,md,css,md,mdx,yml,yaml}": "yarn prettier:fix" } -} \ No newline at end of file +} diff --git a/src/utils/code-import.ts b/src/utils/code-import.ts new file mode 100644 index 000000000..b31e0277a --- /dev/null +++ b/src/utils/code-import.ts @@ -0,0 +1,169 @@ +// remark-code-import +// code-import.ts +// https://github.com/kevin940726/remark-code-import +import { readFile, stat } from "node:fs/promises"; +import path from "node:path"; +import { EOL } from "node:os"; +import { visit } from "unist-util-visit"; +import stripIndent from "strip-indent"; +import type { Root, Code, Parent } from "mdast"; +import type { VFile } from "vfile"; + +interface CodeImportOptions { + async?: boolean; + preserveTrailingNewline?: boolean; + removeRedundantIndentations?: boolean; + rootDir?: string; + allowImportingFromOutside?: boolean; +} + +interface LineRange { + from: number; + to: number; +} + +function parseLineRanges(rangeString: string): LineRange[] { + const rangeRegex = /#L(\d+)(?:-L?(\d+))?/g; + const ranges: LineRange[] = []; + let match; + + while ((match = rangeRegex.exec(rangeString)) !== null) { + const [, from, to] = match; + const fromLine = parseInt(from, 10); + const toLine = to ? parseInt(to, 10) : fromLine; + + if (fromLine === 0 || toLine === 0) { + throw new Error( + `Invalid line number: Line numbers must be positive integers`, + ); + } + + if (fromLine > toLine) { + throw new Error( + `Invalid range: L${fromLine}-L${toLine}. 'from' should be less than or equal to 'to'`, + ); + } + + ranges.push({ from: fromLine, to: toLine }); + } + + // Sort ranges and check for overlaps + ranges.sort((a, b) => a.from - b.from); + for (let i = 1; i < ranges.length; i++) { + if (ranges[i].from <= ranges[i - 1].to) { + throw new Error(`Overlapping or out-of-order ranges are not allowed`); + } + } + + return ranges; +} + +function extractLines( + content: string, + ranges: LineRange[], + preserveTrailingNewline = false, +): string { + const lines = content.split(EOL); + let result: string[] = []; + + for (const range of ranges) { + if (range.to > lines.length) { + throw new Error( + `Line range exceeds file length of ${lines.length} lines`, + ); + } + result = result.concat(lines.slice(range.from - 1, range.to)); + } + + let finalResult = result.join("\n"); + if ( + preserveTrailingNewline && + content.endsWith("\n") && + !finalResult.endsWith("\n") + ) { + finalResult += "\n"; + } + + return finalResult; +} + +function importCode(options: CodeImportOptions = {}) { + const rootDir = options.rootDir || process.cwd(); + + if (!path.isAbsolute(rootDir)) { + throw new Error(`"rootDir" has to be an absolute path`); + } + + return async function transform(tree: Root, file: VFile) { + const codes: [Code, number | null, Parent][] = []; + + visit(tree, "code", (node, index, parent) => { + codes.push([node as Code, index as null | number, parent as Parent]); + }); + + for (const [node] of codes) { + const fileMeta = (node.meta || "") + .split(/(? meta.startsWith("file=")); + + if (!fileMeta) { + continue; + } + + const res = /^file=(["'])?(\/.+?)\1?(#.+)?$/.exec(fileMeta); + + if (!res) { + throw new Error( + `Unable to parse file path ${fileMeta}. File path must start with a forward slash (/)`, + ); + } + + const [, , filePath, rangeString = ""] = res; + + // Resolve the path relative to rootDir + const normalizedFilePath = path.join(rootDir, filePath.slice(1)); + const fileAbsPath = path.resolve(normalizedFilePath); + + try { + // Check if the path is a directory + const stats = await stat(fileAbsPath); + if (stats.isDirectory()) { + throw new Error(`Path is a directory, not a file`); + } + + if (!options.allowImportingFromOutside) { + const relativePathFromRootDir = path.relative(rootDir, fileAbsPath); + if ( + relativePathFromRootDir.startsWith(`..${path.sep}`) || + path.isAbsolute(relativePathFromRootDir) + ) { + throw new Error( + `Attempted to import code from "${fileAbsPath}", which is outside from the rootDir "${rootDir}"`, + ); + } + } + + const ranges = rangeString + ? parseLineRanges(rangeString) + : [{ from: 1, to: Infinity }]; + + const fileContent = await readFile(fileAbsPath, "utf8"); + node.value = extractLines( + fileContent, + ranges, + options.preserveTrailingNewline, + ); + if (options.removeRedundantIndentations) { + node.value = stripIndent(node.value); + } + } catch (error) { + throw new Error( + `Error processing ${fileAbsPath}: ${(error as Error).message}`, + ); + } + } + }; +} + +export { importCode }; +export default importCode; diff --git a/tsconfig.json b/tsconfig.json index 26231d6e6..811b9a6c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], "skipLibCheck": true, "allowJs": true, From 4f897712875de3d1f5ecf237a50873685042075b Mon Sep 17 00:00:00 2001 From: Nick Frostbutter <75431177+nickfrosty@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:08:09 -0400 Subject: [PATCH 50/54] fix: exclude code dir (#557) --- tsconfig.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 811b9a6c6..d90767bb3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,20 +18,20 @@ "paths": { "contentlayer/generated": ["./.contentlayer/generated"], "@/*": ["./src/*"], - "@@/*": ["./*"] + "@@/*": ["./*"], }, "plugins": [ { - "name": "next" - } - ] + "name": "next", + }, + ], }, "include": [ ".contentlayer/generated", "next-env.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts" + ".next/types/**/*.ts", ], - "exclude": ["node_modules"] + "exclude": ["node_modules", "code"], } From 25931ca86ef7dca72ea7dd3f5122a626072e019a Mon Sep 17 00:00:00 2001 From: Shivaji Raut <108468912+shivaji43@users.noreply.github.com> Date: Sun, 13 Oct 2024 12:52:23 +0530 Subject: [PATCH 51/54] Added styles in interact-with-wallet.md for wallet ui (#559) Co-authored-by: shivaji <108468912+shivaji1134@users.noreply.github.com> --- content/courses/intro-to-solana/interact-with-wallets.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/courses/intro-to-solana/interact-with-wallets.md b/content/courses/intro-to-solana/interact-with-wallets.md index 960366e60..53cdae15f 100644 --- a/content/courses/intro-to-solana/interact-with-wallets.md +++ b/content/courses/intro-to-solana/interact-with-wallets.md @@ -119,6 +119,7 @@ import { WalletProvider, } from "@solana/wallet-adapter-react"; import { clusterApiUrl } from "@solana/web3.js"; +import "@solana/wallet-adapter-react-ui/styles.css"; export const Home: NextPage = props => { const endpoint = clusterApiUrl("devnet"); @@ -173,6 +174,7 @@ import { PublicKey, SystemProgram, } from "@solana/web3.js"; +import "@solana/wallet-adapter-react-ui/styles.css"; const Home: NextPage = props => { const endpoint = clusterApiUrl("devnet"); From f015ba805f19a61535a6f1e9c27063546452dbfa Mon Sep 17 00:00:00 2001 From: Sonkeng Date: Sun, 13 Oct 2024 08:22:47 +0100 Subject: [PATCH 52/54] Fix typo in program-derived-address.md (#558) --- docs/intro/quick-start/program-derived-address.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/quick-start/program-derived-address.md b/docs/intro/quick-start/program-derived-address.md index 4f826ad36..eba31e76e 100644 --- a/docs/intro/quick-start/program-derived-address.md +++ b/docs/intro/quick-start/program-derived-address.md @@ -525,7 +525,7 @@ The body of the function then: -Rebuld the program +Rebuild the program ```shell filename="Terminal" build From 3cc30737d29dfe3c489324305ec28ccdb3debac0 Mon Sep 17 00:00:00 2001 From: Mik Watkins <63586831+Mikerniker@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:26:04 +0800 Subject: [PATCH 53/54] Fix broken links in solana-mobile-dapps-with-expo.md (#555) * fix broken link * fix broken link 2 * Fix broken link 3 * change prerequisite to getting started * adjust workding * Fix broken link 4 * adjust wording * Fix prettier --- .../mobile/solana-mobile-dapps-with-expo.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/content/courses/mobile/solana-mobile-dapps-with-expo.md b/content/courses/mobile/solana-mobile-dapps-with-expo.md index 90d1bece8..27006dd07 100644 --- a/content/courses/mobile/solana-mobile-dapps-with-expo.md +++ b/content/courses/mobile/solana-mobile-dapps-with-expo.md @@ -71,8 +71,9 @@ speakers. The libraries are intuitive and the documentation is phenomenal. #### How to create an Expo app -To get started with Expo, you first need the prerequisite setup described in the -[Introduction to Solana Mobile lesson](/content/courses/mobile/intro-to-solana-mobile). +To begin using Expo, first follow the setup instructions described in the +Getting Started section of the +[Introduction to Solana Mobile lesson](/content/courses/mobile/intro-to-solana-mobile.md). After that, you'll want to sign up for an [Expo Application Services (EAS) account](https://expo.dev/eas). @@ -266,7 +267,7 @@ diary of sorts. To mint the NFTs we'll be using Metaplex's Umi libraries along with [Pinata Cloud](https://pinata.cloud/) to store images and metadata. We are using Pinata in this tutorial, but -[there are many good solutions for store images for long-term storage](https://solana.com/developers/guides/getstarted/how-to-create-a-token#create-and-upload-image-and-offchain-metadata). +[there are many good solutions for long-term image storage](https://solana.com/developers/guides/getstarted/how-to-create-a-token#create-and-upload-image-and-offchain-metadata). All of our onchain work will be on Devnet. The first half of this lab is cobbling together the needed components to make @@ -390,8 +391,8 @@ already have a Devnet-enabled wallet installed you can skip step 0. #### 0. Install a Devnet-enabled Solana wallet -You'll need a wallet that supports Devnet to test with. In -[our Mobile Wallet Adapter lesson](/content/courses/mobile/mwa-deep-dive) we +You'll need a wallet that supports Devnet to test with. In our +[Mobile Wallet Adapter lesson](/content/courses/mobile/mwa-deep-dive.md) we created one of these. Let's install it from the repo in a different directory from our app: @@ -436,13 +437,13 @@ Solana-based apps. Create two new folders: `components` and `screens`. We are going to use some boilerplate code from the -[first Mobile lesson](/content/courses/mobile/basic-solana-mobile). We will be -copying over `components/AuthorizationProvider.tsx` and +[first Mobile lesson](/content/courses/mobile/intro-to-solana-mobile.md). We +will be copying over `components/AuthorizationProvider.tsx` and `components/ConnectionProvider.tsx`. These files provide us with a `Connection` object as well as some helper functions that authorize our dapp. Create file `components/AuthorizationProvider.tsx` and copy the contents of -[our existing Auth Provider from Github](https://raw.githubusercontent.com/solana-developers/mobile-apps-with-expo/main/components/AuthorizationProvider.tsx) +[our existing Auth Provider from Github](https://raw.githubusercontent.com/solana-developers/mobile-apps-with-expo/main/components/AuthProvider.tsx) into the new file. Secondly, create file `components/ConnectionProvider.tsx` and copy the contents @@ -943,7 +944,7 @@ This should have the following fields: function that creates a new snapshot NFT The `DigitalAsset` type comes from `@metaplex-foundation/mpl-token-metadata` -that have metadata, off-chain metadata, collection data, plugins (including +which has metadata, off-chain metadata, collection data, plugins (including Attributes), and more. ```tsx From 21cd11a1b5c51ae506ad83ff8dad3f6e3020d17a Mon Sep 17 00:00:00 2001 From: Shawaz <65177277+shawazi@users.noreply.github.com> Date: Sun, 13 Oct 2024 03:27:23 -0400 Subject: [PATCH 54/54] Remove deprecated reference to nonexistent VSCode Grammarly extension (#504) the link no longer works; further research shows LTex is a sub-par alternative users don't enjoy, but I'm not sure if there are any other options for VSCode users. https://www.reddit.com/r/vscode/comments/1crtdg8/grammarly_extension_removed/ --- CONTRIBUTING.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2616dd370..fdf3f949a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,9 +48,7 @@ In particular: and save the person reviewing your PR some time. We recommend [Grammarly](https://grammarly.com/). In [your Grammarly dictionary](https://account.grammarly.com/customize), you may - wish to add Solana-specific words like `lamport`, `blockhash`, etc. For VScode - users, there is a - [VScode extension for Grammarly](https://marketplace.visualstudio.com/items?itemName=znck.grammarly). + wish to add Solana-specific words like `lamport`, `blockhash`, etc. - Use US English rather than British English. Grammarly will catch this for you. - Use 'onchain' (not on-chain, definitely not smart contract) when referring to onchain apps. This comes from the Solana Foundation style guide, and is