main f220f058c9d0 cached
208 files
1.0 MB
290.3k tokens
236 symbols
1 requests
Download .txt
Showing preview only (1,119K chars total). Download the full file or copy to clipboard to get everything.
Repository: freeCodeCamp/solana-curriculum
Branch: main
Commit: f220f058c9d0
Files: 208
Total size: 1.0 MB

Directory structure:
gitextract_on91dfbr/

├── .devcontainer.json
├── .editorconfig
├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── BUG.md
│       └── HELP.md
├── .gitignore
├── .gitpod.Dockerfile
├── .gitpod.yml
├── .prettierignore
├── .prettierrc
├── .vscode/
│   └── settings.json
├── Dockerfile
├── LICENSE
├── README.md
├── bash/
│   ├── .bashrc
│   └── sourcerer.sh
├── build-a-client-side-app/
│   ├── .gitignore
│   ├── mess.json
│   └── mess.ts
├── build-a-smart-contract/
│   ├── package.json
│   ├── program/
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   └── lib.rs
│   │   └── tests/
│   │       └── process_instruction.rs
│   └── wallet.json
├── build-a-university-certification-nft/
│   ├── client/
│   │   ├── .gitignore
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── app.css
│   │   │   ├── app.tsx
│   │   │   ├── index.css
│   │   │   ├── main.tsx
│   │   │   └── vite-env.d.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   └── vite.config.ts
│   ├── index.d.ts
│   ├── index.js
│   ├── metadatas.json
│   ├── package.json
│   ├── server.js
│   └── utils.js
├── build-an-anchor-leaderboard/
│   └── rock-destroyer/
│       ├── .gitignore
│       ├── .prettierignore
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── rock-destroyer/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── rock-destroyer.ts
│       └── tsconfig.json
├── build-and-deploy-your-freeform-app/
│   └── .gitkeep
├── config/
│   ├── projects.json
│   └── state.json
├── curriculum/
│   └── locales/
│       └── english/
│           ├── build-a-client-side-app.md
│           ├── build-a-smart-contract.md
│           ├── build-a-university-certification-nft.md
│           ├── build-an-anchor-leaderboard.md
│           ├── build-and-deploy-your-freeform-app.md
│           ├── learn-anchor-by-building-tic-tac-toe-part-1.md
│           ├── learn-anchor-by-building-tic-tac-toe-part-2.md
│           ├── learn-how-to-build-a-client-side-app-part-1.md
│           ├── learn-how-to-build-a-client-side-app-part-2.md
│           ├── learn-how-to-build-for-mainnet.md
│           ├── learn-how-to-deploy-to-devnet.md
│           ├── learn-how-to-interact-with-on-chain-programs.md
│           ├── learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract.md
│           ├── learn-solanas-token-program-by-minting-a-fungible-token.md
│           └── learn-the-metaplex-sdk-by-minting-an-nft.md
├── freecodecamp.conf.json
├── learn-anchor-by-building-tic-tac-toe-part-1/
│   └── .gitkeep
├── learn-anchor-by-building-tic-tac-toe-part-2/
│   ├── .prettierignore
│   └── tic-tac-toe/
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── tic-tac-toe/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── tic-tac-toe.ts
│       └── tsconfig.json
├── learn-how-to-build-a-client-side-app-part-1/
│   ├── .gitkeep
│   └── tic-tac-toe/
│       ├── .gitignore
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── app/
│       │   ├── .gitignore
│       │   ├── index.css
│       │   ├── index.html
│       │   ├── index.js
│       │   ├── package.json
│       │   ├── utils.js
│       │   └── wallet.js
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── tic-tac-toe/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── tic-tac-toe.ts
│       └── tsconfig.json
├── learn-how-to-build-a-client-side-app-part-2/
│   ├── app/
│   │   ├── .gitignore
│   │   ├── index.css
│   │   ├── index.html
│   │   ├── index.js
│   │   ├── package.json
│   │   ├── utils.js
│   │   ├── wallet.js
│   │   └── web3.js
│   └── tic_tac_toe.ts
├── learn-how-to-build-for-mainnet/
│   ├── _answer/
│   │   ├── Anchor.toml
│   │   ├── Cargo.toml
│   │   ├── app/
│   │   │   ├── index.html
│   │   │   ├── package.json
│   │   │   ├── src/
│   │   │   │   ├── app.css
│   │   │   │   ├── app.tsx
│   │   │   │   ├── index.css
│   │   │   │   ├── main.tsx
│   │   │   │   ├── utils.ts
│   │   │   │   └── vite-env.d.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.node.json
│   │   │   └── vite.config.ts
│   │   ├── migrations/
│   │   │   └── deploy.ts
│   │   ├── package.json
│   │   ├── programs/
│   │   │   └── todo/
│   │   │       ├── Cargo.toml
│   │   │       ├── Xargo.toml
│   │   │       └── src/
│   │   │           └── lib.rs
│   │   ├── tests/
│   │   │   └── todo.ts
│   │   └── tsconfig.json
│   └── todo/
│       ├── .gitignore
│       ├── .prettierignore
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── app/
│       │   ├── index.html
│       │   ├── package.json
│       │   ├── src/
│       │   │   ├── app.css
│       │   │   ├── app.tsx
│       │   │   ├── index.css
│       │   │   ├── main.tsx
│       │   │   ├── utils.ts
│       │   │   └── vite-env.d.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.node.json
│       │   └── vite.config.ts
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── todo/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── todo.ts
│       └── tsconfig.json
├── learn-how-to-deploy-to-devnet/
│   └── todo/
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── app/
│       │   ├── index.html
│       │   ├── package.json
│       │   ├── src/
│       │   │   ├── app.css
│       │   │   ├── app.tsx
│       │   │   ├── index.css
│       │   │   ├── main.tsx
│       │   │   ├── utils.ts
│       │   │   └── vite-env.d.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.node.json
│       │   └── vite.config.ts
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── todo/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── todo.ts
│       └── tsconfig.json
├── learn-how-to-interact-with-on-chain-programs/
│   ├── cluster-devnet.env
│   ├── cluster-mainnet-beta.env
│   ├── cluster-testnet.env
│   ├── package.json
│   └── src/
│       ├── _answer/
│       │   └── client/
│       │       ├── hello-world.js
│       │       └── main.js
│       └── program-rust/
│           ├── .gitignore
│           ├── Cargo.toml
│           └── src/
│               └── lib.rs
├── learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/
│   ├── .gitignore
│   ├── package.json
│   ├── src/
│   │   ├── client/
│   │   │   ├── hello_world.ts
│   │   │   ├── main.ts
│   │   │   └── utils.ts
│   │   └── program-rust/
│   │       ├── .gitignore
│   │       ├── Cargo.toml
│   │       └── src/
│   │           └── lib.rs
│   └── tsconfig.json
├── learn-solanas-token-program-by-minting-a-fungible-token/
│   ├── .gitkeep
│   ├── package.json
│   └── utils.js
├── learn-the-metaplex-sdk-by-minting-an-nft/
│   ├── package.json
│   ├── server.js
│   ├── spl-program/
│   │   ├── create-mint-account.js
│   │   ├── create-token-account.js
│   │   ├── get-token-account.js
│   │   ├── get-token-info.js
│   │   ├── mint.js
│   │   ├── package.json
│   │   ├── transfer.js
│   │   └── utils.js
│   └── utils.js
├── package.json
├── renovate.json
└── tooling/
    ├── camper-info.js
    ├── helpers.js
    ├── rejig.js
    └── seed.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .devcontainer.json
================================================
{
  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "freeCodeCamp.freecodecamp-courses@1.7.5",
        "freeCodeCamp.freecodecamp-dark-vscode-theme"
      ]
    }
  },
  "forwardPorts": [8080],
  "workspaceFolder": "/workspace/solana-curriculum",
  "dockerFile": "./Dockerfile",
  "context": "."
}


================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[package.json]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .github/ISSUE_TEMPLATE/BUG.md
================================================
---
name: Bug Found
about: Report a bug with the platform/content
title: '[BUG]: '
---

### Issue/Experience

### Output of running `node tooling/camper-info.js` from the workspace root


================================================
FILE: .github/ISSUE_TEMPLATE/HELP.md
================================================
---
name: Help Needed
about: Get help with a project or lesson
title: '[HELP]: '
---

### Project

### Lesson Number

### Question

### Code and Screenshots


================================================
FILE: .gitignore
================================================
target
node_modules
Cargo.lock
!.gitkeep
__test

# Should be included in repo, but does not get updated
.logs
test-ledger
.anchor

================================================
FILE: .gitpod.Dockerfile
================================================
FROM gitpod/workspace-full:2024-05-22-07-25-51

ARG REPO_NAME=solana-curriculum
ARG HOMEDIR=/workspace/$REPO_NAME

WORKDIR ${HOMEDIR}

RUN bash -c 'VERSION="20" \
    && source $HOME/.nvm/nvm.sh && nvm install $VERSION \
    && nvm use $VERSION && nvm alias default $VERSION'

RUN echo "nvm use default &>/dev/null" >> ~/.bashrc.d/51-nvm-fix

RUN sudo apt-get update && sudo apt-get upgrade -y

# Solana
RUN sh -c "$(curl -sSfL https://release.solana.com/v1.17.18/install)"
# RUN wget https://github.com/solana-labs/solana/releases/download/v1.16.9/solana-release-x86_64-unknown-linux-gnu.tar.bz2
# RUN tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
# RUN cd solana-release/
# ENV PATH="$PWD/bin:${PATH}"

RUN npm i -g @coral-xyz/anchor-cli@0.30.0

================================================
FILE: .gitpod.yml
================================================
image:
  file: .gitpod.Dockerfile

# Commands to start on workspace startup
tasks:
  - init: npm ci

ports:
  - port: 8080
    onOpen: open-preview

# TODO: See about publishing to Open VSX for smoother process
vscode:
  extensions:
    - https://github.com/freeCodeCamp/courses-vscode-extension/releases/download/v1.7.5/freecodecamp-courses-1.7.5.vsix
    - https://github.com/freeCodeCamp/freecodecamp-dark-vscode-theme/releases/download/v1.0.0/freecodecamp-dark-vscode-theme-1.0.0.vsix


================================================
FILE: .prettierignore
================================================
**/.cache
**/package-lock.json
**/pkg


================================================
FILE: .prettierrc
================================================
{
  "endOfLine": "lf",
  "semi": true,
  "singleQuote": true,
  "jsxSingleQuote": true,
  "tabWidth": 2,
  "trailingComma": "none",
  "arrowParens": "avoid"
}


================================================
FILE: .vscode/settings.json
================================================
{
  "files.exclude": {
    ".devcontainer.json": false,
    ".editorconfig": false,
    ".git": false,
    ".github": false,
    ".gitignore": false,
    ".gitpod.Dockerfile": false,
    ".gitpod.yml": false,
    ".logs": false,
    ".prettierignore": false,
    ".prettierrc": false,
    ".vscode": false,
    "bash": false,
    "client": false,
    "config": false,
    "curriculum": false,
    "tooling": false,
    "Dockerfile": false,
    "freecodecamp.conf.json": false,
    "node_modules": false,
    "package.json": false,
    "package-lock.json": false,
    "LICENSE": false,
    "README.md": false,
    "renovate.json": false,
    "build-x-using-y": false,
    "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract": false,
    "learn-how-to-interact-with-on-chain-programs": false,
    "build-a-smart-contract": false,
    "learn-solanas-token-program-by-minting-a-fungible-token": false,
    "learn-the-metaplex-sdk-by-minting-an-nft": false,
    "build-a-university-certification-nft": false,
    "learn-anchor-by-building-tic-tac-toe-part-1": false,
    "learn-anchor-by-building-tic-tac-toe-part-2": false,
    "build-an-anchor-leaderboard": false,
    "learn-how-to-build-a-client-side-app-part-1": false,
    "learn-how-to-build-a-client-side-app-part-2": false,
    "build-a-client-side-app": false,
    "learn-how-to-build-for-mainnet": false,
    "learn-how-to-deploy-to-devnet": false,
    "build-and-deploy-your-freeform-app": false
  },
  "terminal.integrated.profiles.linux": {
    "bash": {
      "path": "bash",
      "icon": "terminal-bash",
      "args": ["--init-file", "./bash/sourcerer.sh"]
    }
  },
  "terminal.integrated.defaultProfile.linux": "bash",
  "workbench.colorTheme": "freeCodeCamp Dark Theme"
}


================================================
FILE: Dockerfile
================================================
FROM ubuntu:20.04

ARG USERNAME=camper
ARG REPO_NAME=solana-curriculum
ARG HOMEDIR=/workspace/$REPO_NAME

ENV TZ="America/New_York"
ENV HOME=/workspace

RUN apt-get update && apt-get install -y sudo

# Unminimize Ubuntu to restore man pages
RUN yes | unminimize

# Set up timezone
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# Set up user, disable pw, and add to sudo group
RUN adduser --disabled-password \
  --gecos '' ${USERNAME}

RUN adduser ${USERNAME} sudo

RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> \
  /etc/sudoers

# Install packages for projects
RUN sudo apt-get install -y curl git bash-completion man-db htop nano

# Install Node LTS
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
RUN sudo apt-get install -y nodejs

# Rust
RUN sudo apt-get install -y build-essential
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV PATH="/workspace/.cargo/bin:${PATH}"

# Solana
RUN sh -c "$(curl -sSfL https://release.solana.com/v1.17.18/install)"
# RUN wget https://github.com/solana-labs/solana/releases/download/v1.16.9/solana-release-x86_64-unknown-linux-gnu.tar.bz2
# RUN tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
# RUN cd solana-release/
# ENV PATH="$PWD/bin:${PATH}"

# /usr/lib/node_modules is owned by root, so this creates a folder ${USERNAME} 
# can use for npm install --global
WORKDIR ${HOMEDIR}
RUN mkdir ~/.npm-global
RUN npm config set prefix '~/.npm-global'

RUN npm install -g yarn @coral-xyz/anchor-cli@0.30.0

# Configure course-specific environment
COPY . .
WORKDIR ${HOMEDIR}

RUN cd ${HOMEDIR} && npm install

# wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.16_amd64.deb
# sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2.16_amd64.deb


================================================
FILE: LICENSE
================================================
BSD 3-Clause License

Copyright (c) 2022-2023, freeCodeCamp
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
# freeCodeCamp: Solana Curriculum

Get started here: https://web3.freecodecamp.org/solana

## Projects

| Project                                                             | Description                                                                                                                                                              |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Learn How to Set Up Solana by Building a Hello World Smart Contract | In this course, you will learn how to set up Solana by building a simple hello world contract.                                                                           |
| Learn How to Interact with On-Chain Programs                        | In this course, you will learn how to write the client code to interact with your previously deployed hello world smart contract.                                        |
| Build a Smart Contract                                              | In this integrated project, you will use what you previously learnt to build a smart contract, and interact with it.                                                     |
| Learn Solana's Token Program by Minting a Fungible Token            | In this course, you will learn how to use Solana's token program by minting a fungible token.                                                                            |
| Learn the Metaplex SDK by Minting an NFT                            | In this course, you will learn how to use the Metaplex JS SDK to mint an NFT.                                                                                            |
| Build a University Certification NFT                                | In this integrated project, you will use what you previously learnt to build out the logic for an NFT-issuing system for university certifications.                      |
| Learn Anchor by Building Tic-Tac-Toe: Part 1                        | In this course, you will learn how to use Anchor, a framework for building smart contracts on Solana, to build an on-chain Tic-Tac-Toe game.                             |
| Learn Anchor by Building Tic-Tac-Toe: Part 2                        | In this course, you will learn how to test the previously built Tic-Tac-Toe game.                                                                                        |
| Build an Anchor Leaderboard                                         | In this integrated project, you will use what you previously learnt to build the program logic for a game leaderboard                                                    |
| Learn How to Build a Client-Side App: Part 1                        | In this course, you will learn how to build a multiplayer, client-side app that interacts with your previously deployed Tic-Tac-Toe game                                 |
| Learn How to Build a Client-Side App: Part 2                        | In this course, you will learn how to use the Phantom wallet browser extension to connect to your local validator, connect your wallet to a dApp, and sign transactions. |
| Build a Client Side App                                             | In this integrated project, you will use what you previously learnt to build an app your friends can use to message one another.                                         |
| More Coming Soon...                                                 | Keep an 👁️ out                                                                                                                                                           |


================================================
FILE: bash/.bashrc
================================================
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000

# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize

# If set, the pattern "**" used in a pathname expansion context will
# match all files and zero or more directories and subdirectories.
#shopt -s globstar

# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"

# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
    debian_chroot=$(cat /etc/debian_chroot)
fi

# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
    xterm-color|*-256color) color_prompt=yes;;
esac

# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes

if [ -n "$force_color_prompt" ]; then
    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
	# We have color support; assume it's compliant with Ecma-48
	# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
	# a case would tend to support setf rather than setaf.)
	color_prompt=yes
    else
	color_prompt=
    fi
fi

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
    ;;
*)
    ;;
esac

# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    #alias dir='dir --color=auto'
    #alias vdir='vdir --color=auto'

    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'
fi

# colored GCC warnings and errors
#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'

# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'

# Add an "alert" alias for long running commands.  Use like so:
#   sleep 10; alert
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'

# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
  if [ -f /usr/share/bash-completion/bash_completion ]; then
    . /usr/share/bash-completion/bash_completion
  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi

PS1='\[\]\u\[\] \[\]\w\[\]$(__git_ps1 " (%s)") $ '

for i in $(ls -A $HOME/.bashrc.d/); do source $HOME/.bashrc.d/$i; done

. "$HOME/.cargo/env"

export PATH="$HOME/.npm-global/bin:$PATH"

# freeCodeCamp - Needed for most tests to work

WD=/workspace/solana-curriculum

PROMPT_COMMAND='>| $WD/.logs/.terminal-out.log && cat $WD/.logs/.temp.log >| $WD/.logs/.terminal-out.log && truncate -s 0 $WD/.logs/.temp.log; echo $PWD >> $WD/.logs/.cwd.log; history -a $WD/.logs/.bash_history.log; echo $PWD\$ $(history | tail -n 1) >> $WD/.logs/.history_cwd.log;'
exec > >(tee -ia $WD/.logs/.temp.log) 2>&1


================================================
FILE: bash/sourcerer.sh
================================================
#!/bin/bash
source ./bash/.bashrc
echo "BashRC Sourced"


================================================
FILE: build-a-client-side-app/.gitignore
================================================
mess

================================================
FILE: build-a-client-side-app/mess.json
================================================
{
  "version": "0.1.0",
  "name": "mess",
  "instructions": [
    {
      "name": "init",
      "accounts": [
        {
          "name": "chat",
          "isMut": true,
          "isSigner": false,
          "docs": [
            "Global chat account to hold 20 messages"
          ]
        },
        {
          "name": "payer",
          "isMut": true,
          "isSigner": true
        },
        {
          "name": "systemProgram",
          "isMut": false,
          "isSigner": false
        }
      ],
      "args": []
    },
    {
      "name": "send",
      "accounts": [
        {
          "name": "chat",
          "isMut": true,
          "isSigner": false
        },
        {
          "name": "sender",
          "isMut": false,
          "isSigner": true
        }
      ],
      "args": [
        {
          "name": "text",
          "type": "string"
        }
      ]
    }
  ],
  "accounts": [
    {
      "name": "Chat",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "messages",
            "type": {
              "vec": {
                "defined": "Message"
              }
            }
          }
        ]
      }
    }
  ],
  "types": [
    {
      "name": "Message",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "sender",
            "type": "publicKey"
          },
          {
            "name": "text",
            "type": "string"
          }
        ]
      }
    }
  ]
}

================================================
FILE: build-a-client-side-app/mess.ts
================================================
export type Mess = {
  "version": "0.1.0",
  "name": "mess",
  "instructions": [
    {
      "name": "init",
      "accounts": [
        {
          "name": "chat",
          "isMut": true,
          "isSigner": false,
          "docs": [
            "Global chat account to hold 20 messages"
          ]
        },
        {
          "name": "payer",
          "isMut": true,
          "isSigner": true
        },
        {
          "name": "systemProgram",
          "isMut": false,
          "isSigner": false
        }
      ],
      "args": []
    },
    {
      "name": "send",
      "accounts": [
        {
          "name": "chat",
          "isMut": true,
          "isSigner": false
        },
        {
          "name": "sender",
          "isMut": false,
          "isSigner": true
        }
      ],
      "args": [
        {
          "name": "text",
          "type": "string"
        }
      ]
    }
  ],
  "accounts": [
    {
      "name": "chat",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "messages",
            "type": {
              "vec": {
                "defined": "Message"
              }
            }
          }
        ]
      }
    }
  ],
  "types": [
    {
      "name": "Message",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "sender",
            "type": "publicKey"
          },
          {
            "name": "text",
            "type": "string"
          }
        ]
      }
    }
  ]
};

export const IDL: Mess = {
  "version": "0.1.0",
  "name": "mess",
  "instructions": [
    {
      "name": "init",
      "accounts": [
        {
          "name": "chat",
          "isMut": true,
          "isSigner": false,
          "docs": [
            "Global chat account to hold 20 messages"
          ]
        },
        {
          "name": "payer",
          "isMut": true,
          "isSigner": true
        },
        {
          "name": "systemProgram",
          "isMut": false,
          "isSigner": false
        }
      ],
      "args": []
    },
    {
      "name": "send",
      "accounts": [
        {
          "name": "chat",
          "isMut": true,
          "isSigner": false
        },
        {
          "name": "sender",
          "isMut": false,
          "isSigner": true
        }
      ],
      "args": [
        {
          "name": "text",
          "type": "string"
        }
      ]
    }
  ],
  "accounts": [
    {
      "name": "chat",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "messages",
            "type": {
              "vec": {
                "defined": "Message"
              }
            }
          }
        ]
      }
    }
  ],
  "types": [
    {
      "name": "Message",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "sender",
            "type": "publicKey"
          },
          {
            "name": "text",
            "type": "string"
          }
        ]
      }
    }
  ]
};


================================================
FILE: build-a-smart-contract/package.json
================================================
{
  "name": "build-a-smart-contract",
  "type": "module",
  "dependencies": {
    "@solana/web3.js": "1.87.7",
    "borsh": "0.7.0"
  },
  "scripts": {
    "build": "cargo build-sbf --manifest-path program/Cargo.toml --sbf-out-dir=dist/program",
    "deploy": "solana program deploy --keypair wallet.json dist/program/message.so"
  }
}


================================================
FILE: build-a-smart-contract/program/Cargo.toml
================================================
[package]
name = "message"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
borsh = "0.10.3"
borsh-derive = "0.10.3"
solana-program = "1.17.3"

[lib]
name = "message"
crate-type = ["cdylib", "lib"]


================================================
FILE: build-a-smart-contract/program/src/lib.rs
================================================



================================================
FILE: build-a-smart-contract/program/tests/process_instruction.rs
================================================
extern crate message;
use message::process_instruction;
use borsh::BorshDeserialize;

use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};

#[derive(BorshDeserialize, Debug)]
pub struct MessageStructForTest {
    pub message: String
}

#[test]
fn owner_not_program_id() {
    let program_id = Pubkey::new_unique();
    let owner = Pubkey::new_unique();
    let mut data = vec![0; 284];
    let mut lam = 2;
    let account_info = AccountInfo::new(
        &program_id,
        false,
        true,
        &mut lam,
        &mut data,
        &owner,
        false,
        2,
    );
    let accounts = vec![account_info];
    let instruction_data = vec![];
    let result = process_instruction(&program_id, &accounts, &instruction_data);
    assert_eq!(result, Err(ProgramError::IncorrectProgramId));
}

#[test]
fn instruction_is_deserialized() {
    let program_id = Pubkey::new_unique();
    let mut data = vec![0; 284];
    let mut lam = 2;
    let account_info = AccountInfo::new(
        &program_id,
        false,
        true,
        &mut lam,
        &mut data,
        &program_id,
        false,
        2,
    );
    let accounts = vec![account_info];
    let instruction_data = "Hello World!".as_bytes();
    let _result = process_instruction(&program_id, &accounts, &instruction_data);
    let data = MessageStructForTest::try_from_slice(String::from_utf8(accounts[0].data.borrow().to_vec()).unwrap().as_bytes()).unwrap();
    let fmt = format!("{: <280}", "Hello World!");
    assert_eq!(fmt, data.message);
}

#[test]
fn instruction_not_string() {
    let program_id = Pubkey::new_unique();
    let mut data = vec![0; 284];
    let mut lam = 2;
    let account_info = AccountInfo::new(
        &program_id,
        false,
        true,
        &mut lam,
        &mut data,
        &program_id,
        false,
        2,
    );
    let accounts = vec![account_info];
    let instruction_data = vec![0, 159, 146, 150];
    let result = process_instruction(&program_id, &accounts, &instruction_data);
    assert_eq!(result, Err(ProgramError::InvalidInstructionData));
}

#[test]
fn instruction_too_long() {
    let program_id = Pubkey::new_unique();
    let mut data = vec![0; 284];
    let mut lam = 2;
    let account_info = AccountInfo::new(
        &program_id,
        false,
        true,
        &mut lam,
        &mut data,
        &program_id,
        false,
        2,
    );
    let accounts = vec![account_info];
    let instruction_data = vec![0; 281];
    let result = process_instruction(&program_id, &accounts, &instruction_data);
    assert_eq!(result, Err(ProgramError::InvalidInstructionData));
}

#[test]
fn no_accounts() {
    let program_id = Pubkey::new_unique();
    let accounts = vec![];
    let instruction_data = vec![];
    let result = process_instruction(&program_id, &accounts, &instruction_data);
    assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys));
}

#[test]
fn instruction_data_padded() {
    let program_id = Pubkey::new_unique();
    let mut data = vec![0; 284];
    let mut lam = 2;
    let account_info = AccountInfo::new(
        &program_id,
        false,
        true,
        &mut lam,
        &mut data,
        &program_id,
        false,
        2,
    );
    let accounts = vec![account_info];
    let instruction_data = "Hello World!".as_bytes();
    let _result = process_instruction(&program_id, &accounts, &instruction_data);
    let account_data = accounts[0].data.borrow();
    assert_eq!(account_data.len(), 284);
}

#[test]
fn success() {
    let program_id = Pubkey::new_unique();
    let mut data = vec![0; 284];
    let mut lam = 2;
    let account_info = AccountInfo::new(
        &program_id,
        false,
        true,
        &mut lam,
        &mut data,
        &program_id,
        false,
        2,
    );
    let accounts = vec![account_info];
    let instruction_data = "Hello World!".as_bytes();
    let result = process_instruction(&program_id, &accounts, &instruction_data);
    assert_eq!(result, Ok(()));
}


================================================
FILE: build-a-smart-contract/wallet.json
================================================
[143,176,178,47,104,172,153,69,159,235,167,100,198,176,237,12,40,244,4,227,138,109,255,63,148,130,55,85,138,167,29,47,116,163,179,15,205,180,80,61,111,245,231,19,180,55,178,17,209,19,9,73,227,57,40,209,71,188,174,32,62,95,107,144]

================================================
FILE: build-a-university-certification-nft/client/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: build-a-university-certification-nft/client/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/solana.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Solana University Certification Dashboard</title>
  </head>
  <body>
    <div id="root"></div>
    <script>
      // Global node polyfill.
      window.global = window;
    </script>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: build-a-university-certification-nft/client/package.json
================================================
{
  "name": "client",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "npm i && vite --clearScreen=false",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@metaplex-foundation/js": "0.18.3",
    "@solana/spl-token": "0.3.7",
    "@solana/web3.js": "1.87.7",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "@esbuild-plugins/node-globals-polyfill": "0.2.3",
    "@types/react": "18.2.31",
    "@types/react-dom": "18.2.14",
    "@vitejs/plugin-react": "3.1.0",
    "assert": "2.1.0",
    "crypto-browserify": "3.12.0",
    "rollup-plugin-node-polyfills": "0.2.1",
    "typescript": "4.9.3",
    "util": "0.12.5",
    "vite": "4.5.6"
  }
}


================================================
FILE: build-a-university-certification-nft/client/src/app.css
================================================
#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

form {
  display: flex;
  flex-direction: column;
  align-items: end;
}

label {
  margin: 0.5rem 0;
}

.controls {
  margin-top: 1.5rem;
}


================================================
FILE: build-a-university-certification-nft/client/src/app.tsx
================================================
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { Keypair, PublicKey, Signer } from '@solana/web3.js';
import {
  createMintAccount as camperCreateMintAccount,
  getMintAccounts as camperGetMintAccounts,
  createTokenAccount as camperCreateTokenAccount,
  mintToken as camperMintToken,
  uploadFile as camperUploadFile,
  getNFTs as camperGetNFTs
} from '../../index.js';
import './app.css';
import { toMetaplexFile } from '@metaplex-foundation/js';

// 1) Create a certificate program - create a new mint
// 2) Register new student - create a token account for the student
// 3) Issue a certificate - mint NFT to the student's token account
// 4) View a certificate - view the certificate's metadata
export function App() {
  const [output, setOutput] = useState<string>('OUTPUT');
  const [payer, setPayer] = useState<Signer>();
  const [mintAddress, setMintAddress] = useState<PublicKey>();
  const [ownerAddress, setOwnerAddress] = useState<PublicKey>();
  const [uri, setUri] = useState<string>();
  const [invalidInputs, setInvalidInputs] = useState<string[]>([]);

  const createMintAccount: CreateMintAccountF = async () => {
    if (!payer) {
      setInvalidInputs(['payer']);
      return;
    }
    setInvalidInputs([]);
    try {
      const mint = await camperCreateMintAccount({ payer });
      setOutput(JSON.stringify(mint, null, 2));
    } catch (e) {
      console.warn(e);
      setOutput(
        'Error creating mint account:\n\n' + JSON.stringify(e, null, 2)
      );
    }
  };
  const getMintAccounts: GetMintAccountsF = async () => {
    if (!payer) {
      setInvalidInputs(['payer']);
      return;
    }
    setInvalidInputs([]);
    try {
      const mintAccounts = await camperGetMintAccounts({ payer });
      setOutput(JSON.stringify(mintAccounts, null, 2));
    } catch (e) {
      console.warn(e);
      setOutput(
        'Error getting mint accounts:\n\n' + JSON.stringify(e, null, 2)
      );
    }
  };
  const createTokenAccount: CreateTokenAccountF = async () => {
    if (!payer || !mintAddress || !ownerAddress) {
      setInvalidInputs(['payer', 'mintAddress', 'ownerAddress']);
      return;
    }
    setInvalidInputs([]);
    try {
      const tokenAccount = await camperCreateTokenAccount({
        payer,
        mintAddress,
        ownerAddress
      });
      console.log(tokenAccount);
      setOutput(tokenAccount.address.toBase58());
    } catch (e) {
      console.warn(e);
      setOutput(
        'Error creating token account:\n\n' + JSON.stringify(e, null, 2)
      );
    }
  };
  const mintToken: MintTokenF = async () => {
    if (!payer || !mintAddress || !ownerAddress || !uri) {
      setInvalidInputs(['payer', 'mintAddress', 'ownerAddress', 'uri']);
      return;
    }
    setInvalidInputs([]);
    const year = new Date().getFullYear();
    try {
      const nft = await camperMintToken({
        payer,
        mintAddress,
        ownerAddress,
        year,
        uri
      });
      console.log(nft);
      setOutput(JSON.stringify(nft, null, 2));
    } catch (e) {
      console.warn(e);
      setOutput('Error minting token:\n\n' + JSON.stringify(e, null, 2));
    }
  };
  const getNFTs: GetNFTsF = async () => {
    if (!ownerAddress) {
      setInvalidInputs(['ownerAddress']);
      return;
    }
    setInvalidInputs([]);
    try {
      const nfts = await camperGetNFTs({ ownerAddress });
      console.log(nfts);
      setOutput(JSON.stringify(nfts, null, 2));
    } catch (e) {
      console.warn(e);
      setOutput('Error getting NFTs:\n\n' + JSON.stringify(e, null, 2));
    }
  };

  const imageInput = useRef<HTMLInputElement>(null);
  const previewImg = useRef<HTMLImageElement>(null);
  const uriInput = useRef<HTMLInputElement>(null);

  function setAuthority(e: ChangeEvent<HTMLInputElement>) {
    if (e.target) {
      try {
        const keypair = Keypair.fromSecretKey(
          new Uint8Array(JSON.parse(e.target.value))
        );
        setPayer(keypair);
      } catch (e) {
        console.warn(e);
        setOutput('Invalid payer secret key\n\n' + JSON.stringify(e, null, 2));
      }
    }
  }

  function setMint(e: ChangeEvent<HTMLInputElement>) {
    if (e.target) {
      try {
        const mint = new PublicKey(e.target.value);
        setMintAddress(mint);
      } catch (e) {
        console.warn(e);
        setOutput('Invalid mint public key\n\n' + JSON.stringify(e, null, 2));
      }
    }
  }

  function setOwner(e: ChangeEvent<HTMLInputElement>) {
    if (e.target) {
      try {
        const owner = new PublicKey(e.target.value);
        setOwnerAddress(owner);
      } catch (e) {
        console.warn(e);
        setOutput(
          'Invalid student public key\n\n' + JSON.stringify(e, null, 2)
        );
      }
    }
  }

  function setUriInput(e: ChangeEvent<HTMLInputElement>) {
    if (e.target) {
      setUri(e.target.value);
    }
  }

  useEffect(() => {
    if (uri) {
      if (uriInput.current && uri !== uriInput.current.value) {
        uriInput.current.value = uri;
      }
    }
  }, [uri]);

  async function uploadFile() {
    if (imageInput.current) {
      if (imageInput.current.files) {
        const file = imageInput.current.files[0];
        try {
          const arrayBuffer = await file.arrayBuffer();
          const metaplexFile = toMetaplexFile(arrayBuffer, file.name);
          if (!payer) {
            setInvalidInputs(['payer']);
            return;
          }
          setInvalidInputs([]);
          const uri = await camperUploadFile({ metaplexFile, payer });
          setUri(uri);
          setOutput(uri);
        } catch (e) {
          console.warn(e);
          setOutput('Invalid file\n\n' + JSON.stringify(e, null, 2));
        }
      }
    }
  }

  return (
    <main>
      <h1>Solana University Certification Dashboard</h1>
      <form>
        <label>
          Payer Secret Key:{' '}
          <input type='text' id='authority' onChange={setAuthority}></input>
        </label>
        <label>
          Mint Public Key:{' '}
          <input type='text' id='mint' onChange={setMint}></input>
        </label>
        <label>
          Student Public Key:{' '}
          <input type='text' id='owner' onChange={setOwner}></input>
        </label>
        <label>
          NFT Metadata URI:{' '}
          <input
            type='text'
            id='owner'
            onChange={setUriInput}
            ref={uriInput}
          ></input>
        </label>
      </form>
      <form>
        <label>
          Image File:{' '}
          <input
            type='file'
            id='image'
            accept='image/*'
            ref={imageInput}
            onChange={e => {
              if (e.target.files) {
                const file = e.target.files[0];
                const reader = new FileReader();
                reader.onload = ev => {
                  if (ev.target) {
                    if (previewImg.current) {
                      previewImg.current.src = ev.target.result as string;
                    }
                  }
                  if (reader.result) {
                    console.log(reader.result);
                  }
                };
                reader.readAsDataURL(file);
              }
            }}
          ></input>
        </label>
        <img id='preview' alt='nft preview' ref={previewImg} />
        <button
          type='submit'
          onClick={e => {
            e.preventDefault();
            uploadFile();
          }}
        >
          Upload Image
        </button>
      </form>
      <div className='controls'>
        <CreateCertificateProgram {...{ createMintAccount }} />
        <GetCertificatePrograms {...{ getMintAccounts }} />
        <RegisterStudent {...{ createTokenAccount }} />
        <GrantCertificate {...{ mintToken }} />
        <ViewStudentCertificate {...{ getNFTs }} />
      </div>
      {invalidInputs.length > 0 && <ValidationError {...{ invalidInputs }} />}
      <Output {...{ output }} />
    </main>
  );
}

type OutputT = {
  output: string;
};

type CreateMintAccountF = () => Promise<void>;
type GetMintAccountsF = () => Promise<void>;
type CreateTokenAccountF = () => Promise<void>;
type MintTokenF = () => Promise<void>;
type GetNFTsF = () => Promise<void>;

type CreateCertificateProgramT = {
  createMintAccount: CreateMintAccountF;
};

/**
 * Create a new Mint for a certificate program
 */
function CreateCertificateProgram({
  createMintAccount
}: CreateCertificateProgramT) {
  return (
    <section>
      <p>Create Certificate Program</p>
      <button onClick={() => createMintAccount()}>Create Mint Account</button>
    </section>
  );
}

/**
 * Get all mitns. Useful to get the mint address for a certificate program
 */
function GetCertificatePrograms({
  getMintAccounts
}: {
  getMintAccounts: GetMintAccountsF;
}) {
  return (
    <section>
      <p>Get Certificate Programs</p>
      <button onClick={() => getMintAccounts()}>Get Mint Accounts</button>
    </section>
  );
}

/**
 * Create a new token account associated with the public key of the student
 */
function RegisterStudent({
  createTokenAccount
}: {
  createTokenAccount: CreateTokenAccountF;
}) {
  return (
    <section>
      <p>Register Student</p>
      <button onClick={() => createTokenAccount()}>Create Token Account</button>
    </section>
  );
}

/**
 * Mint a new NFT to the student's token account
 */
function GrantCertificate({ mintToken }: { mintToken: MintTokenF }) {
  return (
    <section>
      <p>Grant Certificate</p>
      <button onClick={() => mintToken()}>Mint Token</button>
    </section>
  );
}

function ViewStudentCertificate({ getNFTs }: { getNFTs: GetNFTsF }) {
  return (
    <section>
      <p>View Student Certificate</p>
      <button onClick={() => getNFTs()}>Get NFT</button>
    </section>
  );
}

type DisplayPngT = {
  buffer: Buffer;
};

function DisplayPng({ buffer }: DisplayPngT) {
  return <img src={`data:image/png;base64,${buffer.toString('base64')}`}></img>;
}

function Output({ output }: OutputT) {
  return (
    <div>
      <pre style={{ textAlign: 'left' }}>
        <code>{output}</code>
      </pre>
    </div>
  );
}

function ValidationError({ invalidInputs }: { invalidInputs: string[] }) {
  return (
    <div>
      <p>Form requires: {invalidInputs.join(', ')}</p>
    </div>
  );
}


================================================
FILE: build-a-university-certification-nft/client/src/index.css
================================================
:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #000000;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;

  --solana-purple: #9945ff;
  --solana-green: #14f195;
}

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}
a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

h1 {
  font-size: 1.8em;
  line-height: 1.1;
  background: -webkit-linear-gradient(
    120deg,
    var(--solana-green),
    var(--solana-purple)
  );
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.controls {
  display: flex;
  flex-direction: row;
  gap: 1em;
  padding: 1em;
  border-radius: 8px;
  background-color: rgba(40, 40, 40, 0.5);
  backdrop-filter: blur(8px);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: var(--solana-purple);
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}


================================================
FILE: build-a-university-certification-nft/client/src/main.tsx
================================================
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './app';
import './index.css';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);


================================================
FILE: build-a-university-certification-nft/client/src/vite-env.d.ts
================================================
/// <reference types="vite/client" />


================================================
FILE: build-a-university-certification-nft/client/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}


================================================
FILE: build-a-university-certification-nft/client/tsconfig.node.json
================================================
{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}


================================================
FILE: build-a-university-certification-nft/client/vite.config.ts
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
import nodePolyfills from 'rollup-plugin-node-polyfills';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      stream: 'node_modules/rollup-plugin-node-polyfills/polyfills/stream.js',
      events: 'node_modules/rollup-plugin-node-polyfills/polyfills/events.js',
      assert: 'assert',
      crypto: 'node_modules/crypto-browserify/index.js',
      util: 'util',
      'near-api-js': 'near-api-js/dist/near-api-js.js'
    }
  },
  define: {
    'process.env': process.env ?? {}
  },
  build: {
    target: 'esnext',
    rollupOptions: {
      plugins: [nodePolyfills({ crypto: true })]
    }
  },
  optimizeDeps: {
    esbuildOptions: {
      plugins: [NodeGlobalsPolyfillPlugin({ buffer: true })]
    }
  }
});


================================================
FILE: build-a-university-certification-nft/index.d.ts
================================================
import { MetaplexFile, CreateNftOutput, FindNftsByOwnerOutput } from '@metaplex-foundation/js';
import { Account, createMint } from '@solana/spl-token';
import {
  AccountInfo,
  ParsedAccountData,
  PublicKey,
  Signer
} from '@solana/web3.js';

declare function uploadFile({
  metaplexFile,
  payer
}: {
  metaplexFile: MetaplexFile;
  payer: Signer;
}): Promise<string>;

declare function createMintAccount({
  payer
}: {
  payer: Signer;
}): ReturnType<typeof createMint>;

declare function getMintAccounts({
  payer
}: {
  payer: Signer;
}): Promise<AccountInfo<ParsedAccountData>[]>;

declare function createTokenAccount({
  payer,
  mintAddress,
  ownerAddress
}: {
  payer: Signer;
  mintAddress: PublicKey;
  ownerAddress: PublicKey;
}): Promise<Account>;

declare function mintToken({
  payer,
  mintAddress,
  ownerAddress,
  year,
  uri
}: {
  payer: Signer;
  mintAddress: PublicKey;
  ownerAddress: PublicKey;
  year: number;
  uri: string;
}): Promise<CreateNftOutput>;

declare function getNFTs({
  ownerAddress
}: {
  ownerAddress: PublicKey;
}): Promise<FindNftsByOwnerOutput>;


================================================
FILE: build-a-university-certification-nft/index.js
================================================


================================================
FILE: build-a-university-certification-nft/metadatas.json
================================================
{}


================================================
FILE: build-a-university-certification-nft/package.json
================================================
{
  "name": "build-a-university-certification-nft",
  "scripts": {
    "start:server": "node server.js",
    "start:client": "cd client && npm run dev -- --force"
  },
  "type": "module",
  "dependencies": {
    "cors": "2.8.5",
    "express": "4.20.0"
  }
}


================================================
FILE: build-a-university-certification-nft/server.js
================================================
import { readFileSync, writeFileSync } from 'fs';
import cors from 'cors';
import express from 'express';
import { setDefaultResultOrder } from 'dns';

setDefaultResultOrder('ipv4first');

const app = express();

app.use(cors());
app.use((req, res, next) => {
  console.log(req.method, req.url);
  next();
});
app.use(express.json());

app.get('/status/ping', (_req, res) => {
  console.log('Got ping');
  return res.status(200).send('pong');
});

app.get('/meta/:id', (req, res) => {
  console.log('GET', req.params.id);
  const metadatas = getMetadatas();
  const metadata = metadatas[req.params.id];
  if (!metadata) {
    return res.status(404).end();
  }

  return res.send(Buffer.from(metadata));
});
app.put('/meta/:id', (req, res) => {
  console.log('POST', req.params.id);
  const metadatas = getMetadatas();
  metadatas[req.params.id] = req.body;
  writeMetadatas(metadatas);
  res.status(200).end();
});

const PORT = process.env.PORT || 3002;
app.listen(PORT, () => {
  console.log('Server started at', `http://127.0.0.1:${PORT}`);
});

function getMetadatas() {
  const metadatas = readFileSync('metadatas.json', 'utf8');
  return JSON.parse(metadatas);
}

function writeMetadatas(metadatas) {
  writeFileSync('metadatas.json', JSON.stringify(metadatas));
}


================================================
FILE: build-a-university-certification-nft/utils.js
================================================
export function localStorage(options) {
  return {
    install(metaplex) {
      metaplex.storage().setDriver(new LocalStorageDriver(options));
    }
  };
}

class LocalStorageDriver {
  constructor(options) {
    if (!options.baseUrl) {
      throw new Error('Missing baseUrl option');
    }
    this.baseUrl = options.baseUrl;
    this.costPerByte = 2;
  }
  async getUploadPrice(bytes) {
    const { amount } = await import('@metaplex-foundation/js');
    return amount(this.costPerByte * bytes, { symbol: 'SOL', decimals: 9 });
  }
  async upload(file) {
    const uri = `${this.baseUrl}meta/${file.uniqueName}`;
    await fetch(uri, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(file.buffer)
    });
    return uri;
  }
  async download(uri) {
    const { toMetaplexFile } = await import('@metaplex-foundation/js');
    const res = await fetch(uri);
    if (!res) {
      throw new Error(`URI not found: ${uri}`);
    }
    const buffer = await res.arrayBuffer();
    const metaplexFile = toMetaplexFile(buffer, uri);
    return metaplexFile;
  }
}


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/.gitignore
================================================

.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/.prettierignore
================================================

.anchor
.DS_Store
target
node_modules
dist
build
test-ledger


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/Anchor.toml
================================================
[features]
seeds = false
skip-lint = false
[programs.localnet]
rock_destroyer = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"

[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"


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/Cargo.toml
================================================
[workspace]
members = [
    "programs/*"
]

[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1
[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/migrations/deploy.ts
================================================
// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.

const anchor = require("@coral-xyz/anchor");

module.exports = async function (provider) {
  // Configure client to use the provider.
  anchor.setProvider(provider);

  // Add your deploy script here.
};


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/package.json
================================================
{
  "scripts": {
    "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
    "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
  },
  "dependencies": {
    "@coral-xyz/anchor": "^0.27.0"
  },
  "devDependencies": {
    "chai": "^4.3.4",
    "mocha": "^9.0.3",
    "ts-mocha": "^10.0.0",
    "@types/bn.js": "^5.1.0",
    "@types/chai": "^4.3.0",
    "@types/mocha": "^9.0.0",
    "typescript": "^4.3.5",
    "prettier": "^2.6.2"
  }
}


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/Cargo.toml
================================================
[package]
name = "rock-destroyer"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "rock_destroyer"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []

[dependencies]
anchor-lang = "0.27.0"


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/Xargo.toml
================================================
[target.bpfel-unknown-unknown.dependencies.std]
features = []


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/src/lib.rs
================================================
use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod rock_destroyer {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/tests/rock-destroyer.ts
================================================
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { RockDestroyer } from "../target/types/rock_destroyer";

describe("rock-destroyer", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());

  const program = anchor.workspace.RockDestroyer as Program<RockDestroyer>;

  it("Is initialized!", async () => {
    // Add your test here.
    const tx = await program.methods.initialize().rpc();
    console.log("Your transaction signature", tx);
  });
});


================================================
FILE: build-an-anchor-leaderboard/rock-destroyer/tsconfig.json
================================================
{
            "compilerOptions": {
              "types": ["mocha", "chai"],
              "typeRoots": ["./node_modules/@types"],
              "lib": ["es2015"],
              "module": "commonjs",
              "target": "es6",
              "esModuleInterop": true
            }
          }
          

================================================
FILE: build-and-deploy-your-freeform-app/.gitkeep
================================================


================================================
FILE: config/projects.json
================================================
[
  {
    "id": 0,
    "title": "Learn How to Set Up Solana by Building a Hello World Smart Contract",
    "dashedName": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract",
    "isIntegrated": false,
    "description": "In this course, you will learn how to set up Solana by building a simple hello world contract.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 58
  },
  {
    "id": 1,
    "title": "Learn How to Interact with On-Chain Programs",
    "dashedName": "learn-how-to-interact-with-on-chain-programs",
    "isIntegrated": false,
    "description": "In this course, you will learn how to write the client code to interact with your previously deployed hello world smart contract.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 65
  },
  {
    "id": 2,
    "title": "Build a Smart Contract",
    "dashedName": "build-a-smart-contract",
    "isIntegrated": true,
    "description": "In this integrated project, you will use what you previously learnt to build a smart contract, and interact with it.",
    "isPublic": true,
    "currentLesson": 1,
    "numberOfLessons": 1
  },
  {
    "id": 3,
    "title": "Learn Solana's Token Program by Minting a Fungible Token",
    "dashedName": "learn-solanas-token-program-by-minting-a-fungible-token",
    "isIntegrated": false,
    "description": "In this course, you will learn how to use Solana's token program by minting a fungible token.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 52
  },
  {
    "id": 4,
    "title": "Learn the Metaplex SDK by Minting an NFT",
    "dashedName": "learn-the-metaplex-sdk-by-minting-an-nft",
    "isIntegrated": false,
    "description": "In this course, you will learn how to use the Metaplex JS SDK to mint an NFT.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 51
  },
  {
    "id": 5,
    "title": "Build a University Certification NFT",
    "dashedName": "build-a-university-certification-nft",
    "isIntegrated": true,
    "description": "In this integrated project, you will use what you previously learnt to build out the logic for an NFT-issuing system for university certifications.",
    "isPublic": true,
    "currentLesson": 1,
    "numberOfLessons": 1
  },
  {
    "id": 6,
    "title": "Learn Anchor by Building Tic-Tac-Toe: Part 1",
    "dashedName": "learn-anchor-by-building-tic-tac-toe-part-1",
    "isIntegrated": false,
    "description": "In this course, you will learn how to use Anchor, a framework for building smart contracts on Solana, to build an on-chain Tic-Tac-Toe game.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 68
  },
  {
    "id": 7,
    "title": "Learn Anchor by Building Tic-Tac-Toe: Part 2",
    "dashedName": "learn-anchor-by-building-tic-tac-toe-part-2",
    "isIntegrated": false,
    "description": "In this course, you will learn how to test the previously built Tic-Tac-Toe game.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 67
  },
  {
    "id": 8,
    "title": "Build an Anchor Leaderboard",
    "dashedName": "build-an-anchor-leaderboard",
    "isIntegrated": true,
    "description": "In this integrated project, you will use what you previously learnt to build the program logic for a game leaderboard.",
    "isPublic": true,
    "currentLesson": 1,
    "numberOfLessons": 1
  },
  {
    "id": 9,
    "title": "Learn How to Build a Client-Side App: Part 1",
    "dashedName": "learn-how-to-build-a-client-side-app-part-1",
    "isIntegrated": false,
    "description": "In this project, you will learn how to build a multi-player client-side app that interacts with your previously deployed Tic-Tac-Toe game.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 62
  },
  {
    "id": 10,
    "title": "Learn How to Build a Client-Side App: Part 2",
    "dashedName": "learn-how-to-build-a-client-side-app-part-2",
    "isIntegrated": false,
    "description": "In this project, you will learn how to use the Phantom browser extension to connect to your local validator, connect your wallet to your dApp, and sign transactions.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 35
  },
  {
    "id": 11,
    "title": "Build a Client-Side App",
    "dashedName": "build-a-client-side-app",
    "isIntegrated": true,
    "description": "In this integrated project, you will use what you previously learnt to build an app your friends can use to message one another.",
    "isPublic": true,
    "currentLesson": 1,
    "numberOfLessons": 1
  },
  {
    "id": 12,
    "title": "Learn How to Build for Mainnet",
    "dashedName": "learn-how-to-build-for-mainnet",
    "isIntegrated": false,
    "description": "In this project, you will learn how to build a dApp from start to finish, preparing for deployment to mainnet-beta.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 1
  },
  {
    "id": 13,
    "title": "Learn How to Deploy to Devnet",
    "dashedName": "learn-how-to-deploy-to-devnet",
    "isIntegrated": false,
    "description": "In this project, you will learn how to put an app on the public devnet.",
    "isPublic": true,
    "currentLesson": 1,
    "runTestsOnWatch": true,
    "numberOfLessons": 1
  },
  {
    "id": 14,
    "title": "Build and Deploy Your Freeform App",
    "dashedName": "build-and-deploy-your-freeform-app",
    "isIntegrated": true,
    "description": "The final project of this course. Use what you have learnt to build and deploy your own app to the public devnet or mainnet-beta, and share your work with the world!",
    "isPublic": true,
    "currentLesson": 1,
    "numberOfLessons": 1
  }
]


================================================
FILE: config/state.json
================================================
{
  "currentProject": null,
  "locale": "english"
}

================================================
FILE: curriculum/locales/english/build-a-client-side-app.md
================================================
# Solana - Build a Client Side App

## 1

### --description--

You are developing a client-side app to interact with the `mess.so` program.

The `mess.so` program consists of a `chat` account that stores a list of <= 20 messages.

You will be working entirely within the `build-a-client-side-app/` directory.

### User Stories

1. You should generate two new keypairs stored in `messer-1.json` and `messer-2.json`.
2. You should deploy the `mess.so` program to a local Solana validator.
3. You should airdrop SOL into each of the two keypairs.
4. You should initialize the `chat` account, by calling the `init` instruction.
   1. The payer should be one of the two keypairs.
5. You should send at least 20 messages using your client app.
   1. At least 5 messages should be sent from each of the two keypairs.

#### Types

- The `mess` program IDL is stored in `mess.ts`.

### Notes

- The `mess.so` program id is `TODO`.
- The `chat` account has a seed of `"global"`.

### --tests--

The validator should be running at `http://localhost:8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

The mess program should be deployed.

```js
const command = `curl http://127.0.0.1:8899 -X POST -H "Content-Type: application/json" -d '
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "BPFLoader2111111111111111111111111111111111", {
        "encoding": "base64",
        "dataSlice": {
          "length": 0,
          "offset": 0
        }
      }
    ]
}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.exists(jsonOut.result.find(r => r.pubkey === __programId));
} catch (e) {
  assert.fail(
    e,
    `Try running \`solana-test-validator --bpf-program ${__programId} mess.so --reset\``
  );
}
```

There should be a keypair named `messer-1.json`.

```js
const { access, constants } = await import('fs/promises');
await access(join(project.dashedName, 'messer-1.json'), constants.F_OK);

try {
  const { Keypair } = await import('@solana/web3.js');
  const keypair = Keypair.fromSecretKey(Uint8Array.from(__messer1_json));
} catch (e) {
  assert.fail(e, 'Try running `solana-keygen new --outfile messer-1.json`.');
}
```

There should be a keypair named `messer-2.json`.

```js
const { access, constants } = await import('fs/promises');
await access(join(project.dashedName, 'messer-2.json'), constants.F_OK);

try {
  const { Keypair } = await import('@solana/web3.js');
  const keypair = Keypair.fromSecretKey(Uint8Array.from(__messer2_json));
} catch (e) {
  assert.fail(e, 'Try running `solana-keygen new --outfile messer-2.json`.');
}
```

The `messer-1.json` keypair should have a balance greater than 0 SOL.

```js
const { stdout } = await __helpers.getCommandOutput(
  `solana balance ${project.dashedName}/messer-1.json`
);
const balance = stdout.trim()?.match(/\d+/)?.[0];
assert.isAbove(
  parseInt(balance),
  0,
  'Try running `solana airdrop 1 ./messer-1.json`.'
);
```

The `messer-2.json` keypair should have a balance greater than 0 SOL.

```js
const { stdout } = await __helpers.getCommandOutput(
  `solana balance ${project.dashedName}/messer-2.json`
);
const balance = stdout.trim()?.match(/\d+/)?.[0];
assert.isAbove(
  parseInt(balance),
  0,
  'Try running `solana airdrop 1 ./messer-2.json`.'
);
```

The `chat` account should be initialized.

```js
const accountInfo = await __connection.getAccountInfo(__chatPublicKey);
assert.exists(accountInfo);
```

The `messer-1.json` keypair should have sent at least 5 messages.

```js
const pubkey = __messer1_keypair.publicKey;
const chatData = await program.account.chat.fetch(pubkey);
const messages = chatData.messages.filter(m => m.sender.equals(pubkey));
assert.isAtLeast(messages.length, 5);
```

The `messer-2.json` keypair should have sent at least 5 messages.

```js
const pubkey = __messer2_keypair.publicKey;
const chatData = await program.account.chat.fetch(pubkey);
const messages = chatData.messages.filter(m => m.sender.equals(pubkey));
assert.isAtLeast(messages.length, 5);
```

At least 20 messages should have been sent.

```js
const chatData = await program.account.chat.fetch(__chatPublicKey);
assert.isAtLeast(chatData.messages.length, 20);
```

### --before-all--

```js
const { AnchorProvider, setProvider, Program } = await import(
  '@coral-xyz/anchor'
);
const { PublicKey, Connection, Keypair } = await import('@solana/web3.js');

setProvider(AnchorProvider.env());
const IDL = JSON.parse(await __helpers.getFile('mess.json'));
const PROGRAM_ID = new PublicKey(
  '8D2EQasXmadK7bWhRPrkryhAGYtERQzzGMJVGiisUqqh'
);
const program = new Program(IDL, PROGRAM_ID);

const connection = new Connection('http://localhost:8899', 'confirmed');

const [chatPublicKey, _] = PublicKey.findProgramAddressSync(
  [Buffer.from('global')],
  new PublicKey('8D2EQasXmadK7bWhRPrkryhAGYtERQzzGMJVGiisUqqh')
);

try {
  const messer1_keypair = JSON.parse(
    await __helpers.getFile(join(project.dashedName, 'messer-1.json'))
  );
  const messer2_keypair = JSON.parse(
    await __helpers.getFile(join(project.dashedName, 'messer-2.json'))
  );
  global.__messer1_json = messer1_keypair;
  global.__messer2_json = messer2_keypair;

  const keypair1 = Keypair.fromSecretKey(Uint8Array.from(__messer1_keypair));
  const keypair2 = Keypair.fromSecretKey(Uint8Array.from(__messer2_keypair));

  global.__messer1_keypair = keypair1;
  global.__messer2_keypair = keypair2;
} catch (e) {
  logover.warn(
    'You need to create two keypairs. Try running `solana-keygen new --outfile messer-1.json` and `solana-keygen new --outfile messer-2.json`.',
    e
  );
}

global.__chatPublicKey = chatPublicKey;
global.__connection = connection;
global.__programId = PROGRAM_ID;
global.__program = program;
```

### --after-all--

```js
delete global.__messer1_keypair;
delete global.__messer2_keypair;
delete global.__chatPublicKey;
delete global.__messer1_json;
delete global.__messer2_json;
delete global.__connection;
delete global.__programId;
delete global.__program;
```

## --fcc-end--


================================================
FILE: curriculum/locales/english/build-a-smart-contract.md
================================================
# Solana - Build a Smart Contract

## 1

### --description--

You need to create a smart contract in Rust, deploy the contract to your localnet, and write a Nodejs script to interact with the contract.

**User Stories**

- You should generate a new keypair
  - The keypair should be stored in `wallet.json`
  - The keypair should be used to deploy the program account
  - The keypair should **not** use a BIP39 passphrase
- You should write a smart contract in Rust
  - The program should be written in `program/src/lib.rs`
  - The program should exort a `process_instruction` function with the `solana_program::entrypoint` parameter signature
  - The program should return the `NotEnoughAccountKeys` variant of `ProgramError` if the number of accounts is less than 1
  - The program should return the `IncorrectProgramId` variant of `ProgramError` if the account owner does not match the program id
  - The program should own a data account for storing a text message of 280 characters
    - The program data account should hold data in the form of:
      ```rust
      struct MessageAccount {
        message: String,
      }
      ```
  - The program should deserialize the `InstructionData` into a `String`, and store the string in the program data account
    - If the `InstructionData` is not deserializable into a `String`, the program should return the `InvalidInstructionData` variant of `ProgramError`
    - If the `String` length is greater than 280 characters, the program should return the `InvalidInstructionData` variant of `ProgramError`
    - The `InstructionData` should be padded with space characters to 280 characters
  - The program should be built using `cargo-build-sbf`
    - The resulting `.so` and `.json` files should be stored in the `dist/program/` directory
- You should write a script interacting with the smart contract
  - The script entrypoint should be `client/main.js`
  - The script should expect a string as a command line argument
    - If no argument is provided, the script should throw an error with the message `"No message provided"`
    - The string should be sent as the instruction data when calling the smart contract
  - The script should use the account stored in `wallet.json` to pay for transactions
  - The script should use the `dist/program/` keypair file to get the program id
  - The script should create a program data account, if one does not already exist
    - The program data account public key should be created using `"fcc-seed"` as the seed
- You should have a local Solana cluster running at port `8899`
  - The program should be deployed to the local cluster
  - The program data account should be created on the local cluster

**NOTES:**

- All referenced paths are relative to `build-a-smart-contract/`

### --tests--

You should have a `wallet.json` file in the root of your project.

```js
const walletExists = __helpers.fileExists(join(__loc, 'wallet.json'));
assert.isTrue(walletExists, 'wallet.json should exist');
```

You should have a local Solana cluster running at port `8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

You should have a `dist/program/` directory.

```js
const distExists = __helpers.fileExists(join(__loc, 'dist'));
assert.isTrue(distExists, 'dist/ should exist');
const programExists = __helpers.fileExists(join(__loc, 'dist', 'program'));
assert.isTrue(programExists, 'dist/program/ should exist');
```

You should have a `.so` file in the `dist/program/` directory.

```js
const dir = await __helpers.getDirectory(join(__loc, 'dist', 'program'));
let program;
for (const file of dir) {
  if (file.endsWith('.so')) {
    program = file;
  }
}
assert.exists(program, 'dist/program/ should have a .so file');
```

You should have a `.json` file in the `dist/program/` directory.

```js
const dir = await __helpers.getDirectory(join(__loc, 'dist', 'program'));
let keypair;
for (const file of dir) {
  if (file.endsWith('.json')) {
    keypair = file;
  }
}
assert.exists(keypair, 'dist/program/ should have a .json file');
```

You should deploy the `.so` file as an executable program to the local net.

```js
const programKeypair = await __helpers.getProgramKeypair();
assert.exists(programKeypair, 'dist/program/ does not have a .json file');
const programId = programKeypair.publicKey;
const connection = __helpers.establishConnection();
assert.exists(connection, 'unable to establish connection to localnet');
const programAccountInfo = await connection.getAccountInfo(programId);
assert.exists(programAccountInfo, 'Program not deployed to the local net');
assert.equal(
  programAccountInfo.executable,
  true,
  'Program is not deployed as an executable'
);
```

The owner of the program account should be the associated account of the `wallet.json` keypair.

```js
const camperKeypair = await __helpers.getCamperKeypair();
assert.exists(camperKeypair, 'wallet.json does not exist');
const connection = __helpers.establishConnection();
assert.exists(connection, 'unable to establish connection to localnet');
const camperAccount = await connection.getAccountInfo(camperKeypair.publicKey);
assert.exists(camperAccount, 'wallet.json does not have an associated account');
const programKeypair = await __helpers.getProgramKeypair();
assert.exists(programKeypair, 'dist/program/ does not have a .json file');
const programId = programKeypair.publicKey;
const programAccountInfo = await connection.getAccountInfo(programId);
assert.exists(programAccountInfo, 'Program not deployed to the local net');
assert.equal(
  programAccountInfo.executable,
  true,
  'Program is not deployed as an executable'
);
const { stdout, stderr } = await __helpers.getCommandOutput(
  `solana program show ${programId}`
);
assert.include(stdout, 'Authority:', "Program owner not found, run 'solana program show <program_id>' and make sure there's an 'Authority' ID");
const authority = stdout.match(/Authority: \S+/gm)
assert.equal(
  authority[0].split(' ')[1],
  camperKeypair.publicKey.toBase58(),
  'Program account owner does not match the wallet.json account owner'
);
```

The program should return the `IncorrectProgramId` variant of `ProgramError` if the account owner does not match the program id.

```js
// Should pass `owner_not_program_id` test
const { stdout, stderr } = await __helpers.getCommandOutput(
  `cargo test owner_not_program_id`, `${__loc}/program`
);
assert.include(stdout, 'test owner_not_program_id ... ok');
```

The program should return the `NotEnoughAccountKeys` variant of `ProgramError` if the number of account keys is less than 1.

```js
// Should pass `no_accounts` test
const { stdout, stderr } = await __helpers.getCommandOutput(
  `cargo test no_accounts`, `${__loc}/program`
);
assert.include(stdout, 'test no_accounts ... ok');
```

The program should own a data account for storing a text message of 280 characters.

```js
const programKeypair = await __helpers.getProgramKeypair();
assert.exists(programKeypair, 'dist/program/ does not have a .json file');
const programId = programKeypair.publicKey;
const connection = __helpers.establishConnection();
assert.exists(connection, 'unable to establish connection to localnet');
const dataAccountPublicKey = await __helpers.getDataAccountPublicKey();
assert.exists(dataAccountPublicKey, 'Unable to get data account public key');
const dataAccountInfo = await connection.getAccountInfo(dataAccountPublicKey);
assert.exists(dataAccountInfo, 'Data account does not exist');
assert.equal(
  dataAccountInfo.owner.toBase58(),
  programId.toBase58(),
  'Data account owner does not match program id'
);
```

The data account should be created using the `wallet.json` public key, `"fcc-seed"` as the seed, and the program id as the owner.

```js
const expectedDataAccountPublicKey = await __helpers.getDataAccountPublicKey();
assert.exists(
  expectedDataAccountPublicKey,
  'Unable to get data account public key'
);
```

The program should deserialize the `InstructionData` into a `String`, and store the string in the program data account.

```js
// Should pass `instruction_is_deserialized` test
const { stdout, stderr } = await __helpers.getCommandOutput(
  `cargo test instruction_is_deserialized`, `${__loc}/program`
);
assert.include(stdout, 'test instruction_is_deserialized ... ok');
```

If the `InstructionData` is not deserializable into a `String`, the program should return the `InvalidInstructionData` variant of `ProgramError`.

```js
// Should pass `instruction_not_string` test
const { stdout, stderr } = await __helpers.getCommandOutput(
  `cargo test instruction_not_string`, `${__loc}/program`
);
assert.include(stdout, 'test instruction_not_string ... ok');
```

If the `String` length is greater than 280 characters, the program should return the `InvalidInstructionData` variant of `ProgramError`.

```js
// Should pass `instruction_too_long` test
const { stdout, stderr } = await __helpers.getCommandOutput(
  `cargo test instruction_too_long`, `${__loc}/program`
);
assert.include(stdout, 'test instruction_too_long ... ok');
```

The `InstructionData` should be padded with space characters to 280 characters.

```js
// Should pass `instruction_data_padded` test
const { stdout, stderr } = await __helpers.getCommandOutput(
  `cargo test instruction_data_padded`, `${__loc}/program`
);
assert.include(stdout, 'test instruction_data_padded ... ok');
```

You should write a `client/main.js` script interacting with the smart contract.

```js
const clientExists = await __helpers.fileExists(join(__loc, 'client'));
assert.isTrue(clientExists, 'client/ does not exist');
const mainExists = await __helpers.fileExists(join(__loc, 'client', 'main.js'));
assert.isTrue(mainExists, 'client/main.js does not exist');
```

Calling `node client/main.js` should throw an error with the message `"No message provided"`.

```js
const { stdout, stderr } = await __helpers.getCommandOutput(
  `node client/main.js`, __loc
);
assert.include(stderr, 'No message provided');
```

Calling `node client/main.js "Test string"` should change the message stored in the program data account.

```js
const { stdout, stderr } = await __helpers.getCommandOutput(
  `node client/main.js "Test string"`, __loc
);
const connection = __helpers.establishConnection();
assert.exists(connection, 'unable to establish connection to localnet');
const dataAccountPublicKey = await __helpers.getDataAccountPublicKey();
assert.exists(dataAccountPublicKey, 'Unable to get data account public key');
const message = await __helpers.getMessage(connection, dataAccountPublicKey);
assert.include(message, 'Test string');
assert.equal(message, 'Test string'.padEnd(280, ' '));
```

### --before-all--

```js
global.__loc = 'build-a-smart-contract';
```

### --after-all--

```js
delete global.__loc;
```

## --fcc-end--


================================================
FILE: curriculum/locales/english/build-a-university-certification-nft.md
================================================
# Solana - Build a University Certification NFT

## 1

### --description--

You have been contacted by Solana University to build an NFT that will be used to certify students who have completed their course. You will be building an NFT that will be minted by the university and will be used to certify students who have completed their course.

**User Stories**

1. You should generate a new keypair and store it in a file called `solana-university-wallet.json`
2. You should use the `solana-university-wallet.json` keypair as the payer for all transactions
3. You should generate two more keypairs stored in `student-1.json` and `student-2.json`
4. You should deploy the Metaplex Token Metadata program to your local Solana cluster
5. You should create a connection to your local cluster which should be used for all transactions
6. You should use the provided `localStorage` function in `utils.js` for the Metaplex storage driver
7. You should export a function named `uploadFile` from `index.js` with the signature defined in `index.d.ts`
   1. `uploadFile` should upload the provided `metaplexFile` parameter to the storage driver
   2. `uploadFile` should upload metadata consiting of the image URL returned from the storage driver, and the `fileName` property of the `metaplexFile`
   3. `uploadFile` should use the provided `payer` parameter as the fee payer for the metadata upload transaction
8. You should export a function named `createMintAccount` from `index.js` with the signature defined in `index.d.ts`
   1. `createMintAccount` should create and initialise a new NFT mint, using the provided `payer` parameter as the fee payer, mint authority, and freeze authority
9. You should export a function named `getMintAccounts` from `index.js` with the signature defined in `index.d.ts`
   1. `getMintAccounts` should return all mint accounts owned by the provided `payer` parameter
10. You should export a function named `createTokenAccount` from `index.js` with the signatrure defined in `index.d.ts`
11. `createTokenAccount` should get or create an associated token account for the provided `ownerAddress` parameter
12. `createTokenAccount` should use the provided `payer` parameter to pay for the transaction fee
13. `createTokenAccount` should use the provided `mintAddress` parameter as the mint associated with the token account
14. You should export a function named `mintToken` from `index.js` with the signature defined in `index.d.ts`
    1. `mintToken` should mint an NFT to the associated token account of the provided account (`ownerAddress`), using the existing mint account (`mintAddress`)
    2. `mintToken` should use the provided `uri` parameter to point to the JSON metadata
    3. `mintToken` should use the provided `year` parameter to give the NFT a `name` of `SOL-{year}`
    4. `mintToken` should mint an NFT with `0` royalties when resold
    5. `mintToken` should mint an NFT with a `symbol` of `SOLU`
    6. `mintToken` should mint an NFT that is set to immutable
    7. `mintToken` should mint an NFT owned by the associated token account of the provided account (`ownerAddress`)
    8. `mintToken` should mint an NFT with an update authority set to the provided `payer` parameter
    9. `mintToken` should mint an NFT with an mint authority set to the provided `payer` parameter
15. You should export a function named `getNFTs` from `index.js` with the signature defined in `index.d.ts`
    1. `getNFTs` should return all NFTs owned by the provided `ownerAddress` parameter
16. You should use the Solana Univeristy Dashboard (`client/` _see below_) to create a new mint account
    1. The `payer` should be the `solana-university-wallet.json` keypair
17. You should use the Solana University Dashboard to create two token accounts associated with the new mint account, and owned by `student-1.json` and `student-2.json` respectively
18. You should use the Solana University Dashboard to upload a metaplex file to the storage driver
    1. You can use any image file for this, but one is provided: `solanaLogoMark.png`
19. You should use the Solana University Dashboard to mint one token to each new token account

**Types**

The expected signatures for your functions are visible in the `index.d.ts` file. This file should **not** be modified.

**Commands**

| Command                | Description                           |
| ---------------------- | ------------------------------------- |
| `npm run start:server` | Start the local storage driver        |
| `npm run start:client` | Start the Solana University dashboard |

**Notes**

- You should work entirely within the `build-a-university-certification-nft` directory.
- You can use provided Solana University dashboard (`client/`) to test and play around with your code.
- Useful links to API documentation:
  - [Solana JS SDK](https://solana-labs.github.io/solana-web3.js/)
  - [Metaplex JS SDK](https://github.com/metaplex-foundation/js)

### --tests--

You should deploy the Metaplex Token Metadata program to the local Solana cluster.

```js
const command = `curl http://127.0.0.1:8899 -X POST -H "Content-Type: application/json" -d '
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "BPFLoaderUpgradeab1e11111111111111111111111", {
        "encoding": "base64",
        "dataSlice": {
          "length": 0,
          "offset": 0
        }
      }
    ]
}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.exists(
    jsonOut.result.find(
      r => r.pubkey === 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'
    )
  );
} catch (e) {
  assert.fail(
    e,
    'Try running `solana-test-validator --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./mlp_token.so --reset`'
  );
}
```

The `~/.config/solana/cli/config.yml` file should have the URL set to `localhost`.

```js
const { stdout } = await __helpers.getCommandOutput('solana config get');
const toMatch = 'RPC URL: http://localhost:8899';
assert.include(stdout, toMatch);
```

The validator should be running at `http://127.0.0.1:8899`.

```js
const command = `curl http://127.0.0.1:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

The local storage driver should be running at `http://localhost:3002/`.

```js
try {
  const res = await fetch('http://localhost:3002/status/ping');
  // Response should be 200 with text "pong"
  if (res.status === 200) {
    const text = await res.text();
    if (text !== 'pong') {
      throw new Error(`Expected response text "pong", got ${text}`);
    }
  } else {
    throw new Error(`Expected status code 200, got ${res.status}`);
  }
} catch (e) {
  assert.fail(e);
}
```

You should create a new keypair named `student-1.json`.

```js
const walletPath = join(__projectDir, 'student-1.json');
const walletJsonExists = __helpers.fileExists(walletPath);
assert.isTrue(walletJsonExists, 'The `student-1.json` file should exist');
const walletJson = JSON.parse(await __helpers.getFile(walletPath));
assert.isArray(
  walletJson,
  'The `student-1.json` file should be an array of numbers.\nRun `solana-keygen new --outfile student-1.json` to create a new keypair.'
);
```

You should create a new keypair named `student-2.json`.

```js
const walletPath = join(__projectDir, 'student-2.json');
const walletJsonExists = __helpers.fileExists(walletPath);
assert.isTrue(walletJsonExists, 'The `student-2.json` file should exist');
const walletJson = JSON.parse(await __helpers.getFile(walletPath));
assert.isArray(
  walletJson,
  'The `student-2.json` file should be an array of numbers.\nRun `solana-keygen new --outfile student-2.json` to create a new keypair.'
);
```

You should create a new keypair named `solana-university-wallet.json`.

```js
const walletPath = join(__projectDir, 'solana-university-wallet.json');
const walletJsonExists = __helpers.fileExists(walletPath);
assert.isTrue(
  walletJsonExists,
  'The `solana-university-wallet.json` file should exist'
);
const walletJson = JSON.parse(await __helpers.getFile(walletPath));
assert.isArray(
  walletJson,
  'The `solana-university-wallet.json` file should be an array of numbers.\nRun `solana-keygen new --outfile solana-university-wallet.json` to create a new keypair.'
);
```

The `index.js` file should export a `uploadFile` function.

```js
const { uploadFile } = await __helpers.importSansCache(
  join(__projectDir, 'index.js')
);
assert.isFunction(uploadFile);
```

The `uploadFile` function should match the `index.d.ts` signature definition.

```js
const functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {
  return f.id.name === 'uploadFile';
});
assert.exists(
  functionDeclaration,
  'A function named `uploadFile` should exist'
);

const exports = babelisedCode.getType('ExportNamedDeclaration');
const functionIsExported = exports.some(e => {
  return (
    e.declaration?.id?.name === 'uploadFile' ||
    e.specifiers?.find(s => s.exported.name === 'uploadFile')
  );
});
assert.isTrue(
  functionIsExported,
  'The `uploadFile` function should be exported'
);
```

The `index.js` file should export a `createMintAccount` function.

```js
const { createMintAccount } = await __helpers.importSansCache(
  './' + join(__projectDir, 'index.js')
);
assert.isFunction(createMintAccount);
```

The `createMintAccount` function should match the `index.d.ts` signature definition.

```js
const functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {
  return f.id.name === 'createMintAccount';
});
assert.exists(
  functionDeclaration,
  'A function named `createMintAccount` should exist'
);

const exports = babelisedCode.getType('ExportNamedDeclaration');
const functionIsExported = exports.some(e => {
  return (
    e.declaration?.id?.name === 'createMintAccount' ||
    e.specifiers?.find(s => s.exported.name === 'createMintAccount')
  );
});
assert.isTrue(
  functionIsExported,
  'The `createMintAccount` function should be exported'
);
```

The `createMintAccount` function should create a new mint account for an NFT.

```js
// `payer` should be payer
// `payer` should be mint authority and freeze authority
// The mint should have 0 decimal places
try {
  const { createMintAccount } = await __helpers.importSansCache(
    './' + join(__projectDir, 'index.js')
  );
  const { Keypair, Connection } = await import('@solana/web3.js');
  const { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');

  const connection = new Connection('http://127.0.0.1:8899', 'finalized');
  const payer = Keypair.generate();

  async function airdrop() {
    const airdropSignature = await connection.requestAirdrop(
      payer.publicKey,
      1000000000
    );
    // Confirm transaction
    await connection.confirmTransaction(airdropSignature);
  }
  await airdrop();

  const mint = await createMintAccount({ payer });

  const mintAccounts = await connection.getParsedProgramAccounts(
    TOKEN_PROGRAM_ID,
    {
      filters: [
        {
          dataSize: 82
        },
        {
          memcmp: {
            offset: 4,
            bytes: payer.publicKey.toBase58()
          }
        }
      ]
    }
  );
  const mintAccount = mintAccounts[0];
  assert.exists(mintAccount, 'The mint account should exist');
  assert.equal(
    mintAccount.account.data.parsed.info.mintAuthority,
    payer.publicKey.toBase58(),
    'The mint authority should be the payer'
  );
  assert.equal(
    mintAccount.account.data.parsed.info.freezeAuthority,
    payer.publicKey.toBase58(),
    'The freeze authority should be the payer'
  );
  assert.equal(
    mintAccount.account.data.parsed.info.decimals,
    0,
    'The mint should have 0 decimal places'
  );
} catch (e) {
  assert.fail(e);
}
```

The `createMintAccount` function should return the `PublicKey` of the mint account.

```js
try {
  const { createMintAccount } = await __helpers.importSansCache(
    './' + join(__projectDir, 'index.js')
  );
  const { Keypair, Connection } = await import('@solana/web3.js');
  const { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');

  const connection = new Connection('http://127.0.0.1:8899', 'finalized');

  const payer = Keypair.generate();

  async function airdrop() {
    const airdropSignature = await connection.requestAirdrop(
      payer.publicKey,
      1000000000
    );
    // Confirm transaction
    await connection.confirmTransaction(airdropSignature);
  }
  await airdrop();

  const mint = await createMintAccount({ payer });

  const mintAccounts = await connection.getParsedProgramAccounts(
    TOKEN_PROGRAM_ID,
    {
      filters: [
        {
          dataSize: 82
        },
        {
          memcmp: {
            offset: 4,
            bytes: payer.publicKey.toBase58()
          }
        }
      ]
    }
  );
  const mintAccount = mintAccounts[0];

  assert.equal(
    mintAccount.pubkey.toBase58(),
    mint.toBase58(),
    'The mint account should be returned'
  );
} catch (e) {
  assert.fail(e);
}
```

The `index.js` file should export a `getMintAccounts` function.

```js
const { getMintAccounts } = await __helpers.importSansCache(
  './' + join(__projectDir, 'index.js')
);
assert.isFunction(getMintAccounts);
```

The `getMintAccounts` function should match the `index.d.ts` signature definition.

```js
const functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {
  return f.id.name === 'getMintAccounts';
});
assert.exists(
  functionDeclaration,
  'A function named `getMintAccounts` should exist'
);

const exports = babelisedCode.getType('ExportNamedDeclaration');
const functionIsExported = exports.some(e => {
  return (
    e.declaration?.id?.name === 'getMintAccounts' ||
    e.specifiers?.find(s => s.exported.name === 'getMintAccounts')
  );
});
assert.isTrue(
  functionIsExported,
  'The `getMintAccounts` function should be exported'
);
```

The `getMintAccounts` function should return all mint accounts owned by the `payer` argument.

```js
try {
  const { getMintAccounts, createMintAccount } =
    await __helpers.importSansCache('./' + join(__projectDir, 'index.js'));
  const { Keypair, Connection } = await import('@solana/web3.js');
  const { createMint } = await import('@solana/spl-token');

  const connection = new Connection('http://127.0.0.1:8899', 'finalized');

  const payer = Keypair.generate();

  async function airdrop() {
    const airdropSignature = await connection.requestAirdrop(
      payer.publicKey,
      1000000000
    );
    // Confirm transaction
    await connection.confirmTransaction(airdropSignature);
  }
  await airdrop();

  const mintAuthority = payer.publicKey;
  const freezeAuthority = payer.publicKey;
  const mint = await createMint(
    connection,
    payer,
    mintAuthority,
    freezeAuthority,
    0
  );

  const mintAccounts = await getMintAccounts({ payer });
  assert.isArray(mintAccounts, '`getMintAccounts` should return an array');
  const mintAccount = mintAccounts[0];
  assert.exists(
    mintAccount,
    'This test creates a mint account. At least one account should exist'
  );
  assert.equal(
    mintAccount.pubkey.toBase58(),
    mint.toBase58(),
    'The mint account should match the payer'
  );
} catch (e) {
  assert.fail(e);
}
```

The `index.js` file should export a `createTokenAccount` function.

```js
const { createTokenAccount } = await __helpers.importSansCache(
  './' + join(__projectDir, 'index.js')
);
assert.isFunction(createTokenAccount);
```

The `createTokenAccount` function should match the `index.d.ts` signature definition.

```js
const functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {
  return f.id.name === 'createTokenAccount';
});
assert.exists(
  functionDeclaration,
  'A function named `createTokenAccount` should exist'
);

const exports = babelisedCode.getType('ExportNamedDeclaration');
const functionIsExported = exports.some(e => {
  return (
    e.declaration?.id?.name === 'createTokenAccount' ||
    e.specifiers?.find(s => s.exported.name === 'createTokenAccount')
  );
});
assert.isTrue(
  functionIsExported,
  'The `createTokenAccount` function should be exported'
);
```

The `index.js` file should export a `mintToken` function.

```js
const { mintToken } = await __helpers.importSansCache(
  './' + join(__projectDir, 'index.js')
);
assert.isFunction(mintToken);
```

The `mintToken` function should match the `index.d.ts` signature definition.

```js
const functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {
  return f.id.name === 'mintToken';
});
assert.exists(functionDeclaration, 'A function named `mintToken` should exist');

const exports = babelisedCode.getType('ExportNamedDeclaration');
const functionIsExported = exports.some(e => {
  return (
    e.declaration?.id?.name === 'mintToken' ||
    e.specifiers?.find(s => s.exported.name === 'mintToken')
  );
});
assert.isTrue(
  functionIsExported,
  'The `mintToken` function should be exported'
);
```

The `index.js` file should export a `getNFTs` function.

```js
const { getNFTs } = await __helpers.importSansCache(
  './' + join(__projectDir, 'index.js')
);
assert.isFunction(getNFTs);
```

The `getNFTs` function should match the `index.d.ts` signature definition.

```js
const functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {
  return f.id.name === 'getNFTs';
});
assert.exists(functionDeclaration, 'A function named `getNFTs` should exist');

const exports = babelisedCode.getType('ExportNamedDeclaration');
const functionIsExported = exports.some(e => {
  return (
    e.declaration?.id?.name === 'getNFTs' ||
    e.specifiers?.find(s => s.exported.name === 'getNFTs')
  );
});
assert.isTrue(functionIsExported, 'The `getNFTs` function should be exported');
```

The `getNFTs` function should return all NFTs owned by the `ownerAddress` argument.

```js
try {
  const { Connection, Keypair } = await import('@solana/web3.js');
  const { Metaplex } = await import('@metaplex-foundation/js');
  const { getNFTs } = await __helpers.importSansCache(
    './' + join(__projectDir, 'index.js')
  );

  // Create two NFTs owned by `ownerAddress`
  const connection = new Connection('http://127.0.0.1:8899', 'finalized');
  const payer = Keypair.generate();
  const owner = Keypair.generate();
  const ownerAddress = owner.publicKey;

  async function airdrop(acc) {
    const airdropSignature = await connection.requestAirdrop(
      acc.publicKey,
      1000000000
    );
    // Confirm transaction
    await connection.confirmTransaction(airdropSignature);
  }

  await airdrop(payer);
  await airdrop(owner);

  const metaplex = Metaplex.make(connection);

  const createResponse = await metaplex.nfts().create(
    {
      tokenOwner: ownerAddress,
      uri: 'http://localhost:1213',
      name: `Test`,
      sellerFeeBasisPoints: 0,
      maxSupply: 1,
      symbol: 'fCCTest',
      isMutable: false,
      updateAuthority: payer,
      mintAuthority: payer
    },
    { payer }
  );

  // Call `getNFTs`

  const nfts = await getNFTs({ ownerAddress, payer });
  assert.isArray(nfts, '`getNFTs` should return an array');
  assert.equal(
    nfts.length,
    1,
    'The `getNFTs` function should return all NFTs owned by the `ownerAddress` argument'
  );

  const createResponse2 = await metaplex.nfts().create(
    {
      tokenOwner: ownerAddress,
      uri: 'http://localhost:1213',
      name: `Test2`,
      sellerFeeBasisPoints: 0,
      maxSupply: 1,
      symbol: 'fCCTest',
      isMutable: false,
      updateAuthority: payer,
      mintAuthority: payer
    },
    { payer }
  );

  const nfts2 = await getNFTs({ ownerAddress, payer });
  assert.isArray(nfts2, '`getNFTs` should return an array');
  assert.equal(
    nfts2.length,
    2,
    'The `getNFTs` function should return all NFTs owned by the `ownerAddress` argument'
  );
} catch (e) {
  assert.fail(e);
}
```

### --before-all--

```js
const __projectDir = 'build-a-university-certification-nft';
const codeString = await __helpers.getFile(
  './' + join(__projectDir, 'index.js')
);
const babelisedCode = new __helpers.Babeliser(codeString);

global.__projectDir = __projectDir;
global.babelisedCode = babelisedCode;
```

### --after-all--

```js
delete global.__projectDir;
delete global.babelisedCode;
```

## --fcc-end--


================================================
FILE: curriculum/locales/english/build-an-anchor-leaderboard.md
================================================
# Solana - Build an Anchor Leaderboard

## 1

### --description--

You are developing an on-chain game called _Rock Destroyer_. You will be writing the program logic for the game leaderboard using the Anchor framework, as well as writing tests to ensure the program logic is correct.

You will be working entirely within the `build-an-anchor-leaderboard/rock-destroyer` directory. The `rock-destroyer` directory is an Anchor boilerplate project with a front-end already set up.

### User Stories

#### Setup

1. You should generate a new keypair and store it in a file called `game-owner.json`.
2. You should add the correct program id to the `programs.localnet.rock_destroyer` key in the `Anchor.toml` file.

#### Program

1. You should add the correct program id to the `declare_id!` call in the `lib.rs` file.

**`initialize_leaderboard`**

1. The `rock_destroyer` program should expose an `initialize_leaderboard` instruction handler.
2. The `initialize_leaderboard` instruction handler should take a context generic over an `InitializeLeaderboard` accounts struct.
3. The `initialize_leaderboard` instruction handler should initialize the `leaderboard` account with the `players` field set to an empty vector.

**`InitializeLeaderboard`**

1. The `leaderboard` account should be initialized, if it does not already exist.
   - This should be payed for by the `game_owner` account
   - The correct amount of space for 5 players should be allocated
   - The PDA should be seeded with `"leaderboard"` and the `game_owner` public key
2. The `game_owner` account should be a signer.
   - The following constraints should be enforced:
     - The account should be mutable
     - The account public key should match the `game-owner.json` file public key
     - The account owner should be the system program

**`new_game`**

1. The `rock_destroyer` program should expose a `new_game` instruction handler.
2. The `new_game` instruction handler should take a context generic over a `NewGame` accounts struct.
3. The `new_game` instruction handler should take a `String` argument.
4. The `new_game` instruction handler should transfer 1 SOL from the `user` account to the `game_owner` account.[^1]
5. The `new_game` instruction handler should add a new `Player` to the leaderboard with:
   - `username` set to the `String` argument
   - `pubkey` set to the `user` account public key
   - `score` set to `0`
   - `has_payed` set to `true`
6. If the leaderboard is full, the player with the lowest score should be replaced.

**`NewGame`**

1. The `user` account should be a signer.
   - The following constraints should be enforced:
     - The account should be mutable
2. The `game_owner` account should be an unchecked account.
   - The following constraints should be enforced:
     - The account should be mutable
     - The account public key should match the `game-owner.json` file public key
     - The account owner should be the system program
3. The `leaderboard` account should be mutable.

**`add_player_to_leaderboard`**

1. The `rock_destroyer` program should expose an `add_player_to_leaderboard` instruction handler.
2. The `add_player_to_leaderboard` instruction handler should take a context generic over an `AddPlayerToLeaderboard` accounts struct.
3. The `add_player_to_leaderboard` instruction handler should take a `u64` argument.
4. The player matching the user account public key should be updated with:
   - `score` set to the `u64` argument
   - `has_payed` set to `false`
5. If no player matching the user account public key exists and has payed, an Anchor error variant of `PlayerNotFound` should be returned.

**`AddPlayerToLeaderboard`**

1. The `leaderboard` account should be mutable.
2. The `user` account should be a signer.
   - The following constraints should be enforced:
     - The account should be mutable

#### Tests

1. There should be an `it` block named `"initializes leaderboard"`.

- Call the `initialize_leaderboard` instruction
- Assert the `leaderboard` account equals `{ players: [] }`

2. There should be an `it` block named `"creates a new game"`.

- Call the `new_game` instruction with a `username` argument of `"camperbot"`
- Assert the `leaderboard` account has at least one player
- Assert the player has the correct `username`
- Assert the player has the correct `pubkey`
- Assert the player has a `hasPayed` value of `true`
- Assert the player has a `score` value of `0`
- Assert the balance of the `user` account has decreased by at least 1 SOL (_remember transaction fees_)

3. There should be an `it` block named `"adds a player to the leaderboard"`.

- Call the `add_player_to_leaderboard` instruction with an argument of `100`
- Assert a player has a `score` value of `100`
- Assert a player has a `hasPayed` value of `false`

4. There should be an `it` block named `"

- Assert the `PlayerNotFound` error variant is returned when the `user` account has not payed

#### Types

<details>
  <summary><code>InitializeLeaderboard</code></summary>

```rust
leaderboard: Account<'info, Leaderboard>,
game_owner: Signer<'info>,
system_program: Program<'info, System>,
```

</details>

<details>
  <summary><code>NewGame</code></summary>

```rust
user: Signer<'info>,
game_owner: AccountInfo<'info>,
leaderboard: Account<'info, Leaderboard>,
system_program: Program<'info, System>,
```

</details>

<details>
  <summary><code>AddPlayerToLeaderboard</code></summary>

```rust
leaderboard: Account<'info, Leaderboard>,
user: Signer<'info>,
```

</details>

<details>
  <summary><code>Leaderboard</code></summary>

```rust
players: Vec<Player>
```

</details>

<details>
  <summary><code>Player</code></summary>

```rust
username: String, // max length 32
pubkey: Pubkey,
score: u64,
has_payed: bool,
```

</details>

### Notes

- You should not add any external dependencies to the `package.json` file for the tests
  - You have access to `chai`
- Many tests rely on previous user stories being correctly implemented

[^1]: Hint: You can use the `transfer` function from the `system_instruction` module in the `solana_program` crate.

### --tests--

You should generate a new keypair and store it in a file called `game-owner.json`.

```js
try {
  const fileExists = await __fsp.access(
    __path.join(__projectDir, './game-owner.json'),
    __fsp.constants.F_OK
  );
} catch (e) {
  assert.fail(e);
}
```

You should add the correct program id to the `programs.localnet.rock_destroyer` key in the `Anchor.toml` file.

```js
const anchorToml = await __fsp.readFile(
  __path.join(__projectDir, 'Anchor.toml'),
  'utf-8'
);
const actualProgramId = anchorToml.match(/rock_destroyer = "(.*)"/)?.[1];
const { stdout } = await __helpers.getCommandOutput(
  'anchor keys list',
  __projectDir
);
const expectedProgramId = stdout.match(/rock_destroyer: (.*)/)?.[1];
assert.equal(actualProgramId, expectedProgramId);
```

You should add the correct program id to the `declare_id!` call in the `lib.rs` file.

```js
const librs = await __fsp.readFile(
  __path.join(__projectDir, 'programs/rock-destroyer/src/lib.rs'),
  'utf-8'
);
const actualProgramId = librs.match(/declare_id\!\((.*)\)/)?.[1];
const { stdout } = await __helpers.getCommandOutput(
  'anchor keys list',
  __projectDir
);
const expectedProgramId = stdout.match(/rock_destroyer: (.*)/)?.[1];
assert.equal(actualProgramId.replaceAll(/['"`]/g), expectedProgramId);
```

The `rock_destroyer` program should expose an `initialize_leaderboard` instruction handler.

```js
const testDir = await __createTestDir(4);
await __buildTestDir(4);
const { RockDestroyer } = await __helpers.importSansCache(
  __path.join(testDir, 'target/types/rock_destroyer')
);
const ixs = RockDestroyer.instructions;
const initializeLeaderboardIx = ixs.find(
  ix => ix.name === 'initializeLeaderboard'
);
assert.exists(
  initializeLeaderboardIx,
  'The `RockDestroyer` object in `target/types/rock_destroyer` should have an `instructions[].name` property equal to `initializeLeaderboard`'
);
```

The `initialize_leaderboard` instruction handler should take a context generic over an `InitializeLeaderboard` accounts struct.

```js
const testDir = await __createTestDir(5);
await __buildTestDir(5);
const { RockDestroyer } = await __helpers.importSansCache(
  __path.join(testDir, 'target/types/rock_destroyer')
);
const ixs = RockDestroyer.instructions;
const initializeLeaderboardIx = ixs.find(
  ix => ix.name === 'initializeLeaderboard'
);
const accounts = initializeLeaderboardIx.accounts;
assert.deepInclude(accounts, {
  name: 'leaderboard',
  isMut: true,
  isSigner: false
});
assert.deepInclude(accounts, {
  name: 'gameOwner',
  isMut: true,
  isSigner: true
});
assert.deepInclude(accounts, {
  name: 'systemProgram',
  isMut: false,
  isSigner: false
});
```

The `rock_destroyer` program should expose a `new_game` instruction handler.

```js
const testDir = await __createTestDir(4);
await __buildTestDir(4);
const { RockDestroyer } = await __helpers.importSansCache(
  __path.join(testDir, 'target/types/rock_destroyer')
);
const ixs = RockDestroyer.instructions;
const newGameIx = ixs.find(ix => ix.name === 'newGame');
assert.exists(
  newGameIx,
  'The `RockDestroyer` object in `target/types/rock_destroyer` should have an `instructions[].name` property equal to `newGame`'
);
```

The `new_game` instruction handler should take a context generic over a `NewGame` accounts struct.

```js
const testDir = await __createTestDir(6);
await __buildTestDir(6);
const { RockDestroyer } = await __helpers.importSansCache(
  __path.join(testDir, 'target/types/rock_destroyer')
);
const ixs = RockDestroyer.instructions;
const newGameIx = ixs.find(ix => ix.name === 'newGame');
const accounts = newGameIx.accounts;
assert.deepInclude(accounts, {
  name: 'user',
  isMut: true,
  isSigner: true
});
assert.deepInclude(accounts, {
  name: 'leaderboard',
  isMut: true,
  isSigner: false
});
assert.deepInclude(accounts, {
  name: 'gameOwner',
  isMut: true,
  isSigner: false
});
assert.deepInclude(accounts, {
  name: 'systemProgram',
  isMut: false,
  isSigner: false
});
```

The `rock_destroyer` program should expose an `add_player_to_leaderboard` instruction handler.

```js
const testDir = await __createTestDir(4);
await __buildTestDir(4);
const { RockDestroyer } = await __helpers.importSansCache(
  __path.join(testDir, 'target/types/rock_destroyer')
);
const ixs = RockDestroyer.instructions;
const initializeLeaderboardIx = ixs.find(
  ix => ix.name === 'addPlayerToLeaderboard'
);
assert.exists(
  initializeLeaderboardIx,
  'The `RockDestroyer` object in `target/types/rock_destroyer` should have an `instructions[].name` property equal to `addPlayerToLeaderboard`'
);
```

The `add_player_to_leaderboard` instruction handler should take a context generic over an `AddPlayerToLeaderboard` accounts struct.

```js
const testDir = await __createTestDir(7);
await __buildTestDir(7);
const { RockDestroyer } = await __helpers.importSansCache(
  __path.join(testDir, 'target/types/rock_destroyer')
);
const ixs = RockDestroyer.instructions;
const ix = ixs.find(ix => ix.name === 'addPlayerToLeaderboard');
const accounts = ix.accounts;
assert.deepInclude(accounts, {
  name: 'leaderboard',
  isMut: true,
  isSigner: false
});
assert.deepInclude(accounts, {
  name: 'user',
  isMut: true,
  isSigner: true
});
```

There should be an `it` block named `"initializes leaderboard"`.

```js
const callExpressions = babelisedCode
  .getType('CallExpression')
  .filter(c => {
    return;
    c.callee?.name === 'it';
  })
  .map(c => c.arguments?.[1]?.value);
assert.include(callExpressions, 'initializes leaderboard');
```

There should be an `it` block named `"creates a new game"`.

```js
const callExpressions = babelisedCode
  .getType('CallExpression')
  .filter(c => {
    return;
    c.callee?.name === 'it';
  })
  .map(c => c.arguments?.[1]?.value);
assert.include(callExpressions, 'creates a new game');
```

There should be an `it` block named `"adds a player to the leaderboard"`.

```js
const callExpressions = babelisedCode
  .getType('CallExpression')
  .filter(c => {
    return;
    c.callee?.name === 'it';
  })
  .map(c => c.arguments?.[1]?.value);
assert.include(callExpressions, 'adds a player to the leaderboard');
```

There should be an `it` block named `"throws an error when the user has not payed"`.

```js
const callExpressions = babelisedCode
  .getType('CallExpression')
  .filter(c => {
    return;
    c.callee?.name === 'it';
  })
  .map(c => c.arguments?.[1]?.value);
assert.include(callExpressions, 'throws an error when the user has not payed');
```

### --before-all--

```js
const __fsp = await import('fs/promises');
const __path = await import('path');
const __projectDir = 'build-an-anchor-leaderboard/_answer/rock-destroyer';
const __testDir = 'build-an-anchor-leaderboard/__test/rock-destroyer';
const codeString = await __helpers.getFile(
  './' + join(__projectDir, 'tests/index.ts')
);
const babelisedCode = new __helpers.Babeliser(codeString, {
  plugins: ['typescript']
});

async function __createTestDir(num) {
  const testDir = `${__testDir}-${num}`;
  // Remove old test dir
  logover.debug('Removing old test dir');
  await __fsp.rm(testDir, { recursive: true, force: true });
  // Create new test dir
  logover.debug('Creating new test dir');
  await __fsp.cp(__projectDir, testDir, { recursive: true });
  return testDir;
}

async function __buildTestDir(num) {
  const { stdout, stderr } = await __helpers.getCommandOutput(
    'anchor build',
    `${__testDir}-${num}`
  );
  if (stderr) {
    throw new Error(stderr);
  }
  return stdout;
}

global.__projectDir = __projectDir;
global.__testDir = __testDir;
global.__fsp = __fsp;
global.__path = __path;
global.__buildTestDir = __buildTestDir;
global.__createTestDir = __createTestDir;
global.babelisedCode = babelisedCode;
```

### --after-all--

```js
delete global.__projectDir;
delete global.__testDir;
delete global.__fsp;
delete global.__path;
delete global.babelisedCode;

delete global.__buildTestDir;
delete global.__createTestDir;
```

## --fcc-end--


================================================
FILE: curriculum/locales/english/build-and-deploy-your-freeform-app.md
================================================
# Solana - Build and Deploy Your Freeform App

## 1

### --description--

Congratulations on making it to the final project for this course! 🚀 🧨 🌟

You have learnt a lot about the Solana ecosystem, and how to build on it. Now, it is time for you to test your skills and build an app of your own. Once you are done, feel free to share a link to it on Twitter, and `@freeCodeCamp`.

You can use whatever tools you want to build your app, and you are encouraged to explore the library ecosystem to find the best tools for the job.

- You can use <https://beta.solpg.io/> to develop most of your dApp in your browser.

### Concepts Covered

- Accounts
  - Program (executable) accounts
  - State (data) accounts
  - Serialization and Deserialization
- Rent
- Tokens
  - Fungible tokens
  - Non-fungible tokens
  - Creating and Minting
  - Metadata
- Transactions
  - Instructions
  - Signatures

### Tools Covered

- Solana CLI
- `@solana/web3.js`
- `@solana/spl-token`
- Anchor CLI
- `@coral-xyz/anchor`
- `@metaplex-foundation/js`

### Project Ideas

- A Twitter clone
- A blogging platform
- An NFT marketplace
- A decentralized exchange
- A CLI game

### --tests--

Once you are done, enter `done` in the terminal.

```js
const lastCommand = await __helpers.getLastCommand();
assert.include(lastCommand, 'done');
```

## --fcc-end--


================================================
FILE: curriculum/locales/english/learn-anchor-by-building-tic-tac-toe-part-1.md
================================================
# Solana - Learn Anchor by Building Tic-Tac-Toe: Part 1

## 1

### --description--

Previously, you built and deployed a program (smart contract) to the Solana blockchain using the native `solana_program` crate. In this project, you will use the Anchor framework to build and deploy a program to the Solana blockchain.

Anchor offers quick and convenient tools and modules for building and deploying programs.

Open a new terminal, and install the Anchor Version Manager (AVM) to get started.

```bash
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
```

### --tests--

You should have `avm` installed.

```js
const { stdout } = await __helpers.getCommandOutput('avm --version');
assert.include(stdout, 'avm');
```

## 2

### --description--

Anchor Version Manager is a tool for using multiple versions of the Anchor CLI.

Use `avm` to install the Anchor CLI.

```bash
avm install 0.28.0
```

### --tests--

You should have version `0.28.0` of the Anchor CLI installed.

```js
const { stdout } = await __helpers.getCommandOutput('avm list');
assert.include(stdout, 'installed, current)');
```

## 3

### --description--

Instruct `avm` to use the latest version of the Anchor CLI:

```bash
avm use 0.28.0
```

### --tests--

You should be using version `0.28.0` of the Anchor CLI.

```js
const { stdout } = await __helpers.getCommandOutput('avm list');
assert.include(stdout, 'installed, current)');
```

## 4

### --description--

Verify you are using the latest version of the Anchor CLI:

```bash
anchor --version
```

### --tests--

You should see version `0.28` printed to the console.

```js
const terminalOut = await __helpers.getTerminalOutput();
assert.include(terminalOut, '0.28');
```

## 5

### --description--

You will be building a Tic-Tac-Toe game on the Solana blockchain.

Within `learn-anchor-by-building-tic-tac-toe-part-1/`, create a new project named `tic-tac-toe`:

```bash
anchor init --no-git tic-tac-toe
```

**Note:** The `--no-git` flag is used to prevent the project from being initialized as a git repository.

### --tests--

You should be in the `learn-anchor-by-building-tic-tac-toe-part-1` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/?$`);
assert.match(cwd, dirRegex);
```

You should run `anchor init --no-git tic-tac-toe` in the terminal.

```js
const lastCommand = await __helpers.getLastCommand();
assert.equal(lastCommand.trim(), 'anchor init --no-git tic-tac-toe');
```

You should have a `tic-tac-toe` directory.

```js
const exists = __helpers.fileExists(`${project.dashedName}/tic-tac-toe`);
assert.isTrue(exists);
```

## 6

### --description--

Anchor has created a `tic-tac-toe` directory with the following structure:

```bash
tic-tac-toe/
├── app/
├── migrations/
│   └── deploy.ts
├── programs/
│   └── tic-tac-toe/
│       ├── src/
│       │   └── lib.rs
│       ├── Cargo.toml
│       └── Xargo.toml
├── tests/
│   └── tic-tac-toe.ts
├── Anchor.toml
├── Cargo.toml
├── package.json
├── tsconfig.json
└── yarn.lock
```

In your terminal, change into the `tic-tac-toe` directory.

### --tests--

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

## 7

### --description--

The `app` directory is a placeholder for a web app that would interact with the program. The `migrations/deploy.ts` script is run on `anchor migrate`. The `programs` directory contains all the programs (smart contracts) that will be deployed to the Solana blockchain. The `tests` directory contains the client-side tests for your programs.

You will be mostly working in `programs/tic_tac_toe/src/lib.rs`.

Get the program id (public key) of the `tic-tac-toe` program:

```bash
anchor keys list
```

### --tests--

The public key of your `tic-tac-toe` program should be printed to the console.

```js
const { stdout } = await __helpers.getCommandOutput(
  'anchor keys list',
  `${project.dashedName}/tic-tac-toe`
);
const publicKey = stdout.match(/[^\s]{44}/)[0];

const terminalOut = await __helpers.getTerminalOutput();
assert.include(terminalOut, publicKey);
```

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

## 8

### --description--

The Anchor CLI provides an `anchor test` command that:

1. Builds all programs
2. Starts a local Solana cluster
3. Deploys the programs to the cluster
4. Calls the `test` script in the `scripts` table in `Anchor.toml`
5. Cleans up the local Solana cluster

Run the `anchor test` command:

```bash
anchor test
```

### --tests--

The `anchor test` command should error ❌.

```js
const terminalOut = await __helpers.getTerminalOutput();
assert.include(terminalOut, 'Error: failed to send transaction');
```

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

## 9

### --description--

You should see the following error:

```bash
Error: failed to send transaction: Transaction simulation failed: This program may not be used for executing instructions
```

For your program, you will need to manually start a local Solana validator. Do so, in a new terminal.

### --tests--

The validator should be running at `http://localhost:8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

## 10

### --description--

With the local validator running, pass the `--skip-local-validator` flag to tell Anchor to not start its own local validator when running tests.

### --tests--

The `anchor test --skip-local-validator` command should error.

```js
const terminalOut = await __helpers.getTerminalOutput();
assert.include(terminalOut, 'Error: AnchorError occurred.');
```

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

The validator should be running at `http://localhost:8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

## 11

### --description--

You should see the following error:

```bash
Error: AnchorError occurred. Error Code: DeclaredProgramIdMismatch. Error Number: 4100. Error Message: The declared program id does not match the actual program id.
```

Double-check the program id in the `Anchor.toml` file.

Run `anchor keys list` again to see if it matches the `programs.localnet.tic_tac_toe` value.

### --tests--

You should run `anchor keys list` and see the program id printed to the console.

```js
const { stdout } = await __helpers.getCommandOutput(
  'anchor keys list',
  `${project.dashedName}/tic-tac-toe`
);
const publicKey = stdout.match(/[^\s]{44}/)?.[0];

const terminalOut = await __helpers.getTerminalOutput();
assert.include(terminalOut, publicKey);
```

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

## 12

### --description--

Copy the program id, and replace the default values with it in two locations:

1. The string value within the `declare_id` macro in `programs/tic-tac-toe/src/lib.rs`
2. The `tic_tac_toe` key within `Anchor.toml`

### --tests--

The `lib.rs` file should contain the program id within the `declare_id!()` call.

```js
const librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
const { stdout } = await __helpers.getCommandOutput(
  'anchor keys list',
  `${project.dashedName}/tic-tac-toe`
);
const expectedProgramId = stdout.match(/[^\s]{44}/)?.[0];
const actualProgramId = librs.match(/declare_id!\("([^\)]+)"\)/)?.[1];
assert.equal(actualProgramId, expectedProgramId);
```

The `Anchor.toml` file should contain the program id as the value for the `tic_tac_toe` key.

```js
const toml = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/Anchor.toml`
);
const { stdout } = await __helpers.getCommandOutput(
  'anchor keys list',
  `${project.dashedName}/tic-tac-toe`
);
const expectedProgramId = stdout.match(/[^\s]{44}/)?.[0];
const actualProgramId = toml.match(/tic_tac_toe = "([^\"]+)"/)?.[1];
assert.equal(actualProgramId, expectedProgramId);
```

## 13

### --description--

Run the test command again:

```bash
anchor test --skip-local-validator
```

### --tests--

The `anchor test --skip-local-validator` command should succeed.

```js
const terminalOut = await __helpers.getTerminalOutput();
assert.include(terminalOut, '1 passing');
```

The validator should be running at `http://localhost:8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

## 14

### --description--

Shifting focus to the `lib.rs` file, you will see a few similarities to the native Solana program development workflow.

Instead of an entrypoint function, the `program` attribute defines the module containing all instruction handlers defining all entries into a Solana program.

The `initialize` function is an instruction handler. It is a function that takes a `Context` as an argument. The context contains the program id, and the accounts passed into the function. Anchor expects all accounts to be fully declared as inputs to the handler.

Rename the `initialize` function to `setup_game`.

### --tests--

The `setup_game` function should exist in the `lib.rs` file.

```js
assert.match(__librs, /fn setup_game/);
```

The `initialize` function should not exist in the `lib.rs` file.

```js
assert.notMatch(__librs, /fn initialize/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 15

### --description--

The `Initialize` struct is annotated to derive `Accounts`. This means that the `Initialize` struct will be used to deserialize the accounts passed into the `setup_game` function. If a client does not pass in the correct accounts, the deserialization will fail. This is one of the ways that Anchor ensures that the client is passing in the correct accounts.

Rename the `Initialize` struct to `SetupGame`.

### --tests--

The `SetupGame` struct should exist in the `lib.rs` file.

```js
assert.match(__librs, /struct SetupGame/);
```

The `setup_game` function should take a `Context<SetupGame>` as an argument.

```js
assert.match(__librs, /pub fn setup_game/);
assert.match(__librs, /ctx: Context<SetupGame>/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 16

### --description--

Setting up the game will require creating an account to store the game state. This account will be owned by the system program, and will be created with a program derived address (PDA).

You could manually calculate the rent required, validate a passed in account can pay, create the account, and send the create transaction. However, Anchor provides a convenient attribute macro to automate this process:

```rust
#[derive(Accounts)]
pub struct AccountsInContext<'info> {
    #[account(init)]
    pub derived_account: Account<'info, AccountStruct>
}
```

The `#[account()]` attribute with an `init` parameter will create the account when required. The `AccountStruct` is a struct that will be used to deserialize the account data. The `AccountStruct` must implement the `AnchorSerialize` and `AnchorDeserialize` traits.

Within `SetupGame`, add a public field `game` with a type of `Account<'info, Game>`. Annotate the field such that it is initialized when required.

### --tests--

`SetupGame` should contain a field `game`.

```js
assert.match(__librs, /pub game:/);
```

`game` should be typed `Account<'info, Game>`.

```js
assert.match(__librs, /pub game: Account<'info\s*,\s*Game>/);
```

`game` should be annotated with `#[account(init)]`.

```js
assert.match(__librs, /#\[\s*account\s*\(\s*init\s*\)\s*\]\s*pub game:/);
```

`SetupGame` should be punctuated with a lifetime `'info`.

```js
assert.match(__librs, /pub struct SetupGame\s*<'info>/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 17

### --description--

When an account is initialized, another account must pay for the rent and transaction fees.

Declare another account in `SetupGame` called `player_one`. Give `player_one` a type of `Signer<'info>`.

**Note:** The `Signer` trait is a special trait that indicates the account is a signer. This is required for lamports to be transferred **from** the account.

### --tests--

`SetupGame` should contain a field `player_one`.

```js
assert.match(__librs, /pub player_one:/);
```

`player_one` should be typed `Signer<'info>`.

```js
assert.match(__librs, /pub player_one: Signer\s*<'info>/);
```

`player_one` should be annotated with `#[account()]`.

```js
assert.match(__librs, /#\[\s*account\s*\(\s*\)\s*\]\s*pub player_one:/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 18

### --description--

Now, mark the `player_one` account as the payer for the `game` account:

```rust
#[derive(Accounts)]
pub struct AccountsInContext<'info> {
    #[account(
        init,
        payer = payer_account
    )]
    pub derived_account: Account<'info, AccountStruct>,
    #[account()]
    pub payer_account: Signer<'info>
}
```

### --tests--

`game` should be annotated with `payer = player_one`.

```js
const librs = (
  await __helpers.getFile(
    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
  )
)?.replaceAll(/[ \t]{2,}/g, ' ');
assert.match(
  librs,
  /#\[\s*account\s*\(\s*init\s*,\s*payer\s*=\s*player_one\s*\)\s*\]\s*pub game:/
);
```

## 19

### --description--

In order for any data in an account to be changed, the account must be mutable:

```rust
#[account(mut)]
pub mutable_account: Signer<'info>
```

Mark the `player_one` account as mutable.

### --tests--

`player_one` should be annotated with `#[account(mut)]`.

```js
const librs = (
  await __helpers.getFile(
    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
  )
)?.replaceAll(/[ \t]{2,}/g, ' ');
assert.match(librs, /#\[\s*account\s*\(\s*mut\s*\)\s*\]\s*pub player_one:/);
```

## 20

### --description--

The `#[account(init)]` attribute will create the account when required. However, the account must be rent exempt, and have enough space to store any data expected:

```rust
#[account(init, space = <AMOUNT_IN_BYTES>)]
pub derived_account: Account<'info, AccountStruct>
```

Add a `space` parameter to the `game` account with a value of `10`. This means the account will be initialised with 10 bytes of space.

### --tests--

`game` should be annotated with `space = 10`.

```js
const librs = (
  await __helpers.getFile(
    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
  )
)?.replaceAll(/[ \t]{2,}/g, ' ');
assert.match(
  librs,
  /#\[\s*account\s*\([^\)]*?space\s*=\s*10[^\)]*?\)]\s*pub game:/
);
```

## 21

### --description--

Creating an account with `10` bytes allocated is great and all, but you do not actually know how much space you need until you have defined the `Game` struct representing the account data.

Define a public struct `Game`, and annotate it with `#[account]` to indicate it is a Solana account.

### --tests--

`pub struct Game` should exist in the `lib.rs` file.

```js
assert.match(__librs, /pub struct Game/);
```

`Game` should be annotated with `#[account]`.

```js
assert.match(__librs, /#\[\s*account\s*\]\s*pub struct Game/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 22

### --description--

A game of tic-tac-toe consists of two players.

Keep track of the players, by adding a `players` field in `Game` with a type of `[Pubkey; 2]`.

### --tests--

`Game` should contain a field `players`.

```js
const game = __librs.match(/pub struct Game\s*{([^}]*)}/s)?.[1];
assert.match(game, /players:/);
```

`players` should be typed `[Pubkey; 2]`.

```js
assert.match(__librs, /players: \[\s*Pubkey\s*;\s*2\s*\]/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 23

### --description--

Keep track of the current turn number, by adding a `turn` field in `Game` with a type of `u8`.

### --tests--

`Game` should contain a field `turn`.

```js
const game = __librs.match(/pub struct Game\s*{([^}]*)}/s)?.[1];
assert.match(game, /turn:/);
```

`turn` should be typed `u8`.

```js
assert.match(__librs, /turn: u8/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 24

### --description--

Keep track of the board state (the value of each tile), by adding a `board` field in `Game` with a type of `[[Option<Sign>; 3]; 3]`.

### --tests--

`Game` should contain a field `board`.

```js
const game = __librs.match(/pub struct Game\s*{([^}]*)}/s)?.[1];
assert.match(game, /board:/);
```

`board` should be typed `[[Option<Sign>; 3]; 3]`.

```js
assert.match(
  __librs,
  /board: \[\[\s*Option\s*<\s*Sign\s*>\s*;\s*3\s*\]\s*;\s*3\s*\]/
);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 25

### --description--

Keep track of the current game condition, by adding a `state` field in `Game` with a type of `GameState`.

### --tests--

`Game` should contain a field `state`.

```js
const game = __librs.match(/pub struct Game\s*{([^}]*)}/s)?.[1];
assert.match(game, /state:/);
```

`state` should be typed `GameState`.

```js
assert.match(__librs, /state: GameState/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 26

### --description--

Define a public enum `Sign` with variants `X` and `O`.

### --tests--

`pub enum Sign` should exist in the `lib.rs` file.

```js
assert.match(__librs, /pub enum Sign/);
```

`Sign` should have a variant `X`.

```js
const sign = __librs.match(/pub enum Sign\s*{([^}]*)}/s)?.[1];
assert.match(sign, /X/);
```

`Sign` should have a variant `O`.

```js
const sign = __librs.match(/pub enum Sign\s*{([^}]*)}/s)?.[1];
assert.match(sign, /O/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 27

### --description--

Define a public enum `GameState` with variants `Active`, `Tie`, and `Won`. The `Won` variant should contain a named field `winner` with a type of `Pubkey`.

### --tests--

`pub enum GameState` should exist in the `lib.rs` file.

```js
assert.match(__librs, /pub enum GameState/);
```

`GameState` should have a variant `Active`.

```js
const gameState = __librs.match(/pub enum GameState\s*{([^}]*)}/s)?.[1];
assert.match(gameState, /Active/);
```

`GameState` should have a variant `Tie`.

```js
const gameState = __librs.match(/pub enum GameState\s*{([^}]*)}/s)?.[1];
assert.match(gameState, /Tie/);
```

`GameState` should have a variant `Won { winner: Pubkey }`.

```js
const gameState = __librs.match(
  /pub enum GameState ({[\s\S]*?({[\s\S]*?}[\s\S]*?}|}))/s
)?.[1];
assert.match(gameState, /Won\s*{\s*winner:\s*Pubkey\s*,?\s*}/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 28

### --description--

In order for Anchor to serialize and deserialize the `Game` account data, `GameState` and `Sign` must implement the `AnchorSerialize` and `AnchorDeserialize` traits.

Derive the `AnchorSerialize` and `AnchorDeserialize` traits for `GameState` and `Sign`.

### --tests--

`GameState` should be annotated with `#[derive(AnchorSerialize, AnchorDeserialize)]`.

```js
assert.match(
  __librs,
  /#\[\s*derive\s*\(\s*AnchorSerialize\s*,\s*AnchorDeserialize\s*\)\s*\]\s*pub enum GameState/
);
```

`Sign` should be annotated with `#[derive(AnchorSerialize, AnchorDeserialize)]`.

```js
assert.match(
  __librs,
  /#\[\s*derive\s*\(\s*AnchorSerialize\s*,\s*AnchorDeserialize\s*\)\s*\]\s*pub enum Sign/
);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 29

### --description--

On top of `AnchorSerialize` and `AnchorDeserialize`, both `GameState` and `Sign` must also implement the `Clone` trait.

Derive the `Clone` trait for `GameState` and `Sign`.

### --tests--

`GameState` should be annotated with `#[derive(Clone)]`.

```js
assert.match(
  __librs,
  /#\[\s*derive\s*\([^\]]*?Clone[^\]]*?\)\s*\]\s*pub enum GameState/
);
```

`Sign` should be annotated with `#[derive(Clone)]`.

```js
assert.match(
  __librs,
  /#\[\s*derive\s*\([^\]]*?Clone[^\]]*?\)\s*\]\s*pub enum Sign/
);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 30

### --description--

Finally, because `Sign` is within a slice, it must also implement the `Copy` trait.

Derive the `Copy` trait for `Sign`.

### --tests--

`Sign` should be annotated with `#[derive(Copy)]`.

```js
const librs = (
  await __helpers.getFile(
    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
  )
)?.replaceAll(/[ \t]{2,}/g, ' ');
assert.match(
  librs,
  /#\[\s*derive\s*\([^\]]*?Copy[^\]]*?\)\s*\]\s*pub enum Sign/
);
```

## 31

### --description--

In order for an account to be created, the `System` program must be used. The `System` program is a built-in program that is available to all Solana programs, but must be annotated as needed in the context.

Add a public `system_program` field to the `SetupGame` struct, and type it as `Program<'info, System>`.

### --tests--

`SetupGame` should contain a field `system_program`.

```js
const setupGame = __librs.match(
  /pub struct SetupGame\s*<'info\s*>\s*{([^}]*?)}/s
)?.[1];
assert.match(setupGame, /system_program:/);
```

`system_program` should be typed `Program<'info, System>`.

```js
const setupGame = __librs.match(
  /pub struct SetupGame\s*<'info\s*>\s*{([^}]*)}/s
)?.[1];
assert.match(setupGame, /system_program: Program\s*<\s*'info\s*,\s*System\s*>/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 32

### --description--

Now that `Game` is fully defined, its size can be determined. Doing so involves summing the size of each field, and adding 8 bytes for the account <dfn title="a uniquely identifiable octet to help Anchor find an account">discriminator</dfn>.

For `Game`:

| Field   | Unit Size | Quantity | Total Size |
| ------- | --------- | -------- | ---------- |
| players | 32        | 2        | 64         |
| turn    | 1         | 1        | 1          |
| board   | 1 + 1     | 3 \* 3   | 18         |
| state   | 1 + 32    | 1        | 33         |

Anchor provides a table of sizes for each Rust type: `https://www.anchor-lang.com/docs/space`

Replace the `10` bytes allocated for the `Game` account with the correct size.

### --tests--

The `game` field in `SetupGame` should be annotated with `#[account(space = 8 + (32*2) + (1) + ((1+1)*(3*3)) + (1+32))]`.

```js
const librs = (
  await __helpers.getFile(
    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
  )
)?.replaceAll(/[ \t]{2,}/g, ' ');

const setupGame = librs.match(
  /pub struct SetupGame\s*<'info\s*>\s*{([^}]*)}/s
)?.[1];
const mat = setupGame?.match(
  /#\[\s*account\s*\([^\]]*?space\s*=\s*([^\]]+?)\s*\)\s*\]\s*pub game:/
)?.[1];
assert.exists(
  mat,
  `game field should be annotated with #[account(space = <SIZE>)]`
);
const math = eval(mat);
assert.equal(
  math,
  8 + 32 * 2 + 1 + (1 + 1) * (3 * 3) + (1 + 32),
  `space should sum up to correct size`
);
```

## 33

### --description--

**ATTENTION**: Your `lib.rs` file should have been seeded with all the game code. The game code is not relevant to Anchor, but you are still encouraged to read through it to understand how the game works.

Just a few things to fix. First, derive `PartialEq` for `GameState` and `Sign`.

### --tests--

`GameState` should be annotated with `#[derive(PartialEq)]`.

```js
assert.match(
  __librs,
  /#\[\s*derive\s*\([^\]]*?PartialEq[^\]]*?\)\s*\]\s*pub enum GameState/
);
```

`Sign` should be annotated with `#[derive(PartialEq)]`.

```js
assert.match(
  __librs,
  /#\[\s*derive\s*\([^\]]*?PartialEq[^\]]*?\)\s*\]\s*pub enum Sign/
);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

### --seed--

#### --force--

#### --"learn-anchor-by-building-tic-tac-toe-part-1/tic-tac-toe/programs/tic-tac-toe/src/lib.rs"--

```rust
use anchor_lang::prelude::*;

declare_id!("BUfb6FXLkiSpMnJnMR4Q5uGZYZkaNGytjhLwiiJQsE8F");

#[program]
pub mod tic_tac_toe {
    use super::*;

    pub fn setup_game(ctx: Context<SetupGame>) -> Result<()> {
      Ok(())
    }
}

#[derive(Accounts)]
pub struct SetupGame<'info> {
    #[account(init, payer = player_one, space = 8 + Game::MAXIMUM_SIZE)]
    pub game: Account<'info, Game>,
    #[account(mut)]
    pub player_one: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct Game {
    players: [Pubkey; 2],          // (32 * 2)
    turn: u8,                      // 1
    board: [[Option<Sign>; 3]; 3], // 9 * (1 + 1) = 18
    state: GameState,              // 32 + 1
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub enum GameState {
    Active,
    Tie,
    Won { winner: Pubkey },
}

#[derive(
    AnchorSerialize,
    AnchorDeserialize,
    Copy,
    Clone,
)]
pub enum Sign {
    X,
    O,
}

/// A tile on the game board.
pub struct Tile {
    row: u8,
    column: u8,
}

impl Game {
    pub const MAXIMUM_SIZE: usize = (32 * 2) + 1 + ((1 + 1) * 9) + (1 + 32);

    pub fn start(&mut self, players: [Pubkey; 2]) -> Result<()> {
        // TODO: Ensure the game is not already started.

        self.players = players;
        self.turn = 1;
        Ok(())
    }

    pub fn is_active(&self) -> bool {
        self.state == GameState::Active
    }

    fn current_player_index(&self) -> usize {
        ((self.turn - 1) % 2) as usize
    }

    pub fn current_player(&self) -> Pubkey {
        self.players[self.current_player_index()]
    }

    pub fn play(&mut self, tile: &Tile) -> Result<()> {
        // TODO: Ensure the game is active.

        match tile {
            tile @ Tile {
                row: 0..=2,
                column: 0..=2,
            } => match self.board[tile.row as usize][tile.column as usize] {
                Some(_) => {
                  // TODO: Return an error that the tile is already set.
                  return Err();
                },
                None => {
                    self.board[tile.row as usize][tile.column as usize] =
                        Some(Sign::from_usize(self.current_player_index()).unwrap());
                }
            },
            _ => {
              // TODO: Return an error that the tile is out of bounds.
              return Err();
            },
        }

        self.update_state();

        if GameState::Active == self.state {
            self.turn += 1;
        }

        Ok(())
    }

    fn is_winning_trio(&self, trio: [(usize, usize); 3]) -> bool {
        let [first, second, third] = trio;
        self.board[first.0][first.1].is_some()
            && self.board[first.0][first.1] == self.board[second.0][second.1]
            && self.board[first.0][first.1] == self.board[third.0][third.1]
    }

    fn update_state(&mut self) {
        for i in 0..=2 {
            // three of the same in one row
            if self.is_winning_trio([(i, 0), (i, 1), (i, 2)]) {
                self.state = GameState::Won {
                    winner: self.current_player(),
                };
                return;
            }
            // three of the same in one column
            if self.is_winning_trio([(0, i), (1, i), (2, i)]) {
                self.state = GameState::Won {
                    winner: self.current_player(),
                };
                return;
            }
        }

        // three of the same in one diagonal
        if self.is_winning_trio([(0, 0), (1, 1), (2, 2)])
            || self.is_winning_trio([(0, 2), (1, 1), (2, 0)])
        {
            self.state = GameState::Won {
                winner: self.current_player(),
            };
            return;
        }

        // reaching this code means the game has not been won,
        // so if there are unfilled tiles left, it's still active
        for row in 0..=2 {
            for column in 0..=2 {
                if self.board[row][column].is_none() {
                    return;
                }
            }
        }

        // game has not been won
        // game has no more free tiles
        // -> game ends in a tie
        self.state = GameState::Tie;
    }
}

```

## 34

### --description--

Second, in order to be able to match the correct `Sign` variant with the current player, `num_traits::FromPrimitive` should be derived for `Sign`.

To derive `FromPrimitive`, you need to add `num-traits` and `num-derive` to the dependencies in `programs/tic-tac-toe/Cargo.toml`.

**Note:** `num-derive` enables the use of `#[derive(FromPrimitive)]` on a struct or enum.

### --tests--

You should add `num-traits` to the dependencies in `programs/tic-tac-toe/Cargo.toml`.

```js
assert.match(__cargo_toml, /num-traits/);
```

You should add `num-derive` to the dependencies in `programs/tic-tac-toe/Cargo.toml`.

```js
assert.match(__cargo_toml, /num-derive/);
```

### --before-all--

```js
const __cargo_toml = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/Cargo.toml`
);
global.__cargo_toml = __cargo_toml?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__cargo_toml;
```

## 35

### --description--

Now, derive `num_derive::FromPrimitive` for `Sign`.

### --tests--

`Sign` should derive `num_derive::FromPrimitive`.

```js
assert.match(
  __librs,
  /#\[\s*derive\s*\([^\]]*?num_derive\s*::\s*FromPrimitive[^\]]*?\)\s*\]\s*pub enum Sign/
);
```

`num_traits::FromPrimitive` should be brought into the module scope.

```js
assert.match(__librs, /use num_traits::FromPrimitive;/);
```

`num_derive` should be brought into the module scope.

```js
assert.match(__librs, /use num_derive/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 36

### --description--

The third fix is to add errors to the `play` method.

Define a public enum `TicTacToeError` with the variants `TileAlreadySet` and `TileOutOfBounds`.

### --tests--

A `TicTacToeError` enum should be defined.

```js
assert.match(__librs, /pub enum TicTacToeError/);
```

`TicTacToeError` should have the variant `TileAlreadySet`.

```js
const ticTacToeError = __librs.match(
  /pub enum TicTacToeError\s*{([^}]*)}/s
)?.[1];
assert.match(ticTacToeError, /TileAlreadySet/);
```

`TicTacToeError` should have the variant `TileOutOfBounds`.

```js
const ticTacToeError = __librs.match(
  /pub enum TicTacToeError\s*{([^}]*)}/s
)?.[1];
assert.match(ticTacToeError, /TileOutOfBounds/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 37

### --description--

In the appropriate location, return the `TileAlreadySet` error.

### --tests--

The first `return Err()` should return the `TileAlreadySet` error.

```js
const librs = await __helpers
  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)
  ?.replaceAll(/[ \t]{2,}/g, ' ');

const firstReturnErr = librs.match(/return Err\s*\(([^\)]*?)\),/)?.[1];
assert.match(firstReturnErr, /TicTacToeError\s*::\s*TileAlreadySet/);
```

## 38

### --description--

Anchor does not understand the type `TicTacToeError` yet. To convert it into an error Anchor understands, use the `error_code` attribute macro above the `TicTacToeError` enum.

```rust
#[error_code]
pub enum MyCustomError { ... }
```

### --tests--

Your `TicTacToeError` enum should have the `error_code` attribute macro.

```js
const librs = await __helpers
  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)
  ?.replaceAll(/[ \t]{2,}/g, ' ');

assert.match(librs, /#\[\s*error_code\s*\]\s*pub enum TicTacToeError/);
```

## 39

### --description--

Convert the `TileAlreadySet` error into an `anchor_lang::error::Error` by calling the derived `into` method on it.

### --tests--

You should have `return Err(TicTacToeError::TileAlreadySet.into());`.

```js
const librs = await __helpers
  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)
  ?.replaceAll(/[ \t]{2,}/g, ' ');

const firstReturnErr = librs.match(/return Err\s*\(([^\)]*?)\),/)?.[1];

assert.match(
  firstReturnErr,
  /TicTacToeError\s*::\s*TileAlreadySet\s*.\s*into\(\)/
);
```

## 40

### --description--

In the appropriate location, return the `TileOutOfBounds` error.

### --tests--

The second `return Err()` should return the `TileOutOfBounds` error.

```js
const secondReturnErr = [
  ...__librs.matchAll(/return Err\s*\(([^\)]*?)\),/g)
]?.[1]?.[1];
assert.match(secondReturnErr, /TicTacToeError\s*::\s*TileOutOfBounds/);
```

The `TileOutOfBounds` error should be converted into an `anchor_lang::error::Error`.

```js
const secondReturnErr = [
  ...__librs.matchAll(/return Err\s*\(([^\)]*?)\),/g)
]?.[1]?.[1];
assert.match(
  secondReturnErr,
  /TicTacToeError\s*::\s*TileOutOfBounds\s*.\s*into\(\)/
);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 41

### --description--

Another way to return an error is to use the `require!` macro:

```rust
require!(condition, MyCustomError::MyCustomErrorVariant);
```

In the appropriate location, use the `require!` macro to return early if the game state is not `Active`. Add the following variant and return with `TicTacToeError::GameAlreadyOver`.

### --tests--

The `require!` macro should be used to return early if the game state is not `Active`.

```js
// `require!(` comes after `-> Result<()> {`, and before `match tile {`
const playFunction = __librs.match(
  /pub fn play\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^}]*)}/s
)?.[2];
assert.match(playFunction, /require!\(/);
```

The `require!` condition should use the provided `is_active` method.

```js
const playFunction = __librs.match(
  /pub fn play\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^}]*)}/s
)?.[2];
const requireCondition = playFunction?.match(
  /require!\s*\(([^\)]*?)\)\s*;\s*match\s*tile\s*{/
)?.[1];
assert.match(requireCondition, /self\s*.\s*is_active\s*\(\s*\)/);
```

The `require!` macro should return the `GameAlreadyOver` error.

```js
const playFunction = __librs.match(
  /pub fn play\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^}]*)}/s
)?.[2];
const requireCondition = playFunction?.match(
  /require!\s*\(([^\)]*?)\)\s*;\s*match\s*tile\s*{/
)?.[1];
assert.match(requireCondition, /TicTacToeError\s*::\s*GameAlreadyOver/);
```

The `TicTacToeError` enum should have the variant `GameAlreadyOver`.

```js
const ticTacToeError = __librs.match(
  /pub enum TicTacToeError\s*{([^}]*)}/s
)?.[1];
assert.match(ticTacToeError, /GameAlreadyOver/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 42

### --description--

The final _TODO_ in the game logic is to return early if the `start` method is called when the `turn` is greater than `0`.

Within the `start` method, use the `require_eq!` macro to return early if the `turn` is not equal to `0`. Add the following variant and return with `TicTacToeError::GameAlreadyStarted`.

### --tests--

The `require_eq!` macro should be used to return early if the `turn` is not equal to `0`.

```js
const startFn = __librs.match(
  /pub fn start\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^}]*)}/s
)?.[2];
const requireEq = startFn?.match(/require_eq!\s*\(([^\)]*?)\)\s*;/)?.[1];
assert.exists(requireEq, '`require_eq!` should be called');
assert.match(
  requireEq,
  /self\s*.\s*turn\s*,\s*0/,
  '`require_eq!` should be called with `self.turn` and `0`'
);
```

The `require_eq!` macro should return the `GameAlreadyStarted` error.

```js
const startFn = __librs.match(
  /pub fn start\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^}]*)}/s
)?.[2];
const requireEq = startFn?.match(/require_eq!\s*\(([^\)]*?)\)\s*;/)?.[1];
assert.match(
  requireEq,
  /TicTacToeError\s*::\s*GameAlreadyStarted/,
  'the third argument to `require_eq!` should be `TicTacToeError::GameAlreadyStarted`'
);
```

The `TicTacToeError` enum should have the variant `GameAlreadyStarted`.

```js
const ticTacToeError = __librs.match(
  /pub enum TicTacToeError\s*{([^}]*)}/s
)?.[1];
assert.match(ticTacToeError, /GameAlreadyStarted/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 43

### --description--

Focussing your attention back to the program, the Context provides all the accounts as defined in the generic passed to `Context`. These accounts can be accessed by name:

```rust
#[derive(Accounts)]
pub struct AccountsStruct<'info> {
    pub account_1: AccountInfo<'info>,
    pub account_2: AccountInfo<'info>,
    pub account_3: ProgramAccount<'info, TicTacToe>,
}
pub fn instruction_handler(ctx: Context<AccountsStruct>) -> Result<()> {
    let account_1 = &ctx.accounts.account_1;
    let account_2 = &ctx.accounts.account_2;
    let account_3 = &ctx.accounts.account_3;
}
```

Within the `setup_game` instruction handler, declare a variable `player_one` and assign the corresponding account reference to it.

### --tests--

The `player_one` variable should be declared.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(setupGame, /let\s*player_one/);
```

The `player_one` variable should be assigned `&ctx.accounts.player_one`.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
const playerOne = setupGame?.match(/let\s*player_one\s*=\s*([^\;]*?)\;/)?.[1];
assert.match(playerOne, /&\s*ctx\.accounts\.player_one/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 44

### --description--

Setting up the game requires three steps:

1. The public address of the first player
2. The public address of the second player
3. To call the `start` method on the `game` account

Withing `setup_game` declare a variable `player_one_pubkey` and assign the return of the `key` method provided by the `player_one` account to it.

### --tests--

The `player_one_pubkey` variable should be declared.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(setupGame, /let\s*player_one_pubkey/);
```

The `player_one_pubkey` variable should be assigned `player_one.key()`.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
const playerOnePubkey = setupGame?.match(
  /let\s*player_one_pubkey\s*=\s*([^\;]*?)\;/
)?.[1];
assert.match(playerOnePubkey, /player_one\s*.\s*key\s*\(\s*\)/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 45

### --description--

Instruction handlers can be called with arguments, and the values accessed through parameters:

```rust
pub fn instruction_handler(ctx: Context<AccountsStruct>, arg1: u8, arg2: u8) -> Result<()> {}
```

In order to get the second player's public key, add a `player_two_pubkey` parameter to the `setup_game` instruction handler. Type it with `Pubkey`.

### --tests--

The `setup_game` instruction handler should have a `player_two_pubkey` parameter.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(setupGame, /player_two_pubkey\s*:/);
```

The `player_two_pubkey` parameter should be of type `Pubkey`.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(setupGame, /player_two_pubkey\s*:\s*Pubkey/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 46

### --description--

Within the `setup_game` instruction handler, declare a variable `game`, and assign a mutable reference to the `game` account to it.

### --tests--

The `game` variable should be declared.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(setupGame, /let\s*game/);
```

The `game` variable should be assigned `&mut ctx.accounts.game`.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
const game = setupGame?.match(/let\s+game\s*=\s*([^\;]*?)\;/)?.[1];
assert.match(game, /&\s*mut\s+ctx\.accounts\.game/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 47

### --description--

Within the `setup_game` instruction handler, replace the `Ok(())` witha call to the `start` method on the `game` account, passing in the `player_one_pubkey` and `player_two_pubkey` variables in the expected format.

### --tests--

`setup_game` should have `game.start([player_one_pubkey, player_two_pubkey])`.

```js
const librs = await __helpers
  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)
  ?.replaceAll(/[ \t]{2,}/g, ' ');

const setupGame = librs.match(
  /pub fn setup_game\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)}/s
)?.[2];
assert.match(
  setupGame,
  /game\s*.\s*start\s*\(\s*\[\s*player_one_pubkey\s*,\s*player_two_pubkey\s*\]\s*\)/
);
```

## 48

### --description--

Currently, the first player has to provide a separate keypair for the `game` account. Then, the first player would also need to share this with the second player in order for them to play.

Instead, you can make use of a PDA to generate a deterministic address for the `game` account:

```rust
#[derive(Accounts)]
pub struct InitialisePDAAccount<'info> {
    #[account(
      init,
      payer = payer,
      seeds = [b"<SEED>", payer.key().as_ref()],
      bump
      )
    ]
    pub pda_account: Account<'info, PDAAccount>,
}
```

Within `lib.rs`, initialize the `game` account using two seeds: the first the byte string `"game"`, and the second the payer's public key.

### --tests--

`SetupGame` should annotate the `game` field with `#[account(seeds = [b"game", player_one.key().as_ref()])]`.

```js
const librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
const setupGameStruct = librs.match(/struct\s*SetupGame\s*{([^\}]*)}/s)?.[1];
const accountAttribute = setupGameStruct?.match(
  /#\[account\s*\(([^\]]*?)\]\s*pub\s*game/
)?.[1];
assert.match(
  accountAttribute,
  /seeds\s*=\s*\[\s*b"game"\s*,\s*player_one\s*.\s*key\s*\(\s*\)\s*.\s*as_ref\s*\(\s*\)\s*\]/
);
```

## 49

### --description--

The seeds are used to hash the address of the `game` account. Being a PDA, the address is deterministic, meaning that the same seeds will always produce the same address. Also, the produced public key must **not** be on the <dfn title="an elliptic curve compatible with 32-byte slices to generate.">ed25519 curve</dfn>.

To ensure this, an extra seed is added. This is called the <dfn title="an extra seed used to push an address off of a curve.">bump seed</dfn>.

Explicitly tell Anchor to generate the bump seed, by annotating the `game` field with `#[account(bump)]`.

### --tests--

`SetupGame` should annotate the `game` field with `#[account(bump)]`.

```js
const librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
const setupGameStruct = librs.match(/struct\s*SetupGame\s*{([^\}]*)}/s)?.[1];
const accountAttribute = setupGameStruct?.match(
  /#\[account\s*\(([^\]]*?)\]\s*pub\s*game/
)?.[1];
assert.match(accountAttribute, /bump/);
```

## 50

### --description--

Run the tests to see if the `setup_game` instruction handler is working correctly.

### --tests--

The test for `setup_game` should pass for `anchor test --skip-local-validator`.

```js
const terminalOutput = await __helpers.getTerminalOutput();
assert.include(terminalOutput, '1 passing');
```

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

The validator should be running at `http://localhost:8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

### --seed--

#### --force--

#### --"learn-anchor-by-building-tic-tac-toe-part-1/tic-tac-toe/tests/tic-tac-toe.ts"--

```typescript
import {
  AnchorError,
  Program,
  AnchorProvider,
  setProvider,
  workspace
} from '@coral-xyz/anchor';
import { TicTacToe } from '../target/types/tic_tac_toe';
import { expect } from 'chai';
import { Keypair, PublicKey } from '@solana/web3.js';

describe('tic-tac-toe', () => {
  // Configure the client to use the local cluster.
  setProvider(AnchorProvider.env());

  const program = workspace.TicTacToe as Program<TicTacToe>;
  const programProvider = program.provider as AnchorProvider;

  it('initializes a game', async () => {
    const playerOne = Keypair.generate();
    const playerTwo = Keypair.generate();

    const [gamePublicKey, _] = PublicKey.findProgramAddressSync(
      [Buffer.from('game'), playerOne.publicKey.toBuffer()],
      program.programId
    );

    // Airdrop to playerOne
    const sg = await programProvider.connection.requestAirdrop(
      playerOne.publicKey,
      1_000_000_000
    );
    await programProvider.connection.confirmTransaction(sg);

    await program.methods
      .setupGame(playerTwo.publicKey)
      .accounts({
        game: gamePublicKey,
        playerOne: playerOne.publicKey
      })
      .signers([playerOne])
      .rpc();

    const gameData = await program.account.game.fetch(gamePublicKey);

    expect(gameData.turn).to.equal(1);
    expect(gameData.players).to.eql([playerOne.publicKey, playerTwo.publicKey]);

    expect(gameData.state).to.eql({ active: {} });
    expect(gameData.board).to.eql([
      [null, null, null],
      [null, null, null],
      [null, null, null]
    ]);
  });
});
```

## 51

### --description--

Using a constant string, and the first player's public key as seeds only allows one game to be played. Dynamic seeds can be added to the `game` account to allow for multiple games to be played with the same player accounts, using the `instruction` attribute:

```rust
pub fn initialize(ctx: Context<Init>, arg_1: String, arg_2: u8) -> Result<()> {}

#[derive(Accounts)]
#[instruction(arg_1: String, arg_2: u8)]
pub struct Init<'info> {
    #[account(
      init,
      payer = payer,
      seeds = [arg_1.as_bytes(), payer.key().as_ref(), arg_2],
      bump
    )]
    pub pda: Account<'info, Game>,
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
```

The `instruction` attribute provides access to the instruction's arugments. You have to list them in the same order as in the instruction but you can omit all arguments after the last one you need.

Within `lib.rs`, add a third parameter `game_id` of type `String` to the `setup_game` instruction handler.

### --tests--

The `setup_game` instruction handler should have a `game_id` parameter.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/s
)?.[1];
assert.match(setupGame, /game_id\s*:/);
```

The `game_id` parameter should be of type `String`.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/s
)?.[1];
assert.match(setupGame, /game_id\s*:\s*String/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 52

### --description--

Within `lib.rs`, annotate the `SetupGame` struct with the `instruction` attribute, passing in the required parameters to access the `game_id` parameter.

### --tests--

The `SetupGame` struct should be annotated with `#[instruction(player_two_pubkey: Pubkey, game_id: String)]`.

```js
const librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
assert.match(
  librs,
  /(?<=#\[\s*instruction\s*\(\s*player_two_pubkey\s*:\s*Pubkey\s*,\s*game_id\s*:\s*String\s*\)\s*])\s*pub\s+struct\s+SetupGame/
);
```

## 53

### --description--

Within `lib.rs`, add the `game_id` parameter as a seed to the `seeds` value of the `game` account.

### --tests--

The `game` field should be annotated with `#[account(seeds = [b"game", player_one.key().as_ref(), game_id.as_bytes()])]`.

```js
const librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
const setupGameStruct = librs.match(/struct\s*SetupGame\s*{([^\}]*)}/s)?.[1];
const accountAttribute = setupGameStruct?.match(
  /#\[account\s*\(([^\]]*?)\]\s*pub\s*game/
)?.[1];
assert.match(
  accountAttribute,
  /seeds\s*=\s*\[\s*b"game"\s*,\s*player_one\s*.\s*key\s*\(\s*\)\s*.\s*as_ref\s*\(\s*\)\s*,\s*game_id\s*.\s*as_bytes\s*\(\s*\)\s*\]/
);
```

## 54

### --description--

To prevent Rust from complaining about the `game_id` parameter not being used, prefix it with an underscore.

### --tests--

The `game_id` parameter should be prefixed with an underscore in the `setup_game` instruction handler.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(setupGame, /_game_id\s*:/);
```

The `game_id` parameter should be prefixed with an underscore in the `instruction` attribute.

```js
const setupGame = __librs.match(
  /pub fn setup_game\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
const instructionAttribute = setupGame?.match(
  /#\[instruction\s*\(([^\]]*?)\)\s*\]/
)?.[1];
assert.match(instructionAttribute, /_game_id\s*:\s*String/);
```

The `game_id` parameter should be prefixed with an underscore in the `game` account's `seeds` value.

```js
const setupGameStruct = __librs.match(/struct\s*SetupGame\s*{([^\}]*)}/s)?.[1];
const accountAttribute = setupGameStruct?.match(
  /#\[account\s*\(([^\]]*?)\]\s*pub\s*game/
)?.[1];
assert.match(
  accountAttribute,
  /seeds\s*=\s*\[\s*b"game"\s*,\s*player_one\s*.\s*key\s*\(\s*\)\s*.\s*as_ref\s*\(\s*\)\s*,\s*_game_id\s*.\s*as_bytes\s*\(\s*\)\s*\]/
);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 55

### --description--

Run the tests to see if the `setup_game` instruction handler is working correctly.

### --tests--

The test for `setup_game` should pass when `anchor test --skip-local-validator`.

```js
const terminalOutput = await __helpers.getTerminalOutput();
assert.include(terminalOutput, '1 passing');
```

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

The validator should be running at `http://localhost:8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

### --seed--

#### --force--

#### --"learn-anchor-by-building-tic-tac-toe-part-1/tic-tac-toe/tests/tic-tac-toe.ts"--

```typescript
import {
  AnchorError,
  Program,
  AnchorProvider,
  setProvider,
  workspace
} from '@coral-xyz/anchor';
import { TicTacToe } from '../target/types/tic_tac_toe';
import { expect } from 'chai';
import { Keypair, PublicKey } from '@solana/web3.js';

describe('tic-tac-toe', () => {
  // Configure the client to use the local cluster.
  setProvider(AnchorProvider.env());

  const program = workspace.TicTacToe as Program<TicTacToe>;
  const programProvider = program.provider as AnchorProvider;

  it('initializes a game', async () => {
    const playerOne = Keypair.generate();
    const playerTwo = Keypair.generate();

    const gameId = 'game-1';

    const [gamePublicKey, _] = PublicKey.findProgramAddressSync(
      [
        Buffer.from('game'),
        playerOne.publicKey.toBuffer(),
        Buffer.from(gameId)
      ],
      program.programId
    );

    // Airdrop to playerOne
    const sg = await programProvider.connection.requestAirdrop(
      playerOne.publicKey,
      1_000_000_000
    );
    await programProvider.connection.confirmTransaction(sg);

    await program.methods
      .setupGame(playerTwo.publicKey, gameId)
      .accounts({
        game: gamePublicKey,
        playerOne: playerOne.publicKey
      })
      .signers([playerOne])
      .rpc();

    const gameData = await program.account.game.fetch(gamePublicKey);

    expect(gameData.turn).to.equal(1);
    expect(gameData.players).to.eql([playerOne.publicKey, playerTwo.publicKey]);

    expect(gameData.state).to.eql({ active: {} });
    expect(gameData.board).to.eql([
      [null, null, null],
      [null, null, null],
      [null, null, null]
    ]);
  });
});
```

## 56

### --description--

Within `lib.rs`, define another instruction handler called `play`. It should take a `ctx` parameter of type `Context<Play>`, and return a `Result<()>`.

### --tests--

The `play` instruction handler should be defined.

```js
assert.match(__librs, /pub fn play\s*\([^\)]*?\)/);
```

The `play` instruction handler should take a `ctx` parameter of type `Context<Play>`.

```js
const playFn = __librs.match(/pub fn play\s*\(([^\)]*?)\)/)?.[1];
assert.match(playFn, /ctx\s*:\s*Context\s*<\s*Play\s*>/);
```

The `play` instruction handler should return a `Result<()>`.

```js
const playReturn = __librs.match(/pub fn play\s*\([^\)]*?\)([^\{]*){/)?.[1];
assert.match(playReturn, /->\s*Result\s*<\s*\(\s*\)\s*>\s*/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 57

### --description--

Within `lib.rs`, define a new public struct `Play` that implements the `Accounts` trait.

### --tests--

The `Play` struct should be defined.

```js
assert.match(__librs, /pub struct Play/);
```

The `Play` struct should implement the `Accounts` trait.

```js
assert.match(
  __librs,
  /(?<=#\[derive\s*\(\s*Accounts\s*\)\s*\])\s*pub struct Play/
);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 58

### --description--

The `play` instruction handler will need access to the `game` account.

Within `Play`, define a field called `game` of type `Account<'info, Game>`.

### --tests--

The `game` field should be defined.

```js
const playStruct = __librs.match(/pub struct Play[^\{]*?{([^\}]*)}/)?.[1];
assert.match(playStruct, /game\s*:/);
```

The `game` field should be of type `Account<'info, Game>`.

```js
const playStruct = __librs.match(/pub struct Play[^\{]*?{([^\}]*?)}/)?.[1];
assert.match(playStruct, /game\s*:\s*Account\s*<\s*'info\s*,\s*Game\s*>/);
```

The `Play` struct should take a generic lifetime parameter `'info`.

```js
assert.match(__librs, /pub struct Play\s*<'info>/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 59

### --description--

The `play` instruction handler will need access to the player who called it.

Within `Play`, define a field called `player` of type `Signer<'info>`.

### --tests--

The `player` field should be defined.

```js
const playStruct = __librs.match(/pub struct Play[^\{]*?{([^\}]*)}/)?.[1];
assert.match(playStruct, /player\s*:/);
```

The `player` field should be of type `Signer<'info>`.

```js
const playStruct = __librs.match(/pub struct Play[^\{]*?{([^\}]*?)}/)?.[1];
assert.match(playStruct, /player\s*:\s*Signer\s*<\s*'info\s*>/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 60

### --description--

Within the `play` instruction handler, declare a variable `game`, and assign a mutable reference to the `game` account to it.

### --tests--

The `game` variable should be declared.

```js
const playFn = __librs.match(
  /pub fn play\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(playFn, /let game/);
```

The `game` variable should be assigned `&mut ctx.accounts.game`.

```js
const playFn = __librs.match(
  /pub fn play\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(playFn, /let game\s*=\s*&\s*mut\s*ctx\s*.\s*accounts\s*.\s*game/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 61

### --description--

Along with the `require!` macro, Anchor provides a `require_keys_eq!` macro. This macro takes two public keys, and ensures they are equal:

```rust
require_keys_eq!(
  ctx.accounts.account_1.key(),
  ctx.accounts.account_2.key(),
  OptionalCustomError::MyError
);
```

**Note:** This is specifically provided, because the `require_eq!` macro should not be used to compare public keys.

Within the `play` instruction handler, use the `require_keys_eq!` macro to ensure the expected current player is the same as the player who called the instruction.

### --tests--

`play` should have `require_keys_eq!(game.current_player(), ctx.accounts.player.key());`.

```js
const librs = await __helpers
  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)
  ?.replaceAll(/[ \t]{2,}/g, ' ');
const playFn = librs.match(
  /pub fn play\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(
  playFn,
  /require_keys_eq\s*!\s*\(\s*game\s*.\s*current_player\s*\(\s*\)\s*,\s*ctx\s*.\s*accounts\s*.\s*player\s*.\s*key\s*\(\s*\)\s*\)/
);
```

## 62

### --description--

Add a third argument of `TicTacToeError::NotPlayersTurn` to the `require_keys_eq!` macro. Also, define the `NotPlayersTurn` error variant in the `TicTacToeError` enum.

### --tests--

`play` should have `require_keys_eq!(game.current_player(), ctx.accounts.player.key(), TicTacToeError::NotPlayersTurn);`.

```js
const playFn = __librs.match(
  /pub fn play\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(
  playFn,
  /require_keys_eq\s*!\s*\(\s*game\s*.\s*current_player\s*\(\s*\)\s*,\s*ctx\s*.\s*accounts\s*.\s*player\s*.\s*key\s*\(\s*\)\s*,\s*TicTacToeError\s*::\s*NotPlayersTurn\s*\)/
);
```

`TicTacToeError` should have a `NotPlayersTurn` variant.

```js
const ticTacToError = __librs.match(/enum\s*TicTacToeError\s*{([^\}]*)}/)?.[1];
assert.match(ticTacToError, /NotPlayersTurn/);
```

### --before-all--

```js
const __librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
global.__librs = __librs?.replaceAll(/[ \t]{2,}/g, ' ');
```

### --after-all--

```js
delete global.__librs;
```

## 63

### --description--

Within the `play` instruction handler, call the `play` method on the `game` account. Pass in a reference to a variable `tile`.

### --tests--

`play` should have `game.play(&tile);`.

```js
const librs = await __helpers
  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)
  ?.replaceAll(/[ \t]{2,}/g, ' ');
const playFn = librs.match(
  /pub fn play\s*\([^\)]*?\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(playFn, /game\s*.\s*play\s*\(\s*&\s*tile\s*\)/);
```

## 64

### --description--

Adjust the `play` instruction handler signature to take a `tile` parameter of type `Tile`.

### --tests--

`play` should take a `tile` parameter of type `Tile`.

```js
const playFnParams = __librs.match(
  /pub fn play\s*\(([^\)]*?)\)\s*->\s*Result\s*<\s*\(\)\s*>\s*{([^\}]*)\}/
)?.[1];
assert.match(playFnParams, /tile\s*:\s*Tile/);
```

## 65

### --description--

Run the tests.

### --tests--

The tests for the `play` instruction handler should error ❌.

```js
const terminalOutput = await __helpers.getTerminalOutput();
assert.include(terminalOutput, '3 failing');
```

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

The validator should be running at `http://localhost:8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

### --seed--

#### --force--

#### --"learn-anchor-by-building-tic-tac-toe-part-1/tic-tac-toe/tests/tic-tac-toe.ts"--

```typescript
import {
  AnchorError,
  Program,
  AnchorProvider,
  setProvider,
  workspace
} from '@coral-xyz/anchor';
import { TicTacToe } from '../target/types/tic_tac_toe';
import { expect } from 'chai';
import { Keypair, PublicKey } from '@solana/web3.js';

describe('tic-tac-toe', () => {
  // Configure the client to use the local cluster.
  setProvider(AnchorProvider.env());

  const program = workspace.TicTacToe as Program<TicTacToe>;
  const programProvider = program.provider as AnchorProvider;

  it('initializes a game', async () => {
    const playerOne = Keypair.generate();
    const playerTwo = Keypair.generate();

    const gameId = 'game-1';

    const [gamePublicKey, _] = PublicKey.findProgramAddressSync(
      [
        Buffer.from('game'),
        playerOne.publicKey.toBuffer(),
        Buffer.from(gameId)
      ],
      program.programId
    );

    // Airdrop to playerOne
    const sg = await programProvider.connection.requestAirdrop(
      playerOne.publicKey,
      1_000_000_000
    );
    await programProvider.connection.confirmTransaction(sg);

    await program.methods
      .setupGame(playerTwo.publicKey, gameId)
      .accounts({
        game: gamePublicKey,
        playerOne: playerOne.publicKey
      })
      .signers([playerOne])
      .rpc();

    const gameData = await program.account.game.fetch(gamePublicKey);

    expect(gameData.turn).to.equal(1);
    expect(gameData.players).to.eql([playerOne.publicKey, playerTwo.publicKey]);

    expect(gameData.state).to.eql({ active: {} });
    expect(gameData.board).to.eql([
      [null, null, null],
      [null, null, null],
      [null, null, null]
    ]);
  });

  it('has player one win', async () => {
    const playerOne = Keypair.generate();
    const playerTwo = Keypair.generate();

    const gameId = 'game-2';

    const [gamePublicKey, _bump] = PublicKey.findProgramAddressSync(
      [
        Buffer.from('game'),
        playerOne.publicKey.toBuffer(),
        Buffer.from(gameId)
      ],
      program.programId
    );

    // Airdrop to playerOne
    const sg = await programProvider.connection.requestAirdrop(
      playerOne.publicKey,
      1_000_000_000
    );
    await programProvider.connection.confirmTransaction(sg);

    await program.methods
      .setupGame(playerTwo.publicKey, gameId)
      .accounts({
        game: gamePublicKey,
        playerOne: playerOne.publicKey
      })
      .signers([playerOne])
      .rpc();

    let gameData = await program.account.game.fetch(gamePublicKey);

    expect(gameData.turn).to.equal(1);

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 0, column: 0 },
      2,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [null, null, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerTwo,
      { row: 1, column: 0 },
      3,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [{ o: {} }, null, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 0, column: 1 },
      4,
      { active: {} },
      [
        [{ x: {} }, { x: {} }, null],
        [{ o: {} }, null, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerTwo,
      { row: 1, column: 1 },
      5,
      { active: {} },
      [
        [{ x: {} }, { x: {} }, null],
        [{ o: {} }, { o: {} }, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 0, column: 2 },
      5,
      { won: { winner: playerOne.publicKey } },
      [
        [{ x: {} }, { x: {} }, { x: {} }],
        [{ o: {} }, { o: {} }, null],
        [null, null, null]
      ]
    );
  });

  it('handles ties', async () => {
    const playerOne = Keypair.generate();
    const playerTwo = Keypair.generate();

    const gameId = 'game-3';

    const [gamePublicKey, _bump] = PublicKey.findProgramAddressSync(
      [
        Buffer.from('game'),
        playerOne.publicKey.toBuffer(),
        Buffer.from(gameId)
      ],
      program.programId
    );

    // Airdrop to playerOne
    const sg = await programProvider.connection.requestAirdrop(
      playerOne.publicKey,
      1_000_000_000
    );
    await programProvider.connection.confirmTransaction(sg);

    await program.methods
      .setupGame(playerTwo.publicKey, gameId)
      .accounts({
        game: gamePublicKey,
        playerOne: playerOne.publicKey
      })
      .signers([playerOne])
      .rpc();

    let gameState = await program.account.game.fetch(gamePublicKey);
    expect(gameState.turn).to.equal(1);

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 0, column: 0 },
      2,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [null, null, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerTwo,
      { row: 1, column: 1 },
      3,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [null, { o: {} }, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 2, column: 0 },
      4,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [null, { o: {} }, null],
        [{ x: {} }, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerTwo,
      { row: 1, column: 0 },
      5,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [{ o: {} }, { o: {} }, null],
        [{ x: {} }, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 1, column: 2 },
      6,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [{ o: {} }, { o: {} }, { x: {} }],
        [{ x: {} }, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerTwo,
      { row: 0, column: 1 },
      7,
      { active: {} },
      [
        [{ x: {} }, { o: {} }, null],
        [{ o: {} }, { o: {} }, { x: {} }],
        [{ x: {} }, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 2, column: 1 },
      8,
      { active: {} },
      [
        [{ x: {} }, { o: {} }, null],
        [{ o: {} }, { o: {} }, { x: {} }],
        [{ x: {} }, { x: {} }, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerTwo,
      { row: 2, column: 2 },
      9,
      { active: {} },
      [
        [{ x: {} }, { o: {} }, null],
        [{ o: {} }, { o: {} }, { x: {} }],
        [{ x: {} }, { x: {} }, { o: {} }]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 0, column: 2 },
      9,
      { tie: {} },
      [
        [{ x: {} }, { o: {} }, { x: {} }],
        [{ o: {} }, { o: {} }, { x: {} }],
        [{ x: {} }, { x: {} }, { o: {} }]
      ]
    );
  });

  it('handles invalid plays', async () => {
    const playerOne = Keypair.generate();
    const playerTwo = Keypair.generate();

    const gameId = 'game-4';

    const [gamePublicKey, _bump] = PublicKey.findProgramAddressSync(
      [
        Buffer.from('game'),
        playerOne.publicKey.toBuffer(),
        Buffer.from(gameId)
      ],
      program.programId
    );

    // Airdrop to playerOne
    const sg = await programProvider.connection.requestAirdrop(
      playerOne.publicKey,
      1_000_000_000
    );
    await programProvider.connection.confirmTransaction(sg);

    await program.methods
      .setupGame(playerTwo.publicKey, gameId)
      .accounts({
        game: gamePublicKey,
        playerOne: playerOne.publicKey
      })
      .signers([playerOne])
      .rpc();

    let gameData = await program.account.game.fetch(gamePublicKey);

    expect(gameData.turn).to.equal(1);

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 0, column: 0 },
      2,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [null, null, null],
        [null, null, null]
      ]
    );

    try {
      await play(
        program,
        gamePublicKey,
        playerOne, // same player in subsequent turns
        // change sth about the tx because
        // duplicate tx that come in too fast
        // after each other may get dropped
        { row: 1, column: 0 },
        2,
        { active: {} },
        [
          [{ x: {} }, null, null],
          [null, null, null],
          [null, null, null]
        ]
      );
      chai.assert(false, "should've failed but didn't ");
    } catch (_err) {
      expect(_err).to.be.instanceOf(AnchorError);
      const err: AnchorError = _err;
      expect(err.error.errorCode.code).to.equal('NotPlayersTurn');
      expect(err.error.errorCode.number).to.equal(6003);
      expect(err.program.equals(program.programId)).is.true;
      expect(err.error.comparedValues).to.deep.equal([
        playerTwo.publicKey,
        playerOne.publicKey
      ]);
    }

    try {
      await play(
        program,
        gamePublicKey,
        playerTwo,
        { row: 5, column: 1 }, // out of bounds row
        3,
        { active: {} },
        [
          [{ x: {} }, null, null],
          [null, null, null],
          [null, null, null]
        ]
      );
      chai.assert(false, "should've failed but didn't ");
    } catch (_err) {
      expect(_err).to.be.instanceOf(AnchorError);
      const err: AnchorError = _err;
      expect(err.error.errorCode.number).to.equal(6000);
      expect(err.error.errorCode.code).to.equal('TileOutOfBounds');
    }

    try {
      await play(
        program,
        gamePublicKey,
        playerTwo,
        { row: 0, column: 0 },
        3,
        { active: {} },
        [
          [{ x: {} }, null, null],
          [null, null, null],
          [null, null, null]
        ]
      );
      chai.assert(false, "should've failed but didn't ");
    } catch (_err) {
      expect(_err).to.be.instanceOf(AnchorError);
      const err: AnchorError = _err;
      expect(err.error.errorCode.number).to.equal(6001);
      expect(err.error.errorCode.code).to.equal('TileAlreadySet');
    }

    await play(
      program,
      gamePublicKey,
      playerTwo,
      { row: 1, column: 0 },
      3,
      { active: {} },
      [
        [{ x: {} }, null, null],
        [{ o: {} }, null, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 0, column: 1 },
      4,
      { active: {} },
      [
        [{ x: {} }, { x: {} }, null],
        [{ o: {} }, null, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerTwo,
      { row: 1, column: 1 },
      5,
      { active: {} },
      [
        [{ x: {} }, { x: {} }, null],
        [{ o: {} }, { o: {} }, null],
        [null, null, null]
      ]
    );

    await play(
      program,
      gamePublicKey,
      playerOne,
      { row: 0, column: 2 },
      5,
      { won: { winner: playerOne.publicKey } },
      [
        [{ x: {} }, { x: {} }, { x: {} }],
        [{ o: {} }, { o: {} }, null],
        [null, null, null]
      ]
    );

    try {
      await play(
        program,
        gamePublicKey,
        playerOne,
        { row: 0, column: 2 },
        6,
        { won: { winner: playerOne.publicKey } },
        [
          [{ x: {} }, { x: {} }, null],
          [{ o: {} }, { o: {} }, null],
          [null, null, null]
        ]
      );
      chai.assert(false, "should've failed but didn't ");
    } catch (_err) {
      expect(_err).to.be.instanceOf(AnchorError);
      const err: AnchorError = _err;
      expect(err.error.errorCode.number).to.equal(6002);
      expect(err.error.errorCode.code).to.equal('GameAlreadyOver');
    }
  });
});

async function play(
  program: Program<TicTacToe>,
  game: PublicKey,
  player: Keypair,
  tile: { row: number; column: number },
  expectedTurn: number,
  expectedGameState:
    | { active: {} }
    | { won: { winner: PublicKey } }
    | { tie: {} },
  expectedBoard: Array<Array<{ x: {} } | { o: {} } | null>>
) {
  await program.methods
    .play(tile)
    .accounts({
      player: player.publicKey,
      game
    })
    .signers([player])
    .rpc();

  const gameData = await program.account.game.fetch(game);

  expect(gameData.turn).to.equal(expectedTurn);
  expect(gameData.state).to.eql(expectedGameState);
  expect(gameData.board).to.eql(expectedBoard);
}
```

## 66

### --description--

The tests failed, because a mutable reference to the `game` account is required, but the account is not marked as `mut`.

Mark the `game` account as `mut`.

### --tests--

The `Play` struct should have `game` annotated with `#[account(mut)]`.

```js
const librs = await __helpers.getFile(
  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`
);
const playStruct = librs.match(/pub\s+struct\s+Play[^\{]*?{([^\}]*)}/)?.[1];
assert.match(
  playStruct,
  /#[\s\n]*account[\s\n]*\([\s\n]*mut[\s\n]*\)\s*pub\s+game/
);
```

## 67

### --description--

Run the tests to ensure everything is working as expected.

### --tests--

All tests should pass for the `anchor test --skip-local-validator` command ✅.

```js
const terminalOutput = await __helpers.getTerminalOutput();
assert.include(terminalOutput, '4 passing');
```

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);
assert.match(cwd, dirRegex);
```

The validator should be running at `http://localhost:8899`.

```js
const command = `curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getHealth"}'`;
const { stdout, stderr } = await __helpers.getCommandOutput(command);
try {
  const jsonOut = JSON.parse(stdout);
  assert.deepInclude(jsonOut, { result: 'ok' });
} catch (e) {
  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');
}
```

## 68

### --description--

**Summary**

- Derive `Accounts` for context structs
- Annotate custom accounts with `#[account]`
- Annotate custom errors with `#[error_code]`
- Use the `instruction` attribute to access the instruction data
- Anchor provides various account constraints:
  - `init` - Initialises an account, setting the owner field of the created account to the currently executing program
  - `mut` - Checks the given account is mutable, and persists any state changes
- The `Account` struct verifies program ownership
- The `Signer` struct verifies the account in the transaction also signed the transaction
- The `Program` struct validates the account provided is the given program

🎆

Once you are done, enter `done` in the terminal.

### --tests--

You should enter `done` in the terminal

```js
const lastCommand = await __helpers.getLastCommand();
assert.include(lastCommand, 'done');
```

## --fcc-end--


================================================
FILE: curriculum/locales/english/learn-anchor-by-building-tic-tac-toe-part-2.md
================================================
# Solana - Learn How to Test an Anchor Program: Part 2

## 1

### --description--

In the previous project, you used Anchor to create a program with instructions to play a game of Tic-Tac-Toe. This same program has been carried over as the boilerplate for this project.

Anchor automatically generated some test boilerplate for the `tic-tac-toe` program in the `tests/` directory. You will be mostly working in this directory.

Within a new terminal, change into the `tic-tac-toe` directory.

### --tests--

You should be in the `tic-tac-toe` directory.

```js
const cwd = await __helpers.getLastCWD();
const dirRegex = new RegExp(`${project.dashedName}/?$`);
assert.match(cwd, 
Download .txt
gitextract_on91dfbr/

├── .devcontainer.json
├── .editorconfig
├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── BUG.md
│       └── HELP.md
├── .gitignore
├── .gitpod.Dockerfile
├── .gitpod.yml
├── .prettierignore
├── .prettierrc
├── .vscode/
│   └── settings.json
├── Dockerfile
├── LICENSE
├── README.md
├── bash/
│   ├── .bashrc
│   └── sourcerer.sh
├── build-a-client-side-app/
│   ├── .gitignore
│   ├── mess.json
│   └── mess.ts
├── build-a-smart-contract/
│   ├── package.json
│   ├── program/
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   └── lib.rs
│   │   └── tests/
│   │       └── process_instruction.rs
│   └── wallet.json
├── build-a-university-certification-nft/
│   ├── client/
│   │   ├── .gitignore
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── app.css
│   │   │   ├── app.tsx
│   │   │   ├── index.css
│   │   │   ├── main.tsx
│   │   │   └── vite-env.d.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   └── vite.config.ts
│   ├── index.d.ts
│   ├── index.js
│   ├── metadatas.json
│   ├── package.json
│   ├── server.js
│   └── utils.js
├── build-an-anchor-leaderboard/
│   └── rock-destroyer/
│       ├── .gitignore
│       ├── .prettierignore
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── rock-destroyer/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── rock-destroyer.ts
│       └── tsconfig.json
├── build-and-deploy-your-freeform-app/
│   └── .gitkeep
├── config/
│   ├── projects.json
│   └── state.json
├── curriculum/
│   └── locales/
│       └── english/
│           ├── build-a-client-side-app.md
│           ├── build-a-smart-contract.md
│           ├── build-a-university-certification-nft.md
│           ├── build-an-anchor-leaderboard.md
│           ├── build-and-deploy-your-freeform-app.md
│           ├── learn-anchor-by-building-tic-tac-toe-part-1.md
│           ├── learn-anchor-by-building-tic-tac-toe-part-2.md
│           ├── learn-how-to-build-a-client-side-app-part-1.md
│           ├── learn-how-to-build-a-client-side-app-part-2.md
│           ├── learn-how-to-build-for-mainnet.md
│           ├── learn-how-to-deploy-to-devnet.md
│           ├── learn-how-to-interact-with-on-chain-programs.md
│           ├── learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract.md
│           ├── learn-solanas-token-program-by-minting-a-fungible-token.md
│           └── learn-the-metaplex-sdk-by-minting-an-nft.md
├── freecodecamp.conf.json
├── learn-anchor-by-building-tic-tac-toe-part-1/
│   └── .gitkeep
├── learn-anchor-by-building-tic-tac-toe-part-2/
│   ├── .prettierignore
│   └── tic-tac-toe/
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── tic-tac-toe/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── tic-tac-toe.ts
│       └── tsconfig.json
├── learn-how-to-build-a-client-side-app-part-1/
│   ├── .gitkeep
│   └── tic-tac-toe/
│       ├── .gitignore
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── app/
│       │   ├── .gitignore
│       │   ├── index.css
│       │   ├── index.html
│       │   ├── index.js
│       │   ├── package.json
│       │   ├── utils.js
│       │   └── wallet.js
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── tic-tac-toe/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── tic-tac-toe.ts
│       └── tsconfig.json
├── learn-how-to-build-a-client-side-app-part-2/
│   ├── app/
│   │   ├── .gitignore
│   │   ├── index.css
│   │   ├── index.html
│   │   ├── index.js
│   │   ├── package.json
│   │   ├── utils.js
│   │   ├── wallet.js
│   │   └── web3.js
│   └── tic_tac_toe.ts
├── learn-how-to-build-for-mainnet/
│   ├── _answer/
│   │   ├── Anchor.toml
│   │   ├── Cargo.toml
│   │   ├── app/
│   │   │   ├── index.html
│   │   │   ├── package.json
│   │   │   ├── src/
│   │   │   │   ├── app.css
│   │   │   │   ├── app.tsx
│   │   │   │   ├── index.css
│   │   │   │   ├── main.tsx
│   │   │   │   ├── utils.ts
│   │   │   │   └── vite-env.d.ts
│   │   │   ├── tsconfig.json
│   │   │   ├── tsconfig.node.json
│   │   │   └── vite.config.ts
│   │   ├── migrations/
│   │   │   └── deploy.ts
│   │   ├── package.json
│   │   ├── programs/
│   │   │   └── todo/
│   │   │       ├── Cargo.toml
│   │   │       ├── Xargo.toml
│   │   │       └── src/
│   │   │           └── lib.rs
│   │   ├── tests/
│   │   │   └── todo.ts
│   │   └── tsconfig.json
│   └── todo/
│       ├── .gitignore
│       ├── .prettierignore
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── app/
│       │   ├── index.html
│       │   ├── package.json
│       │   ├── src/
│       │   │   ├── app.css
│       │   │   ├── app.tsx
│       │   │   ├── index.css
│       │   │   ├── main.tsx
│       │   │   ├── utils.ts
│       │   │   └── vite-env.d.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.node.json
│       │   └── vite.config.ts
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── todo/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── todo.ts
│       └── tsconfig.json
├── learn-how-to-deploy-to-devnet/
│   └── todo/
│       ├── Anchor.toml
│       ├── Cargo.toml
│       ├── app/
│       │   ├── index.html
│       │   ├── package.json
│       │   ├── src/
│       │   │   ├── app.css
│       │   │   ├── app.tsx
│       │   │   ├── index.css
│       │   │   ├── main.tsx
│       │   │   ├── utils.ts
│       │   │   └── vite-env.d.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.node.json
│       │   └── vite.config.ts
│       ├── migrations/
│       │   └── deploy.ts
│       ├── package.json
│       ├── programs/
│       │   └── todo/
│       │       ├── Cargo.toml
│       │       ├── Xargo.toml
│       │       └── src/
│       │           └── lib.rs
│       ├── tests/
│       │   └── todo.ts
│       └── tsconfig.json
├── learn-how-to-interact-with-on-chain-programs/
│   ├── cluster-devnet.env
│   ├── cluster-mainnet-beta.env
│   ├── cluster-testnet.env
│   ├── package.json
│   └── src/
│       ├── _answer/
│       │   └── client/
│       │       ├── hello-world.js
│       │       └── main.js
│       └── program-rust/
│           ├── .gitignore
│           ├── Cargo.toml
│           └── src/
│               └── lib.rs
├── learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/
│   ├── .gitignore
│   ├── package.json
│   ├── src/
│   │   ├── client/
│   │   │   ├── hello_world.ts
│   │   │   ├── main.ts
│   │   │   └── utils.ts
│   │   └── program-rust/
│   │       ├── .gitignore
│   │       ├── Cargo.toml
│   │       └── src/
│   │           └── lib.rs
│   └── tsconfig.json
├── learn-solanas-token-program-by-minting-a-fungible-token/
│   ├── .gitkeep
│   ├── package.json
│   └── utils.js
├── learn-the-metaplex-sdk-by-minting-an-nft/
│   ├── package.json
│   ├── server.js
│   ├── spl-program/
│   │   ├── create-mint-account.js
│   │   ├── create-token-account.js
│   │   ├── get-token-account.js
│   │   ├── get-token-info.js
│   │   ├── mint.js
│   │   ├── package.json
│   │   ├── transfer.js
│   │   └── utils.js
│   └── utils.js
├── package.json
├── renovate.json
└── tooling/
    ├── camper-info.js
    ├── helpers.js
    ├── rejig.js
    └── seed.js
Download .txt
SYMBOL INDEX (236 symbols across 38 files)

FILE: build-a-client-side-app/mess.ts
  type Mess (line 1) | type Mess = {
  constant IDL (line 89) | const IDL: Mess = {

FILE: build-a-smart-contract/program/tests/process_instruction.rs
  type MessageStructForTest (line 8) | pub struct MessageStructForTest {
  function owner_not_program_id (line 13) | fn owner_not_program_id() {
  function instruction_is_deserialized (line 35) | fn instruction_is_deserialized() {
  function instruction_not_string (line 58) | fn instruction_not_string() {
  function instruction_too_long (line 79) | fn instruction_too_long() {
  function no_accounts (line 100) | fn no_accounts() {
  function instruction_data_padded (line 109) | fn instruction_data_padded() {
  function success (line 131) | fn success() {

FILE: build-a-university-certification-nft/client/src/app.tsx
  function App (line 18) | function App() {
  type OutputT (line 275) | type OutputT = {
  type CreateMintAccountF (line 279) | type CreateMintAccountF = () => Promise<void>;
  type GetMintAccountsF (line 280) | type GetMintAccountsF = () => Promise<void>;
  type CreateTokenAccountF (line 281) | type CreateTokenAccountF = () => Promise<void>;
  type MintTokenF (line 282) | type MintTokenF = () => Promise<void>;
  type GetNFTsF (line 283) | type GetNFTsF = () => Promise<void>;
  type CreateCertificateProgramT (line 285) | type CreateCertificateProgramT = {
  function CreateCertificateProgram (line 292) | function CreateCertificateProgram({
  function GetCertificatePrograms (line 306) | function GetCertificatePrograms({
  function RegisterStudent (line 322) | function RegisterStudent({
  function GrantCertificate (line 338) | function GrantCertificate({ mintToken }: { mintToken: MintTokenF }) {
  function ViewStudentCertificate (line 347) | function ViewStudentCertificate({ getNFTs }: { getNFTs: GetNFTsF }) {
  type DisplayPngT (line 356) | type DisplayPngT = {
  function DisplayPng (line 360) | function DisplayPng({ buffer }: DisplayPngT) {
  function Output (line 364) | function Output({ output }: OutputT) {
  function ValidationError (line 374) | function ValidationError({ invalidInputs }: { invalidInputs: string[] }) {

FILE: build-a-university-certification-nft/server.js
  constant PORT (line 40) | const PORT = process.env.PORT || 3002;
  function getMetadatas (line 45) | function getMetadatas() {
  function writeMetadatas (line 50) | function writeMetadatas(metadatas) {

FILE: build-a-university-certification-nft/utils.js
  function localStorage (line 1) | function localStorage(options) {
  class LocalStorageDriver (line 9) | class LocalStorageDriver {
    method constructor (line 10) | constructor(options) {
    method getUploadPrice (line 17) | async getUploadPrice(bytes) {
    method upload (line 21) | async upload(file) {
    method download (line 32) | async download(uri) {

FILE: build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/src/lib.rs
  function initialize (line 9) | pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
  type Initialize (line 15) | pub struct Initialize {}

FILE: learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/programs/tic-tac-toe/src/lib.rs
  function setup_game (line 11) | pub fn setup_game(
  function play (line 24) | pub fn play(ctx: Context<Play>, tile: Tile) -> Result<()> {
  type SetupGame (line 39) | pub struct SetupGame<'info> {
  type Game (line 54) | pub struct Game {
    constant MAXIMUM_SIZE (line 82) | pub const MAXIMUM_SIZE: usize = (32 * 2) + 1 + (9 * (1 + 1)) + (32 + 1);
    method start (line 84) | pub fn start(&mut self, players: [Pubkey; 2]) -> Result<()> {
    method is_active (line 91) | pub fn is_active(&self) -> bool {
    method current_player_index (line 95) | fn current_player_index(&self) -> usize {
    method current_player (line 99) | pub fn current_player(&self) -> Pubkey {
    method play (line 103) | pub fn play(&mut self, tile: &Tile) -> Result<()> {
    method is_winning_trio (line 129) | fn is_winning_trio(&self, trio: [(usize, usize); 3]) -> bool {
    method update_state (line 136) | fn update_state(&mut self) {
  type Play (line 62) | pub struct Play<'info> {
  type GameState (line 69) | pub enum GameState {
  type Sign (line 76) | pub enum Sign {
  type Tile (line 182) | pub struct Tile {
  type TicTacToeError (line 188) | pub enum TicTacToeError {

FILE: learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/index.js
  function startWithPossibleValues (line 105) | function startWithPossibleValues() {

FILE: learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/utils.js
  function tileToString (line 11) | function tileToString(tile) {
  function setTiles (line 24) | function setTiles(board) {
  function displayError (line 37) | function displayError(error) {
  function idToTile (line 50) | function idToTile(id) {
  function showLoader (line 61) | function showLoader() {
  function removeLoader (line 65) | function removeLoader() {

FILE: learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/wallet.js
  class Wallet (line 1) | class Wallet {
    method constructor (line 2) | constructor(keypair) {
    method signTransaction (line 6) | async signTransaction(tx) {
    method signAllTransactions (line 10) | async signAllTransactions(txs) {
    method publicKey (line 14) | get publicKey() {

FILE: learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/programs/tic-tac-toe/src/lib.rs
  function setup_game (line 11) | pub fn setup_game(
  function play (line 24) | pub fn play(ctx: Context<Play>, tile: Tile) -> Result<()> {
  type SetupGame (line 39) | pub struct SetupGame<'info> {
  type Game (line 54) | pub struct Game {
    constant MAXIMUM_SIZE (line 82) | pub const MAXIMUM_SIZE: usize = (32 * 2) + 1 + (9 * (1 + 1)) + (32 + 1);
    method start (line 84) | pub fn start(&mut self, players: [Pubkey; 2]) -> Result<()> {
    method is_active (line 91) | pub fn is_active(&self) -> bool {
    method current_player_index (line 95) | fn current_player_index(&self) -> usize {
    method current_player (line 99) | pub fn current_player(&self) -> Pubkey {
    method play (line 103) | pub fn play(&mut self, tile: &Tile) -> Result<()> {
    method is_winning_trio (line 129) | fn is_winning_trio(&self, trio: [(usize, usize); 3]) -> bool {
    method update_state (line 136) | fn update_state(&mut self) {
  type Play (line 62) | pub struct Play<'info> {
  type GameState (line 69) | pub enum GameState {
  type Sign (line 76) | pub enum Sign {
  type Tile (line 182) | pub struct Tile {
  type TicTacToeError (line 188) | pub enum TicTacToeError {

FILE: learn-how-to-build-a-client-side-app-part-2/app/index.js
  function startWithPossibleValues (line 120) | function startWithPossibleValues() {

FILE: learn-how-to-build-a-client-side-app-part-2/app/utils.js
  function tileToString (line 11) | function tileToString(tile) {
  function setTiles (line 24) | function setTiles(board) {
  function displayError (line 37) | function displayError(error) {
  function idToTile (line 50) | function idToTile(id) {
  function showLoader (line 61) | function showLoader() {
  function removeLoader (line 65) | function removeLoader() {

FILE: learn-how-to-build-a-client-side-app-part-2/app/wallet.js
  class Wallet (line 1) | class Wallet {
    method constructor (line 2) | constructor(keypair) {
    method signTransaction (line 6) | async signTransaction(tx) {
    method signAllTransactions (line 10) | async signAllTransactions(txs) {
    method publicKey (line 14) | get publicKey() {

FILE: learn-how-to-build-a-client-side-app-part-2/app/web3.js
  constant PROGRAM_ID (line 14) | const PROGRAM_ID = new PublicKey(
  function connectWallet (line 20) | function connectWallet() {
  function startGame (line 31) | async function startGame() {
  function handlePlay (line 61) | async function handlePlay(id) {
  function deriveGamePublicKey (line 82) | function deriveGamePublicKey(playerOnePublicKey, gameId, programId) {
  function getGameAccount (line 90) | async function getGameAccount() {
  function updateBoard (line 98) | async function updateBoard() {

FILE: learn-how-to-build-a-client-side-app-part-2/tic_tac_toe.ts
  type TicTacToe (line 1) | type TicTacToe = {
  constant IDL (line 185) | const IDL: TicTacToe = {

FILE: learn-how-to-build-for-mainnet/_answer/app/src/app.tsx
  constant PROGRAM_ID (line 31) | const PROGRAM_ID = new PublicKey(
  constant ENDPOINT (line 34) | const ENDPOINT =
  function App (line 40) | function App() {
  function LogIn (line 61) | function LogIn({
  function Landing (line 76) | function Landing() {
  function TodoC (line 206) | function TodoC({
  function Form (line 313) | function Form({ addTask }: FormT) {
  function FilterButton (line 347) | function FilterButton({ name, isPressed, setFilter }: FilterButtonT) {

FILE: learn-how-to-build-for-mainnet/_answer/app/src/utils.ts
  type Task (line 4) | type Task = {
  type TodoT (line 10) | type TodoT = {
  type FormT (line 19) | type FormT = {
  type FilterButtonT (line 23) | type FilterButtonT = {
  type Filters (line 29) | type Filters = keyof typeof FILTER_MAP;
  constant FILTER_MAP (line 31) | const FILTER_MAP = {
  constant FILTER_NAMES (line 37) | const FILTER_NAMES = Object.keys(FILTER_MAP) as Array<
  function usePrevious (line 41) | function usePrevious<T>(value: T) {
  function isWalletConnected (line 49) | function isWalletConnected(wallet: any): wallet is Wallet {

FILE: learn-how-to-build-for-mainnet/_answer/programs/todo/src/lib.rs
  constant TASK_SIZE (line 5) | const TASK_SIZE: usize = 4 + (4 + 32) + 1;
  function save_tasks (line 11) | pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -...
  type SaveTasks (line 66) | pub struct SaveTasks<'info> {
  type TasksAccount (line 76) | pub struct TasksAccount {
  type Task (line 81) | pub struct Task {
  type ErrorCode (line 89) | pub enum ErrorCode {

FILE: learn-how-to-build-for-mainnet/todo/app/src/app.tsx
  function App (line 28) | function App() {
  function LogIn (line 48) | function LogIn({
  function Landing (line 63) | function Landing() {
  function TodoC (line 171) | function TodoC({
  function Form (line 278) | function Form({ addTask }: FormT) {
  function FilterButton (line 312) | function FilterButton({ name, isPressed, setFilter }: FilterButtonT) {

FILE: learn-how-to-build-for-mainnet/todo/app/src/utils.ts
  type Task (line 4) | type Task = {
  type TodoT (line 10) | type TodoT = {
  type FormT (line 19) | type FormT = {
  type FilterButtonT (line 23) | type FilterButtonT = {
  type Filters (line 29) | type Filters = keyof typeof FILTER_MAP;
  constant FILTER_MAP (line 31) | const FILTER_MAP = {
  constant FILTER_NAMES (line 37) | const FILTER_NAMES = Object.keys(FILTER_MAP) as Array<
  function usePrevious (line 41) | function usePrevious<T>(value: T) {
  function isWalletConnected (line 49) | function isWalletConnected(wallet: any): wallet is Wallet {

FILE: learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs
  function initialize (line 9) | pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
  type Initialize (line 15) | pub struct Initialize {}

FILE: learn-how-to-deploy-to-devnet/todo/app/src/app.tsx
  constant PROGRAM_ID (line 31) | const PROGRAM_ID = new PublicKey(
  constant ENDPOINT (line 35) | const ENDPOINT =
  function App (line 41) | function App() {
  function LogIn (line 62) | function LogIn({
  function Landing (line 77) | function Landing() {
  function TodoC (line 207) | function TodoC({
  function Form (line 314) | function Form({ addTask }: FormT) {
  function FilterButton (line 348) | function FilterButton({ name, isPressed, setFilter }: FilterButtonT) {

FILE: learn-how-to-deploy-to-devnet/todo/app/src/utils.ts
  type Task (line 4) | type Task = {
  type TodoT (line 10) | type TodoT = {
  type FormT (line 19) | type FormT = {
  type FilterButtonT (line 23) | type FilterButtonT = {
  type Filters (line 29) | type Filters = keyof typeof FILTER_MAP;
  constant FILTER_MAP (line 31) | const FILTER_MAP = {
  constant FILTER_NAMES (line 37) | const FILTER_NAMES = Object.keys(FILTER_MAP) as Array<
  function usePrevious (line 41) | function usePrevious<T>(value: T) {
  function isWalletConnected (line 49) | function isWalletConnected(wallet: any): wallet is Wallet {

FILE: learn-how-to-deploy-to-devnet/todo/programs/todo/src/lib.rs
  constant TASK_SIZE (line 5) | const TASK_SIZE: usize = 4 + (4 + 32) + 1;
  function save_tasks (line 11) | pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -...
  type SaveTasks (line 66) | pub struct SaveTasks<'info> {
  type TasksAccount (line 76) | pub struct TasksAccount {
  type Task (line 81) | pub struct Task {
  type ErrorCode (line 89) | pub enum ErrorCode {

FILE: learn-how-to-interact-with-on-chain-programs/src/_answer/client/hello-world.js
  function establishConnection (line 13) | function establishConnection() {
  function establishPayer (line 17) | async function establishPayer() {
  function getProgramId (line 26) | async function getProgramId() {
  function getAccountPubkey (line 36) | async function getAccountPubkey(payer, programId) {
  function checkProgram (line 44) | async function checkProgram(
  class HelloWorldAccount (line 63) | class HelloWorldAccount {
    method constructor (line 64) | constructor(fields) {
  constant ACCOUNT_SIZE (line 75) | const ACCOUNT_SIZE = borsh.serialize(
  function createAccount (line 80) | async function createAccount(
  function sayHello (line 105) | async function sayHello(connection, payer, programId, accountPubkey) {
  function getHelloCount (line 119) | async function getHelloCount(connection, accountPubkey) {

FILE: learn-how-to-interact-with-on-chain-programs/src/_answer/client/main.js
  function main (line 11) | async function main() {

FILE: learn-how-to-interact-with-on-chain-programs/src/program-rust/src/lib.rs
  function process_instruction (line 9) | pub fn process_instruction(
  type GreetingAccount (line 40) | pub struct GreetingAccount {

FILE: learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/hello_world.ts
  constant GREETING_SEED (line 17) | const GREETING_SEED = 'hello';
  constant PROGRAM_PATH (line 22) | const PROGRAM_PATH = resolve(__dirname, '../../dist/program');
  constant PROGRAM_SO_PATH (line 30) | const PROGRAM_SO_PATH = join(PROGRAM_PATH, 'helloworld.so');
  constant PROGRAM_KEYPAIR_PATH (line 36) | const PROGRAM_KEYPAIR_PATH = join(PROGRAM_PATH, 'helloworld-keypair.json');
  class HelloWorldAccount (line 41) | class HelloWorldAccount {
    method constructor (line 43) | constructor(fields: { counter: number } | undefined = undefined) {
  constant ACCOUNT_SIZE (line 60) | const ACCOUNT_SIZE = borsh.serialize(
  function establishConnection (line 68) | async function establishConnection(): Promise<Connection> {
  function establishPayer (line 79) | async function establishPayer(connection: Connection): Promise<Keypair> {
  function getProgramId (line 90) | async function getProgramId(): Promise<PublicKey> {
  function getAccountPubkey (line 102) | async function getAccountPubkey(
  function checkProgram (line 117) | async function checkProgram(
  function createAccount (line 145) | async function createAccount(
  function sayHello (line 175) | async function sayHello(
  function reportGreetings (line 197) | async function reportGreetings(

FILE: learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/main.ts
  function main (line 11) | async function main() {

FILE: learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/utils.ts
  function getConfig (line 10) | async function getConfig(): Promise<any> {
  function getRpcUrl (line 26) | async function getRpcUrl(): Promise<string> {
  function getPayer (line 42) | async function getPayer(): Promise<Keypair> {
  function createKeypairFromFile (line 58) | async function createKeypairFromFile(

FILE: learn-solanas-token-program-by-minting-a-fungible-token/utils.js
  constant MINT_ADDRESS_58 (line 11) | const MINT_ADDRESS_58 = '';
  constant MINT_AUTHORITY_58 (line 13) | const MINT_AUTHORITY_58 = payer.publicKey.toBase58();
  constant TOKEN_ACCOUNT_58 (line 14) | const TOKEN_ACCOUNT_58 = '';

FILE: learn-the-metaplex-sdk-by-minting-an-nft/spl-program/utils.js
  constant MINT_ADDRESS_58 (line 12) | const MINT_ADDRESS_58 = pkg.env.MINT_ACCOUNT_ADDRESS;
  constant MINT_AUTHORITY_58 (line 14) | const MINT_AUTHORITY_58 = payer.publicKey.toBase58();
  constant TOKEN_ACCOUNT_58 (line 15) | const TOKEN_ACCOUNT_58 = pkg.env.TOKEN_ACCOUNT_ADDRESS;

FILE: learn-the-metaplex-sdk-by-minting-an-nft/utils.js
  constant WALLET_KEYPAIR (line 10) | const WALLET_KEYPAIR = Keypair.fromSecretKey(new Uint8Array(t));
  function localStorage (line 15) | function localStorage(options) {
  class LocalStorageDriver (line 23) | class LocalStorageDriver {
    method constructor (line 24) | constructor(options) {
    method getUploadPrice (line 31) | async getUploadPrice(bytes) {
    method upload (line 35) | async upload(file) {
    method download (line 46) | async download(uri) {

FILE: tooling/camper-info.js
  constant FLAGS (line 23) | const FLAGS = process.argv;
  function main (line 25) | async function main() {
  constant IGNORE (line 88) | const IGNORE = [
  function recurseDirectory (line 96) | async function recurseDirectory(path, depth) {

FILE: tooling/helpers.js
  function rustTest (line 10) | async function rustTest(path, filePath, test, cb) {
  function getCamperKeypair (line 40) | async function getCamperKeypair() {
  function getSOFile (line 47) | async function getSOFile() {
  function getProgramJSONFile (line 55) | async function getProgramJSONFile() {
  function getProgramKeypair (line 63) | async function getProgramKeypair() {
  function getDataAccountPublicKey (line 72) | async function getDataAccountPublicKey() {
  function establishConnection (line 83) | function establishConnection() {
  class MessageAccount (line 87) | class MessageAccount {
    method constructor (line 88) | constructor(fields) {
  function setMessage (line 97) | async function setMessage(
  function getMessage (line 117) | async function getMessage(connection, accountPubkey) {

FILE: tooling/rejig.js
  constant PATH (line 4) | const PATH = process.argv[2]?.trim();
  constant CURRICULUM_PATH (line 6) | const CURRICULUM_PATH = 'curriculum/locales/english';
  function rejigFile (line 11) | async function rejigFile(fileName) {

FILE: tooling/seed.js
  constant PATH (line 16) | const PATH =
  constant LESSON_NUMBER (line 19) | const LESSON_NUMBER = Number(process.argv[2]);
  constant FILE (line 20) | const FILE = join(process.argv[3]);
Condensed preview — 208 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,142K chars).
[
  {
    "path": ".devcontainer.json",
    "chars": 350,
    "preview": "{\n  \"customizations\": {\n    \"vscode\": {\n      \"extensions\": [\n        \"dbaeumer.vscode-eslint\",\n        \"freeCodeCamp.fr"
  },
  {
    "path": ".editorconfig",
    "chars": 241,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/BUG.md",
    "chars": 186,
    "preview": "---\nname: Bug Found\nabout: Report a bug with the platform/content\ntitle: '[BUG]: '\n---\n\n### Issue/Experience\n\n### Output"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/HELP.md",
    "chars": 157,
    "preview": "---\nname: Help Needed\nabout: Get help with a project or lesson\ntitle: '[HELP]: '\n---\n\n### Project\n\n### Lesson Number\n\n##"
  },
  {
    "path": ".gitignore",
    "chars": 129,
    "preview": "target\nnode_modules\nCargo.lock\n!.gitkeep\n__test\n\n# Should be included in repo, but does not get updated\n.logs\ntest-ledge"
  },
  {
    "path": ".gitpod.Dockerfile",
    "chars": 756,
    "preview": "FROM gitpod/workspace-full:2024-05-22-07-25-51\n\nARG REPO_NAME=solana-curriculum\nARG HOMEDIR=/workspace/$REPO_NAME\n\nWORKD"
  },
  {
    "path": ".gitpod.yml",
    "chars": 489,
    "preview": "image:\n  file: .gitpod.Dockerfile\n\n# Commands to start on workspace startup\ntasks:\n  - init: npm ci\n\nports:\n  - port: 80"
  },
  {
    "path": ".prettierignore",
    "chars": 38,
    "preview": "**/.cache\n**/package-lock.json\n**/pkg\n"
  },
  {
    "path": ".prettierrc",
    "chars": 159,
    "preview": "{\n  \"endOfLine\": \"lf\",\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"jsxSingleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComm"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 1756,
    "preview": "{\n  \"files.exclude\": {\n    \".devcontainer.json\": false,\n    \".editorconfig\": false,\n    \".git\": false,\n    \".github\": fa"
  },
  {
    "path": "Dockerfile",
    "chars": 1766,
    "preview": "FROM ubuntu:20.04\n\nARG USERNAME=camper\nARG REPO_NAME=solana-curriculum\nARG HOMEDIR=/workspace/$REPO_NAME\n\nENV TZ=\"Americ"
  },
  {
    "path": "LICENSE",
    "chars": 1525,
    "preview": "BSD 3-Clause License\n\nCopyright (c) 2022-2023, freeCodeCamp\nAll rights reserved.\n\nRedistribution and use in source and b"
  },
  {
    "path": "README.md",
    "chars": 3749,
    "preview": "# freeCodeCamp: Solana Curriculum\n\nGet started here: https://web3.freecodecamp.org/solana\n\n## Projects\n\n| Project       "
  },
  {
    "path": "bash/.bashrc",
    "chars": 4394,
    "preview": "# ~/.bashrc: executed by bash(1) for non-login shells.\n# see /usr/share/doc/bash/examples/startup-files (in the package "
  },
  {
    "path": "bash/sourcerer.sh",
    "chars": 56,
    "preview": "#!/bin/bash\nsource ./bash/.bashrc\necho \"BashRC Sourced\"\n"
  },
  {
    "path": "build-a-client-side-app/.gitignore",
    "chars": 4,
    "preview": "mess"
  },
  {
    "path": "build-a-client-side-app/mess.json",
    "chars": 1511,
    "preview": "{\n  \"version\": \"0.1.0\",\n  \"name\": \"mess\",\n  \"instructions\": [\n    {\n      \"name\": \"init\",\n      \"accounts\": [\n        {\n"
  },
  {
    "path": "build-a-client-side-app/mess.ts",
    "chars": 3071,
    "preview": "export type Mess = {\n  \"version\": \"0.1.0\",\n  \"name\": \"mess\",\n  \"instructions\": [\n    {\n      \"name\": \"init\",\n      \"acco"
  },
  {
    "path": "build-a-smart-contract/package.json",
    "chars": 336,
    "preview": "{\n  \"name\": \"build-a-smart-contract\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@solana/web3.js\": \"1.87.7\",\n    \"bors"
  },
  {
    "path": "build-a-smart-contract/program/Cargo.toml",
    "chars": 298,
    "preview": "[package]\nname = \"message\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust"
  },
  {
    "path": "build-a-smart-contract/program/src/lib.rs",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "build-a-smart-contract/program/tests/process_instruction.rs",
    "chars": 4051,
    "preview": "extern crate message;\nuse message::process_instruction;\nuse borsh::BorshDeserialize;\n\nuse solana_program::{account_info:"
  },
  {
    "path": "build-a-smart-contract/wallet.json",
    "chars": 230,
    "preview": "[143,176,178,47,104,172,153,69,159,235,167,100,198,176,237,12,40,244,4,227,138,109,255,63,148,130,55,85,138,167,29,47,11"
  },
  {
    "path": "build-a-university-certification-nft/client/.gitignore",
    "chars": 253,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "build-a-university-certification-nft/client/index.html",
    "chars": 480,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "build-a-university-certification-nft/client/package.json",
    "chars": 756,
    "preview": "{\n  \"name\": \"client\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"npm i && v"
  },
  {
    "path": "build-a-university-certification-nft/client/src/app.css",
    "chars": 229,
    "preview": "#root {\n  max-width: 1280px;\n  margin: 0 auto;\n  padding: 2rem;\n  text-align: center;\n}\n\nform {\n  display: flex;\n  flex-"
  },
  {
    "path": "build-a-university-certification-nft/client/src/app.tsx",
    "chars": 10344,
    "preview": "import { ChangeEvent, useEffect, useRef, useState } from 'react';\nimport { Keypair, PublicKey, Signer } from '@solana/we"
  },
  {
    "path": "build-a-university-certification-nft/client/src/index.css",
    "chars": 1690,
    "preview": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n"
  },
  {
    "path": "build-a-university-certification-nft/client/src/main.tsx",
    "chars": 254,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './app';\nimport './index.css';\n\n"
  },
  {
    "path": "build-a-university-certification-nft/client/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "build-a-university-certification-nft/client/tsconfig.json",
    "chars": 559,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\","
  },
  {
    "path": "build-a-university-certification-nft/client/tsconfig.node.json",
    "chars": 184,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSynthe"
  },
  {
    "path": "build-a-university-certification-nft/client/vite.config.ts",
    "chars": 939,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nimport { NodeGlobalsPolyfillPlugin } fro"
  },
  {
    "path": "build-a-university-certification-nft/index.d.ts",
    "chars": 1096,
    "preview": "import { MetaplexFile, CreateNftOutput, FindNftsByOwnerOutput } from '@metaplex-foundation/js';\nimport { Account, create"
  },
  {
    "path": "build-a-university-certification-nft/index.js",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "build-a-university-certification-nft/metadatas.json",
    "chars": 3,
    "preview": "{}\n"
  },
  {
    "path": "build-a-university-certification-nft/package.json",
    "chars": 259,
    "preview": "{\n  \"name\": \"build-a-university-certification-nft\",\n  \"scripts\": {\n    \"start:server\": \"node server.js\",\n    \"start:clie"
  },
  {
    "path": "build-a-university-certification-nft/server.js",
    "chars": 1271,
    "preview": "import { readFileSync, writeFileSync } from 'fs';\nimport cors from 'cors';\nimport express from 'express';\nimport { setDe"
  },
  {
    "path": "build-a-university-certification-nft/utils.js",
    "chars": 1127,
    "preview": "export function localStorage(options) {\n  return {\n    install(metaplex) {\n      metaplex.storage().setDriver(new LocalS"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/.gitignore",
    "chars": 62,
    "preview": "\n.anchor\n.DS_Store\ntarget\n**/*.rs.bk\nnode_modules\ntest-ledger\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/.prettierignore",
    "chars": 62,
    "preview": "\n.anchor\n.DS_Store\ntarget\nnode_modules\ndist\nbuild\ntest-ledger\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/Anchor.toml",
    "chars": 318,
    "preview": "[features]\nseeds = false\nskip-lint = false\n[programs.localnet]\nrock_destroyer = \"Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zP"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/Cargo.toml",
    "chars": 200,
    "preview": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[prof"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/migrations/deploy.ts",
    "chars": 405,
    "preview": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked fro"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/package.json",
    "chars": 441,
    "preview": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/Cargo.toml",
    "chars": 306,
    "preview": "[package]\nname = \"rock-destroyer\"\nversion = \"0.1.0\"\ndescription = \"Created with Anchor\"\nedition = \"2021\"\n\n[lib]\ncrate-ty"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/Xargo.toml",
    "chars": 62,
    "preview": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/src/lib.rs",
    "chars": 280,
    "preview": "use anchor_lang::prelude::*;\n\ndeclare_id!(\"Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS\");\n\n#[program]\npub mod rock_dest"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/tests/rock-destroyer.ts",
    "chars": 554,
    "preview": "import * as anchor from \"@coral-xyz/anchor\";\nimport { Program } from \"@coral-xyz/anchor\";\nimport { RockDestroyer } from "
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/tsconfig.json",
    "chars": 305,
    "preview": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules"
  },
  {
    "path": "build-and-deploy-your-freeform-app/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "config/projects.json",
    "chars": 5971,
    "preview": "[\n  {\n    \"id\": 0,\n    \"title\": \"Learn How to Set Up Solana by Building a Hello World Smart Contract\",\n    \"dashedName\":"
  },
  {
    "path": "config/state.json",
    "chars": 51,
    "preview": "{\n  \"currentProject\": null,\n  \"locale\": \"english\"\n}"
  },
  {
    "path": "curriculum/locales/english/build-a-client-side-app.md",
    "chars": 6442,
    "preview": "# Solana - Build a Client Side App\n\n## 1\n\n### --description--\n\nYou are developing a client-side app to interact with the"
  },
  {
    "path": "curriculum/locales/english/build-a-smart-contract.md",
    "chars": 11143,
    "preview": "# Solana - Build a Smart Contract\n\n## 1\n\n### --description--\n\nYou need to create a smart contract in Rust, deploy the co"
  },
  {
    "path": "curriculum/locales/english/build-a-university-certification-nft.md",
    "chars": 20598,
    "preview": "# Solana - Build a University Certification NFT\n\n## 1\n\n### --description--\n\nYou have been contacted by Solana University"
  },
  {
    "path": "curriculum/locales/english/build-an-anchor-leaderboard.md",
    "chars": 14065,
    "preview": "# Solana - Build an Anchor Leaderboard\n\n## 1\n\n### --description--\n\nYou are developing an on-chain game called _Rock Dest"
  },
  {
    "path": "curriculum/locales/english/build-and-deploy-your-freeform-app.md",
    "chars": 1333,
    "preview": "# Solana - Build and Deploy Your Freeform App\n\n## 1\n\n### --description--\n\nCongratulations on making it to the final proj"
  },
  {
    "path": "curriculum/locales/english/learn-anchor-by-building-tic-tac-toe-part-1.md",
    "chars": 82810,
    "preview": "# Solana - Learn Anchor by Building Tic-Tac-Toe: Part 1\n\n## 1\n\n### --description--\n\nPreviously, you built and deployed a"
  },
  {
    "path": "curriculum/locales/english/learn-anchor-by-building-tic-tac-toe-part-2.md",
    "chars": 134644,
    "preview": "# Solana - Learn How to Test an Anchor Program: Part 2\n\n## 1\n\n### --description--\n\nIn the previous project, you used Anc"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-build-a-client-side-app-part-1.md",
    "chars": 63257,
    "preview": "# Solana - Learn How to Build a Client-Side App: Part 1\n\n## 1\n\n### --description--\n\nPreviously, you built, tested, and d"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-build-a-client-side-app-part-2.md",
    "chars": 22652,
    "preview": "# Solana - Learn How to Build a Client-Side App: Part 2\n\n## 1\n\n### --description--\n\nIn this project, you will learn how "
  },
  {
    "path": "curriculum/locales/english/learn-how-to-build-for-mainnet.md",
    "chars": 93141,
    "preview": "# Solana - Learn How to Build for Mainnet\n\n## 1\n\n### --description--\n\nYou have been started off with an Anchor full-stac"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-deploy-to-devnet.md",
    "chars": 12154,
    "preview": "# Solana - Learn How to Deploy to Devnet\n\n## 1\n\n### --description--\n\nYou have been started with the same programa and ap"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-interact-with-on-chain-programs.md",
    "chars": 184659,
    "preview": "# Solana - Learn How to Interact with On-Chain Programs\n\n## 1\n\n### --description--\n\nWelcome to the second Solana project"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract.md",
    "chars": 43383,
    "preview": "# Solana - Learn How to Set Up Solana by Building a Hello World Smart Contract\n\n## 1\n\n### --description--\n\nWelcome to th"
  },
  {
    "path": "curriculum/locales/english/learn-solanas-token-program-by-minting-a-fungible-token.md",
    "chars": 96127,
    "preview": "# Solana - Learn Solana's Token Program by Minting a Fungible Token\n\n## 1\n\n### --description--\n\nIn this project, you wil"
  },
  {
    "path": "curriculum/locales/english/learn-the-metaplex-sdk-by-minting-an-nft.md",
    "chars": 83448,
    "preview": "# Solana - Learn the Metaplex SDK by Minting an NFT\n\n## 1\n\n### --description--\n\nIn this project, you will learn how to u"
  },
  {
    "path": "freecodecamp.conf.json",
    "chars": 1502,
    "preview": "{\n  \"path\": \".\",\n  \"version\": \"1.0.3\",\n  \"scripts\": {\n    \"develop-course\": \"NODE_ENV=development node ./node_modules/@f"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-1/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/.prettierignore",
    "chars": 62,
    "preview": "\n.anchor\n.DS_Store\ntarget\nnode_modules\ndist\nbuild\ntest-ledger\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/Anchor.toml",
    "chars": 315,
    "preview": "[features]\nseeds = false\nskip-lint = false\n[programs.localnet]\ntic_tac_toe = \"BUfb6FXLkiSpMnJnMR4Q5uGZYZkaNGytjhLwiiJQsE"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/Cargo.toml",
    "chars": 200,
    "preview": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[prof"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/migrations/deploy.ts",
    "chars": 405,
    "preview": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked fro"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/package.json",
    "chars": 497,
    "preview": "{\n    \"scripts\": {\n        \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n        \"lint\": \"prettier */*.js \\\"*/**"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/programs/tic-tac-toe/Cargo.toml",
    "chars": 338,
    "preview": "[package]\nname = \"tic-tac-toe\"\nversion = \"0.1.0\"\ndescription = \"Created with Anchor\"\nedition = \"2021\"\n\n[lib]\ncrate-type "
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/programs/tic-tac-toe/Xargo.toml",
    "chars": 62,
    "preview": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/programs/tic-tac-toe/src/lib.rs",
    "chars": 5244,
    "preview": "use anchor_lang::prelude::*;\nuse num_derive;\nuse num_traits::FromPrimitive;\n\ndeclare_id!(\"BUfb6FXLkiSpMnJnMR4Q5uGZYZkaNG"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/tests/tic-tac-toe.ts",
    "chars": 516,
    "preview": "import {\n  AnchorProvider,\n  workspace,\n  setProvider,\n  Program\n} from '@coral-xyz/anchor';\nimport { TicTacToe } from '"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/tsconfig.json",
    "chars": 305,
    "preview": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/.gitignore",
    "chars": 68,
    "preview": "\n.anchor\n.DS_Store\ntarget\n**/*.rs.bk\nnode_modules\ntest-ledger\n.yarn\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/Anchor.toml",
    "chars": 315,
    "preview": "[features]\nseeds = false\nskip-lint = false\n[programs.localnet]\ntic_tac_toe = \"5xGwZASoE5ZgxKgaisJNaGTGzMKzjyyBGv9FCUtu2m"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/Cargo.toml",
    "chars": 200,
    "preview": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[prof"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/.gitignore",
    "chars": 253,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/index.css",
    "chars": 1513,
    "preview": "body {\n  background: black;\n  width: 100%;\n  height: 100vh;\n  margin: 0;\n  padding: 0;\n}\n\nform,\ntable {\n  color: whitesm"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/index.html",
    "chars": 2054,
    "preview": "<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-sc"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/index.js",
    "chars": 4135,
    "preview": "import { Keypair, PublicKey } from '@solana/web3.js';\nimport player_one_keypair from '../player-one.json';\nimport player"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/package.json",
    "chars": 247,
    "preview": "{\n  \"name\": \"app\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"b"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/utils.js",
    "chars": 1364,
    "preview": "const errorsEl = document.getElementById('errors');\nconst spinnerEl = document.getElementById('spinner');\n\nconst tableBo"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/wallet.js",
    "chars": 347,
    "preview": "export class Wallet {\n  constructor(keypair) {\n    this.keypair = keypair;\n    this._publicKey = keypair.publicKey;\n  }\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/migrations/deploy.ts",
    "chars": 405,
    "preview": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked fro"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/package.json",
    "chars": 415,
    "preview": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/programs/tic-tac-toe/Cargo.toml",
    "chars": 338,
    "preview": "[package]\nname = \"tic-tac-toe\"\nversion = \"0.1.0\"\ndescription = \"Created with Anchor\"\nedition = \"2021\"\n\n[lib]\ncrate-type "
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/programs/tic-tac-toe/Xargo.toml",
    "chars": 62,
    "preview": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/programs/tic-tac-toe/src/lib.rs",
    "chars": 5244,
    "preview": "use anchor_lang::prelude::*;\nuse num_derive;\nuse num_traits::FromPrimitive;\n\ndeclare_id!(\"5xGwZASoE5ZgxKgaisJNaGTGzMKzjy"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/tests/tic-tac-toe.ts",
    "chars": 536,
    "preview": "import * as anchor from \"@coral-xyz/anchor\";\nimport { Program } from \"@coral-xyz/anchor\";\nimport { TicTacToe } from \"../"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/tsconfig.json",
    "chars": 305,
    "preview": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/.gitignore",
    "chars": 253,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/index.css",
    "chars": 1513,
    "preview": "body {\n  background: black;\n  width: 100%;\n  height: 100vh;\n  margin: 0;\n  padding: 0;\n}\n\nform,\ntable {\n  color: whitesm"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/index.html",
    "chars": 2054,
    "preview": "<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-sc"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/index.js",
    "chars": 4452,
    "preview": "import { Keypair, PublicKey } from '@solana/web3.js';\nimport player_one_keypair from '../player-one.json';\nimport player"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/package.json",
    "chars": 324,
    "preview": "{\n  \"name\": \"app\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"b"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/utils.js",
    "chars": 1364,
    "preview": "const errorsEl = document.getElementById('errors');\nconst spinnerEl = document.getElementById('spinner');\n\nconst tableBo"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/wallet.js",
    "chars": 347,
    "preview": "export class Wallet {\n  constructor(keypair) {\n    this.keypair = keypair;\n    this._publicKey = keypair.publicKey;\n  }\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/web3.js",
    "chars": 3177,
    "preview": "import { AnchorProvider, Program, setProvider } from '@coral-xyz/anchor';\nimport { Wallet } from './wallet.js';\nimport {"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/tic_tac_toe.ts",
    "chars": 6499,
    "preview": "export type TicTacToe = {\n  \"version\": \"0.1.0\",\n  \"name\": \"tic_tac_toe\",\n  \"instructions\": [\n    {\n      \"name\": \"setupG"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/Anchor.toml",
    "chars": 330,
    "preview": "[features]\nseeds = false\nskip-lint = false\nskip-preflight = true\n[programs.localnet]\ntodo = \"9a43FDYE3S98dfN1rPAeavJT6Mz"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/Cargo.toml",
    "chars": 200,
    "preview": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[prof"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/index.html",
    "chars": 353,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/package.json",
    "chars": 850,
    "preview": "{\n  \"name\": \"todo\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \""
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/app.css",
    "chars": 4929,
    "preview": "/* RESETS */\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n*:focus {\n  outline: 3px dashed #228bec;\n  outline-off"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/app.tsx",
    "chars": 9264,
    "preview": "import {\n  ChangeEvent,\n  FormEvent,\n  MouseEventHandler,\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useSta"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/index.css",
    "chars": 389,
    "preview": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/main.tsx",
    "chars": 240,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './app';\nimport './index.css';\n\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/utils.ts",
    "chars": 1085,
    "preview": "import { Wallet } from '@coral-xyz/anchor';\nimport { useEffect, useRef } from 'react';\n\nexport type Task = {\n  id: numbe"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/tsconfig.json",
    "chars": 605,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM."
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/tsconfig.node.json",
    "chars": 213,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\""
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/vite.config.ts",
    "chars": 182,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vitejs.dev/config/\nexport def"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/migrations/deploy.ts",
    "chars": 405,
    "preview": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked fro"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/package.json",
    "chars": 442,
    "preview": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/programs/todo/Cargo.toml",
    "chars": 331,
    "preview": "[package]\nname = \"todo\"\nversion = \"0.1.0\"\ndescription = \"A todo list program\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdy"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/programs/todo/Xargo.toml",
    "chars": 62,
    "preview": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/programs/todo/src/lib.rs",
    "chars": 2956,
    "preview": "use anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\nconst TASK_SIZE: usize = 4 +"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/tests/todo.ts",
    "chars": 1203,
    "preview": "import * as anchor from '@coral-xyz/anchor';\nimport { Program } from '@coral-xyz/anchor';\nimport { Todo } from '../targe"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/tsconfig.json",
    "chars": 305,
    "preview": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/.gitignore",
    "chars": 68,
    "preview": "\n.anchor\n.DS_Store\ntarget\n**/*.rs.bk\nnode_modules\ntest-ledger\n.yarn\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/.prettierignore",
    "chars": 62,
    "preview": "\n.anchor\n.DS_Store\ntarget\nnode_modules\ndist\nbuild\ntest-ledger\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/Anchor.toml",
    "chars": 330,
    "preview": "[features]\nseeds = false\nskip-lint = false\nskip-preflight = true\n[programs.localnet]\ntodo = \"9a43FDYE3S98dfN1rPAeavJT6Mz"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/Cargo.toml",
    "chars": 200,
    "preview": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[prof"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/index.html",
    "chars": 353,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/package.json",
    "chars": 727,
    "preview": "{\n  \"name\": \"todo\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \""
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/app.css",
    "chars": 4929,
    "preview": "/* RESETS */\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n*:focus {\n  outline: 3px dashed #228bec;\n  outline-off"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/app.tsx",
    "chars": 8046,
    "preview": "import {\n  ChangeEvent,\n  FormEvent,\n  MouseEventHandler,\n  useEffect,\n  useRef,\n  useState\n} from 'react';\nimport './ap"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/index.css",
    "chars": 389,
    "preview": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/main.tsx",
    "chars": 240,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './app';\nimport './index.css';\n\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/utils.ts",
    "chars": 1085,
    "preview": "import { Wallet } from '@coral-xyz/anchor';\nimport { useEffect, useRef } from 'react';\n\nexport type Task = {\n  id: numbe"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/tsconfig.json",
    "chars": 605,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM."
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/tsconfig.node.json",
    "chars": 213,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\""
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/vite.config.ts",
    "chars": 182,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vitejs.dev/config/\nexport def"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/migrations/deploy.ts",
    "chars": 405,
    "preview": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked fro"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/package.json",
    "chars": 442,
    "preview": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/programs/todo/Cargo.toml",
    "chars": 315,
    "preview": "[package]\nname = \"todo\"\nversion = \"0.1.0\"\ndescription = \"A todo list program\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdy"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/programs/todo/Xargo.toml",
    "chars": 62,
    "preview": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs",
    "chars": 270,
    "preview": "use anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n  "
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/tests/todo.ts",
    "chars": 1203,
    "preview": "import * as anchor from '@coral-xyz/anchor';\nimport { Program } from '@coral-xyz/anchor';\nimport { Todo } from '../targe"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/tsconfig.json",
    "chars": 305,
    "preview": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/Anchor.toml",
    "chars": 308,
    "preview": "[features]\nseeds = false\nskip-lint = false\n[programs.localnet]\ntodo = \"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\"\n\n[r"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/Cargo.toml",
    "chars": 200,
    "preview": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[prof"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/index.html",
    "chars": 353,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/package.json",
    "chars": 850,
    "preview": "{\n  \"name\": \"todo\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \""
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/app.css",
    "chars": 4929,
    "preview": "/* RESETS */\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n*:focus {\n  outline: 3px dashed #228bec;\n  outline-off"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/app.tsx",
    "chars": 9303,
    "preview": "import {\n  ChangeEvent,\n  FormEvent,\n  MouseEventHandler,\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useSta"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/index.css",
    "chars": 389,
    "preview": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/main.tsx",
    "chars": 240,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './app';\nimport './index.css';\n\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/utils.ts",
    "chars": 1085,
    "preview": "import { Wallet } from '@coral-xyz/anchor';\nimport { useEffect, useRef } from 'react';\n\nexport type Task = {\n  id: numbe"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/tsconfig.json",
    "chars": 605,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM."
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/tsconfig.node.json",
    "chars": 213,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\""
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/vite.config.ts",
    "chars": 182,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vitejs.dev/config/\nexport def"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/migrations/deploy.ts",
    "chars": 405,
    "preview": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked fro"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/package.json",
    "chars": 442,
    "preview": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/programs/todo/Cargo.toml",
    "chars": 331,
    "preview": "[package]\nname = \"todo\"\nversion = \"0.1.0\"\ndescription = \"A todo list program\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdy"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/programs/todo/Xargo.toml",
    "chars": 62,
    "preview": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/programs/todo/src/lib.rs",
    "chars": 2956,
    "preview": "use anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\nconst TASK_SIZE: usize = 4 +"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/tests/todo.ts",
    "chars": 1203,
    "preview": "import * as anchor from '@coral-xyz/anchor';\nimport { Program } from '@coral-xyz/anchor';\nimport { Todo } from '../targe"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/tsconfig.json",
    "chars": 305,
    "preview": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/cluster-devnet.env",
    "chars": 22,
    "preview": "LIVE=1\nCLUSTER=devnet\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/cluster-mainnet-beta.env",
    "chars": 28,
    "preview": "LIVE=1\nCLUSTER=mainnet-beta\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/cluster-testnet.env",
    "chars": 23,
    "preview": "LIVE=1\nCLUSTER=testnet\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/package.json",
    "chars": 613,
    "preview": "{\n  \"name\": \"learn-how-to-interact-with-on-chain-programs\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"build\": \"cargo bui"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/_answer/client/hello-world.js",
    "chars": 3170,
    "preview": "import {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction,\n  Transactio"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/_answer/client/main.js",
    "chars": 688,
    "preview": "import {\n  checkProgram,\n  establishConnection,\n  establishPayer,\n  getAccountPubkey,\n  getHelloCount,\n  getProgramId,\n "
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/program-rust/.gitignore",
    "chars": 9,
    "preview": "/target/\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/program-rust/Cargo.toml",
    "chars": 582,
    "preview": "\n[package]\nname = \"solana-sbf-helloworld\"\nversion = \"0.0.1\"\ndescription = \"Hello World program written in Rust\"\nauthors "
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/program-rust/src/lib.rs",
    "chars": 1302,
    "preview": "use borsh::{BorshDeserialize, BorshSerialize};\nuse solana_program::{\n    account_info::AccountInfo, entrypoint, entrypoi"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/.gitignore",
    "chars": 86,
    "preview": "/node_modules\n*.sw[po]\n/.cargo\n/dist\n.env\nsrc/client/util/store\ntest-ledger/\n.DS_Store"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/package.json",
    "chars": 644,
    "preview": "{\n  \"name\": \"learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/hello_world.ts",
    "chars": 5875,
    "preview": "import {\n  Keypair,\n  Connection,\n  PublicKey,\n  LAMPORTS_PER_SOL,\n  SystemProgram,\n  TransactionInstruction,\n  Transact"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/main.ts",
    "chars": 1027,
    "preview": "import {\n  establishConnection,\n  establishPayer,\n  checkProgram,\n  sayHello,\n  reportGreetings,\n  getProgramId,\n  getAc"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/utils.ts",
    "chars": 1778,
    "preview": "import { homedir } from 'os';\nimport { readFile } from 'fs/promises';\nimport { resolve } from 'path';\nimport { parse } f"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/.gitignore",
    "chars": 9,
    "preview": "/target/\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/Cargo.toml",
    "chars": 586,
    "preview": "\n[package]\nname = \"solana-sbf-helloworld\"\nversion = \"0.0.1\"\ndescription = \"Hello World program written in Rust\"\nauthors "
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/tsconfig.json",
    "chars": 306,
    "preview": "{\n  \"extends\": \"@tsconfig/recommended/tsconfig.json\",\n  \"ts-node\": {\n    \"compilerOptions\": {\n      \"module\": \"commonjs\""
  },
  {
    "path": "learn-solanas-token-program-by-minting-a-fungible-token/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "learn-solanas-token-program-by-minting-a-fungible-token/package.json",
    "chars": 92,
    "preview": "{\n  \"name\": \"learn-solanas-token-program-by-minting-a-fungible-token\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "learn-solanas-token-program-by-minting-a-fungible-token/utils.js",
    "chars": 580,
    "preview": "import { Keypair, PublicKey } from '@solana/web3.js';\n\nconst secretKey = (\n  await import('./wallet.json', {\n    assert:"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/package.json",
    "chars": 821,
    "preview": "{\n  \"name\": \"learn-the-metaplex-sdk-by-minting-an-nft\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@solana/spl-token\":"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/server.js",
    "chars": 613,
    "preview": "import express from 'express';\n\nconst app = express();\n\napp.use(express.json());\n\nconst metadatas = {};\n\napp.get('/meta/"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/create-mint-account.js",
    "chars": 429,
    "preview": "import { Connection } from '@solana/web3.js';\nimport { payer } from './utils.js';\nimport { createMint } from '@solana/sp"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/create-token-account.js",
    "chars": 421,
    "preview": "import { Connection } from '@solana/web3.js';\nimport { payer, mintAddress } from './utils.js';\nimport { getOrCreateAssoc"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/get-token-account.js",
    "chars": 491,
    "preview": "import { Connection, PublicKey } from '@solana/web3.js';\nimport { getAssociatedTokenAddress, getAccount } from '@solana/"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/get-token-info.js",
    "chars": 268,
    "preview": "import { Connection } from '@solana/web3.js';\nimport { getMint } from '@solana/spl-token';\nimport { mintAddress } from '"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/mint.js",
    "chars": 334,
    "preview": "import { Connection } from '@solana/web3.js';\nimport { mintTo } from '@solana/spl-token';\nimport { payer, mintAddress, t"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/package.json",
    "chars": 296,
    "preview": "{\n  \"name\": \"fungi-token\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A fungible token on Solana\",\n  \"scripts\": {\n    \"serv"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/transfer.js",
    "chars": 909,
    "preview": "import { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport {\n  getOrCreateAssociatedTokenAccount,\n  getAcc"
  }
]

// ... and 8 more files (download for full content)

About this extraction

This page contains the full source code of the freeCodeCamp/solana-curriculum GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 208 files (1.0 MB), approximately 290.3k tokens, and a symbol index with 236 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!