[
  {
    "path": ".devcontainer.json",
    "content": "{\n  \"customizations\": {\n    \"vscode\": {\n      \"extensions\": [\n        \"dbaeumer.vscode-eslint\",\n        \"freeCodeCamp.freecodecamp-courses@1.7.5\",\n        \"freeCodeCamp.freecodecamp-dark-vscode-theme\"\n      ]\n    }\n  },\n  \"forwardPorts\": [8080],\n  \"workspaceFolder\": \"/workspace/solana-curriculum\",\n  \"dockerFile\": \"./Dockerfile\",\n  \"context\": \".\"\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[package.json]\nindent_style = space\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/BUG.md",
    "content": "---\nname: Bug Found\nabout: Report a bug with the platform/content\ntitle: '[BUG]: '\n---\n\n### Issue/Experience\n\n### Output of running `node tooling/camper-info.js` from the workspace root\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/HELP.md",
    "content": "---\nname: Help Needed\nabout: Get help with a project or lesson\ntitle: '[HELP]: '\n---\n\n### Project\n\n### Lesson Number\n\n### Question\n\n### Code and Screenshots\n"
  },
  {
    "path": ".gitignore",
    "content": "target\nnode_modules\nCargo.lock\n!.gitkeep\n__test\n\n# Should be included in repo, but does not get updated\n.logs\ntest-ledger\n.anchor"
  },
  {
    "path": ".gitpod.Dockerfile",
    "content": "FROM gitpod/workspace-full:2024-05-22-07-25-51\n\nARG REPO_NAME=solana-curriculum\nARG HOMEDIR=/workspace/$REPO_NAME\n\nWORKDIR ${HOMEDIR}\n\nRUN bash -c 'VERSION=\"20\" \\\n    && source $HOME/.nvm/nvm.sh && nvm install $VERSION \\\n    && nvm use $VERSION && nvm alias default $VERSION'\n\nRUN echo \"nvm use default &>/dev/null\" >> ~/.bashrc.d/51-nvm-fix\n\nRUN sudo apt-get update && sudo apt-get upgrade -y\n\n# Solana\nRUN sh -c \"$(curl -sSfL https://release.solana.com/v1.17.18/install)\"\n# RUN wget https://github.com/solana-labs/solana/releases/download/v1.16.9/solana-release-x86_64-unknown-linux-gnu.tar.bz2\n# RUN tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2\n# RUN cd solana-release/\n# ENV PATH=\"$PWD/bin:${PATH}\"\n\nRUN npm i -g @coral-xyz/anchor-cli@0.30.0"
  },
  {
    "path": ".gitpod.yml",
    "content": "image:\n  file: .gitpod.Dockerfile\n\n# Commands to start on workspace startup\ntasks:\n  - init: npm ci\n\nports:\n  - port: 8080\n    onOpen: open-preview\n\n# TODO: See about publishing to Open VSX for smoother process\nvscode:\n  extensions:\n    - https://github.com/freeCodeCamp/courses-vscode-extension/releases/download/v1.7.5/freecodecamp-courses-1.7.5.vsix\n    - https://github.com/freeCodeCamp/freecodecamp-dark-vscode-theme/releases/download/v1.0.0/freecodecamp-dark-vscode-theme-1.0.0.vsix\n"
  },
  {
    "path": ".prettierignore",
    "content": "**/.cache\n**/package-lock.json\n**/pkg\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"endOfLine\": \"lf\",\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"jsxSingleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"none\",\n  \"arrowParens\": \"avoid\"\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"files.exclude\": {\n    \".devcontainer.json\": false,\n    \".editorconfig\": false,\n    \".git\": false,\n    \".github\": false,\n    \".gitignore\": false,\n    \".gitpod.Dockerfile\": false,\n    \".gitpod.yml\": false,\n    \".logs\": false,\n    \".prettierignore\": false,\n    \".prettierrc\": false,\n    \".vscode\": false,\n    \"bash\": false,\n    \"client\": false,\n    \"config\": false,\n    \"curriculum\": false,\n    \"tooling\": false,\n    \"Dockerfile\": false,\n    \"freecodecamp.conf.json\": false,\n    \"node_modules\": false,\n    \"package.json\": false,\n    \"package-lock.json\": false,\n    \"LICENSE\": false,\n    \"README.md\": false,\n    \"renovate.json\": false,\n    \"build-x-using-y\": false,\n    \"learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\": false,\n    \"learn-how-to-interact-with-on-chain-programs\": false,\n    \"build-a-smart-contract\": false,\n    \"learn-solanas-token-program-by-minting-a-fungible-token\": false,\n    \"learn-the-metaplex-sdk-by-minting-an-nft\": false,\n    \"build-a-university-certification-nft\": false,\n    \"learn-anchor-by-building-tic-tac-toe-part-1\": false,\n    \"learn-anchor-by-building-tic-tac-toe-part-2\": false,\n    \"build-an-anchor-leaderboard\": false,\n    \"learn-how-to-build-a-client-side-app-part-1\": false,\n    \"learn-how-to-build-a-client-side-app-part-2\": false,\n    \"build-a-client-side-app\": false,\n    \"learn-how-to-build-for-mainnet\": false,\n    \"learn-how-to-deploy-to-devnet\": false,\n    \"build-and-deploy-your-freeform-app\": false\n  },\n  \"terminal.integrated.profiles.linux\": {\n    \"bash\": {\n      \"path\": \"bash\",\n      \"icon\": \"terminal-bash\",\n      \"args\": [\"--init-file\", \"./bash/sourcerer.sh\"]\n    }\n  },\n  \"terminal.integrated.defaultProfile.linux\": \"bash\",\n  \"workbench.colorTheme\": \"freeCodeCamp Dark Theme\"\n}\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM ubuntu:20.04\n\nARG USERNAME=camper\nARG REPO_NAME=solana-curriculum\nARG HOMEDIR=/workspace/$REPO_NAME\n\nENV TZ=\"America/New_York\"\nENV HOME=/workspace\n\nRUN apt-get update && apt-get install -y sudo\n\n# Unminimize Ubuntu to restore man pages\nRUN yes | unminimize\n\n# Set up timezone\nRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone\n\n# Set up user, disable pw, and add to sudo group\nRUN adduser --disabled-password \\\n  --gecos '' ${USERNAME}\n\nRUN adduser ${USERNAME} sudo\n\nRUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> \\\n  /etc/sudoers\n\n# Install packages for projects\nRUN sudo apt-get install -y curl git bash-completion man-db htop nano\n\n# Install Node LTS\nRUN curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -\nRUN sudo apt-get install -y nodejs\n\n# Rust\nRUN sudo apt-get install -y build-essential\nRUN curl https://sh.rustup.rs -sSf | sh -s -- -y\nENV PATH=\"/workspace/.cargo/bin:${PATH}\"\n\n# Solana\nRUN sh -c \"$(curl -sSfL https://release.solana.com/v1.17.18/install)\"\n# RUN wget https://github.com/solana-labs/solana/releases/download/v1.16.9/solana-release-x86_64-unknown-linux-gnu.tar.bz2\n# RUN tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2\n# RUN cd solana-release/\n# ENV PATH=\"$PWD/bin:${PATH}\"\n\n# /usr/lib/node_modules is owned by root, so this creates a folder ${USERNAME} \n# can use for npm install --global\nWORKDIR ${HOMEDIR}\nRUN mkdir ~/.npm-global\nRUN npm config set prefix '~/.npm-global'\n\nRUN npm install -g yarn @coral-xyz/anchor-cli@0.30.0\n\n# Configure course-specific environment\nCOPY . .\nWORKDIR ${HOMEDIR}\n\nRUN cd ${HOMEDIR} && npm install\n\n# wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.16_amd64.deb\n# sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2.16_amd64.deb\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2022-2023, freeCodeCamp\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# freeCodeCamp: Solana Curriculum\n\nGet started here: https://web3.freecodecamp.org/solana\n\n## Projects\n\n| Project                                                             | Description                                                                                                                                                              |\n| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| 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.                                                                           |\n| 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.                                        |\n| Build a Smart Contract                                              | In this integrated project, you will use what you previously learnt to build a smart contract, and interact with it.                                                     |\n| 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.                                                                            |\n| 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.                                                                                            |\n| 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.                      |\n| 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.                             |\n| 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.                                                                                        |\n| Build an Anchor Leaderboard                                         | In this integrated project, you will use what you previously learnt to build the program logic for a game leaderboard                                                    |\n| 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                                 |\n| 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. |\n| 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.                                         |\n| More Coming Soon...                                                 | Keep an 👁️ out                                                                                                                                                           |\n"
  },
  {
    "path": "bash/.bashrc",
    "content": "# ~/.bashrc: executed by bash(1) for non-login shells.\n# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)\n# for examples\n\n# If not running interactively, don't do anything\ncase $- in\n    *i*) ;;\n      *) return;;\nesac\n\n# don't put duplicate lines or lines starting with space in the history.\n# See bash(1) for more options\nHISTCONTROL=ignoreboth\n\n# append to the history file, don't overwrite it\nshopt -s histappend\n\n# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)\nHISTSIZE=1000\nHISTFILESIZE=2000\n\n# check the window size after each command and, if necessary,\n# update the values of LINES and COLUMNS.\nshopt -s checkwinsize\n\n# If set, the pattern \"**\" used in a pathname expansion context will\n# match all files and zero or more directories and subdirectories.\n#shopt -s globstar\n\n# make less more friendly for non-text input files, see lesspipe(1)\n[ -x /usr/bin/lesspipe ] && eval \"$(SHELL=/bin/sh lesspipe)\"\n\n# set variable identifying the chroot you work in (used in the prompt below)\nif [ -z \"${debian_chroot:-}\" ] && [ -r /etc/debian_chroot ]; then\n    debian_chroot=$(cat /etc/debian_chroot)\nfi\n\n# set a fancy prompt (non-color, unless we know we \"want\" color)\ncase \"$TERM\" in\n    xterm-color|*-256color) color_prompt=yes;;\nesac\n\n# uncomment for a colored prompt, if the terminal has the capability; turned\n# off by default to not distract the user: the focus in a terminal window\n# should be on the output of commands, not on the prompt\n#force_color_prompt=yes\n\nif [ -n \"$force_color_prompt\" ]; then\n    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then\n\t# We have color support; assume it's compliant with Ecma-48\n\t# (ISO/IEC-6429). (Lack of such support is extremely rare, and such\n\t# a case would tend to support setf rather than setaf.)\n\tcolor_prompt=yes\n    else\n\tcolor_prompt=\n    fi\nfi\n\nif [ \"$color_prompt\" = yes ]; then\n    PS1='${debian_chroot:+($debian_chroot)}\\[\\033[01;32m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[00m\\]\\$ '\nelse\n    PS1='${debian_chroot:+($debian_chroot)}\\u@\\h:\\w\\$ '\nfi\nunset color_prompt force_color_prompt\n\n# If this is an xterm set the title to user@host:dir\ncase \"$TERM\" in\nxterm*|rxvt*)\n    PS1=\"\\[\\e]0;${debian_chroot:+($debian_chroot)}\\u@\\h: \\w\\a\\]$PS1\"\n    ;;\n*)\n    ;;\nesac\n\n# enable color support of ls and also add handy aliases\nif [ -x /usr/bin/dircolors ]; then\n    test -r ~/.dircolors && eval \"$(dircolors -b ~/.dircolors)\" || eval \"$(dircolors -b)\"\n    alias ls='ls --color=auto'\n    #alias dir='dir --color=auto'\n    #alias vdir='vdir --color=auto'\n\n    alias grep='grep --color=auto'\n    alias fgrep='fgrep --color=auto'\n    alias egrep='egrep --color=auto'\nfi\n\n# colored GCC warnings and errors\n#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'\n\n# some more ls aliases\nalias ll='ls -alF'\nalias la='ls -A'\nalias l='ls -CF'\n\n# Add an \"alert\" alias for long running commands.  Use like so:\n#   sleep 10; alert\nalias alert='notify-send --urgency=low -i \"$([ $? = 0 ] && echo terminal || echo error)\" \"$(history|tail -n1|sed -e '\\''s/^\\s*[0-9]\\+\\s*//;s/[;&|]\\s*alert$//'\\'')\"'\n\n# Alias definitions.\n# You may want to put all your additions into a separate file like\n# ~/.bash_aliases, instead of adding them here directly.\n# See /usr/share/doc/bash-doc/examples in the bash-doc package.\n\nif [ -f ~/.bash_aliases ]; then\n    . ~/.bash_aliases\nfi\n\n# enable programmable completion features (you don't need to enable\n# this, if it's already enabled in /etc/bash.bashrc and /etc/profile\n# sources /etc/bash.bashrc).\nif ! shopt -oq posix; then\n  if [ -f /usr/share/bash-completion/bash_completion ]; then\n    . /usr/share/bash-completion/bash_completion\n  elif [ -f /etc/bash_completion ]; then\n    . /etc/bash_completion\n  fi\nfi\n\nPS1='\\[\u001b[01;32m\\]\\u\\[\u001b[00m\\] \\[\u001b[01;34m\\]\\w\\[\u001b[00m\\]$(__git_ps1 \" (%s)\") $ '\n\nfor i in $(ls -A $HOME/.bashrc.d/); do source $HOME/.bashrc.d/$i; done\n\n. \"$HOME/.cargo/env\"\n\nexport PATH=\"$HOME/.npm-global/bin:$PATH\"\n\n# freeCodeCamp - Needed for most tests to work\n\nWD=/workspace/solana-curriculum\n\nPROMPT_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;'\nexec > >(tee -ia $WD/.logs/.temp.log) 2>&1\n"
  },
  {
    "path": "bash/sourcerer.sh",
    "content": "#!/bin/bash\nsource ./bash/.bashrc\necho \"BashRC Sourced\"\n"
  },
  {
    "path": "build-a-client-side-app/.gitignore",
    "content": "mess"
  },
  {
    "path": "build-a-client-side-app/mess.json",
    "content": "{\n  \"version\": \"0.1.0\",\n  \"name\": \"mess\",\n  \"instructions\": [\n    {\n      \"name\": \"init\",\n      \"accounts\": [\n        {\n          \"name\": \"chat\",\n          \"isMut\": true,\n          \"isSigner\": false,\n          \"docs\": [\n            \"Global chat account to hold 20 messages\"\n          ]\n        },\n        {\n          \"name\": \"payer\",\n          \"isMut\": true,\n          \"isSigner\": true\n        },\n        {\n          \"name\": \"systemProgram\",\n          \"isMut\": false,\n          \"isSigner\": false\n        }\n      ],\n      \"args\": []\n    },\n    {\n      \"name\": \"send\",\n      \"accounts\": [\n        {\n          \"name\": \"chat\",\n          \"isMut\": true,\n          \"isSigner\": false\n        },\n        {\n          \"name\": \"sender\",\n          \"isMut\": false,\n          \"isSigner\": true\n        }\n      ],\n      \"args\": [\n        {\n          \"name\": \"text\",\n          \"type\": \"string\"\n        }\n      ]\n    }\n  ],\n  \"accounts\": [\n    {\n      \"name\": \"Chat\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"messages\",\n            \"type\": {\n              \"vec\": {\n                \"defined\": \"Message\"\n              }\n            }\n          }\n        ]\n      }\n    }\n  ],\n  \"types\": [\n    {\n      \"name\": \"Message\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"sender\",\n            \"type\": \"publicKey\"\n          },\n          {\n            \"name\": \"text\",\n            \"type\": \"string\"\n          }\n        ]\n      }\n    }\n  ]\n}"
  },
  {
    "path": "build-a-client-side-app/mess.ts",
    "content": "export type Mess = {\n  \"version\": \"0.1.0\",\n  \"name\": \"mess\",\n  \"instructions\": [\n    {\n      \"name\": \"init\",\n      \"accounts\": [\n        {\n          \"name\": \"chat\",\n          \"isMut\": true,\n          \"isSigner\": false,\n          \"docs\": [\n            \"Global chat account to hold 20 messages\"\n          ]\n        },\n        {\n          \"name\": \"payer\",\n          \"isMut\": true,\n          \"isSigner\": true\n        },\n        {\n          \"name\": \"systemProgram\",\n          \"isMut\": false,\n          \"isSigner\": false\n        }\n      ],\n      \"args\": []\n    },\n    {\n      \"name\": \"send\",\n      \"accounts\": [\n        {\n          \"name\": \"chat\",\n          \"isMut\": true,\n          \"isSigner\": false\n        },\n        {\n          \"name\": \"sender\",\n          \"isMut\": false,\n          \"isSigner\": true\n        }\n      ],\n      \"args\": [\n        {\n          \"name\": \"text\",\n          \"type\": \"string\"\n        }\n      ]\n    }\n  ],\n  \"accounts\": [\n    {\n      \"name\": \"chat\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"messages\",\n            \"type\": {\n              \"vec\": {\n                \"defined\": \"Message\"\n              }\n            }\n          }\n        ]\n      }\n    }\n  ],\n  \"types\": [\n    {\n      \"name\": \"Message\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"sender\",\n            \"type\": \"publicKey\"\n          },\n          {\n            \"name\": \"text\",\n            \"type\": \"string\"\n          }\n        ]\n      }\n    }\n  ]\n};\n\nexport const IDL: Mess = {\n  \"version\": \"0.1.0\",\n  \"name\": \"mess\",\n  \"instructions\": [\n    {\n      \"name\": \"init\",\n      \"accounts\": [\n        {\n          \"name\": \"chat\",\n          \"isMut\": true,\n          \"isSigner\": false,\n          \"docs\": [\n            \"Global chat account to hold 20 messages\"\n          ]\n        },\n        {\n          \"name\": \"payer\",\n          \"isMut\": true,\n          \"isSigner\": true\n        },\n        {\n          \"name\": \"systemProgram\",\n          \"isMut\": false,\n          \"isSigner\": false\n        }\n      ],\n      \"args\": []\n    },\n    {\n      \"name\": \"send\",\n      \"accounts\": [\n        {\n          \"name\": \"chat\",\n          \"isMut\": true,\n          \"isSigner\": false\n        },\n        {\n          \"name\": \"sender\",\n          \"isMut\": false,\n          \"isSigner\": true\n        }\n      ],\n      \"args\": [\n        {\n          \"name\": \"text\",\n          \"type\": \"string\"\n        }\n      ]\n    }\n  ],\n  \"accounts\": [\n    {\n      \"name\": \"chat\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"messages\",\n            \"type\": {\n              \"vec\": {\n                \"defined\": \"Message\"\n              }\n            }\n          }\n        ]\n      }\n    }\n  ],\n  \"types\": [\n    {\n      \"name\": \"Message\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"sender\",\n            \"type\": \"publicKey\"\n          },\n          {\n            \"name\": \"text\",\n            \"type\": \"string\"\n          }\n        ]\n      }\n    }\n  ]\n};\n"
  },
  {
    "path": "build-a-smart-contract/package.json",
    "content": "{\n  \"name\": \"build-a-smart-contract\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@solana/web3.js\": \"1.87.7\",\n    \"borsh\": \"0.7.0\"\n  },\n  \"scripts\": {\n    \"build\": \"cargo build-sbf --manifest-path program/Cargo.toml --sbf-out-dir=dist/program\",\n    \"deploy\": \"solana program deploy --keypair wallet.json dist/program/message.so\"\n  }\n}\n"
  },
  {
    "path": "build-a-smart-contract/program/Cargo.toml",
    "content": "[package]\nname = \"message\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nborsh = \"0.10.3\"\nborsh-derive = \"0.10.3\"\nsolana-program = \"1.17.3\"\n\n[lib]\nname = \"message\"\ncrate-type = [\"cdylib\", \"lib\"]\n"
  },
  {
    "path": "build-a-smart-contract/program/src/lib.rs",
    "content": "\n"
  },
  {
    "path": "build-a-smart-contract/program/tests/process_instruction.rs",
    "content": "extern crate message;\nuse message::process_instruction;\nuse borsh::BorshDeserialize;\n\nuse solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};\n\n#[derive(BorshDeserialize, Debug)]\npub struct MessageStructForTest {\n    pub message: String\n}\n\n#[test]\nfn owner_not_program_id() {\n    let program_id = Pubkey::new_unique();\n    let owner = Pubkey::new_unique();\n    let mut data = vec![0; 284];\n    let mut lam = 2;\n    let account_info = AccountInfo::new(\n        &program_id,\n        false,\n        true,\n        &mut lam,\n        &mut data,\n        &owner,\n        false,\n        2,\n    );\n    let accounts = vec![account_info];\n    let instruction_data = vec![];\n    let result = process_instruction(&program_id, &accounts, &instruction_data);\n    assert_eq!(result, Err(ProgramError::IncorrectProgramId));\n}\n\n#[test]\nfn instruction_is_deserialized() {\n    let program_id = Pubkey::new_unique();\n    let mut data = vec![0; 284];\n    let mut lam = 2;\n    let account_info = AccountInfo::new(\n        &program_id,\n        false,\n        true,\n        &mut lam,\n        &mut data,\n        &program_id,\n        false,\n        2,\n    );\n    let accounts = vec![account_info];\n    let instruction_data = \"Hello World!\".as_bytes();\n    let _result = process_instruction(&program_id, &accounts, &instruction_data);\n    let data = MessageStructForTest::try_from_slice(String::from_utf8(accounts[0].data.borrow().to_vec()).unwrap().as_bytes()).unwrap();\n    let fmt = format!(\"{: <280}\", \"Hello World!\");\n    assert_eq!(fmt, data.message);\n}\n\n#[test]\nfn instruction_not_string() {\n    let program_id = Pubkey::new_unique();\n    let mut data = vec![0; 284];\n    let mut lam = 2;\n    let account_info = AccountInfo::new(\n        &program_id,\n        false,\n        true,\n        &mut lam,\n        &mut data,\n        &program_id,\n        false,\n        2,\n    );\n    let accounts = vec![account_info];\n    let instruction_data = vec![0, 159, 146, 150];\n    let result = process_instruction(&program_id, &accounts, &instruction_data);\n    assert_eq!(result, Err(ProgramError::InvalidInstructionData));\n}\n\n#[test]\nfn instruction_too_long() {\n    let program_id = Pubkey::new_unique();\n    let mut data = vec![0; 284];\n    let mut lam = 2;\n    let account_info = AccountInfo::new(\n        &program_id,\n        false,\n        true,\n        &mut lam,\n        &mut data,\n        &program_id,\n        false,\n        2,\n    );\n    let accounts = vec![account_info];\n    let instruction_data = vec![0; 281];\n    let result = process_instruction(&program_id, &accounts, &instruction_data);\n    assert_eq!(result, Err(ProgramError::InvalidInstructionData));\n}\n\n#[test]\nfn no_accounts() {\n    let program_id = Pubkey::new_unique();\n    let accounts = vec![];\n    let instruction_data = vec![];\n    let result = process_instruction(&program_id, &accounts, &instruction_data);\n    assert_eq!(result, Err(ProgramError::NotEnoughAccountKeys));\n}\n\n#[test]\nfn instruction_data_padded() {\n    let program_id = Pubkey::new_unique();\n    let mut data = vec![0; 284];\n    let mut lam = 2;\n    let account_info = AccountInfo::new(\n        &program_id,\n        false,\n        true,\n        &mut lam,\n        &mut data,\n        &program_id,\n        false,\n        2,\n    );\n    let accounts = vec![account_info];\n    let instruction_data = \"Hello World!\".as_bytes();\n    let _result = process_instruction(&program_id, &accounts, &instruction_data);\n    let account_data = accounts[0].data.borrow();\n    assert_eq!(account_data.len(), 284);\n}\n\n#[test]\nfn success() {\n    let program_id = Pubkey::new_unique();\n    let mut data = vec![0; 284];\n    let mut lam = 2;\n    let account_info = AccountInfo::new(\n        &program_id,\n        false,\n        true,\n        &mut lam,\n        &mut data,\n        &program_id,\n        false,\n        2,\n    );\n    let accounts = vec![account_info];\n    let instruction_data = \"Hello World!\".as_bytes();\n    let result = process_instruction(&program_id, &accounts, &instruction_data);\n    assert_eq!(result, Ok(()));\n}\n"
  },
  {
    "path": "build-a-smart-contract/wallet.json",
    "content": "[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]"
  },
  {
    "path": "build-a-university-certification-nft/client/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "build-a-university-certification-nft/client/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/solana.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Solana University Certification Dashboard</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script>\n      // Global node polyfill.\n      window.global = window;\n    </script>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "build-a-university-certification-nft/client/package.json",
    "content": "{\n  \"name\": \"client\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"npm i && vite --clearScreen=false\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@metaplex-foundation/js\": \"0.18.3\",\n    \"@solana/spl-token\": \"0.3.7\",\n    \"@solana/web3.js\": \"1.87.7\",\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\"\n  },\n  \"devDependencies\": {\n    \"@esbuild-plugins/node-globals-polyfill\": \"0.2.3\",\n    \"@types/react\": \"18.2.31\",\n    \"@types/react-dom\": \"18.2.14\",\n    \"@vitejs/plugin-react\": \"3.1.0\",\n    \"assert\": \"2.1.0\",\n    \"crypto-browserify\": \"3.12.0\",\n    \"rollup-plugin-node-polyfills\": \"0.2.1\",\n    \"typescript\": \"4.9.3\",\n    \"util\": \"0.12.5\",\n    \"vite\": \"4.5.6\"\n  }\n}\n"
  },
  {
    "path": "build-a-university-certification-nft/client/src/app.css",
    "content": "#root {\n  max-width: 1280px;\n  margin: 0 auto;\n  padding: 2rem;\n  text-align: center;\n}\n\nform {\n  display: flex;\n  flex-direction: column;\n  align-items: end;\n}\n\nlabel {\n  margin: 0.5rem 0;\n}\n\n.controls {\n  margin-top: 1.5rem;\n}\n"
  },
  {
    "path": "build-a-university-certification-nft/client/src/app.tsx",
    "content": "import { ChangeEvent, useEffect, useRef, useState } from 'react';\nimport { Keypair, PublicKey, Signer } from '@solana/web3.js';\nimport {\n  createMintAccount as camperCreateMintAccount,\n  getMintAccounts as camperGetMintAccounts,\n  createTokenAccount as camperCreateTokenAccount,\n  mintToken as camperMintToken,\n  uploadFile as camperUploadFile,\n  getNFTs as camperGetNFTs\n} from '../../index.js';\nimport './app.css';\nimport { toMetaplexFile } from '@metaplex-foundation/js';\n\n// 1) Create a certificate program - create a new mint\n// 2) Register new student - create a token account for the student\n// 3) Issue a certificate - mint NFT to the student's token account\n// 4) View a certificate - view the certificate's metadata\nexport function App() {\n  const [output, setOutput] = useState<string>('OUTPUT');\n  const [payer, setPayer] = useState<Signer>();\n  const [mintAddress, setMintAddress] = useState<PublicKey>();\n  const [ownerAddress, setOwnerAddress] = useState<PublicKey>();\n  const [uri, setUri] = useState<string>();\n  const [invalidInputs, setInvalidInputs] = useState<string[]>([]);\n\n  const createMintAccount: CreateMintAccountF = async () => {\n    if (!payer) {\n      setInvalidInputs(['payer']);\n      return;\n    }\n    setInvalidInputs([]);\n    try {\n      const mint = await camperCreateMintAccount({ payer });\n      setOutput(JSON.stringify(mint, null, 2));\n    } catch (e) {\n      console.warn(e);\n      setOutput(\n        'Error creating mint account:\\n\\n' + JSON.stringify(e, null, 2)\n      );\n    }\n  };\n  const getMintAccounts: GetMintAccountsF = async () => {\n    if (!payer) {\n      setInvalidInputs(['payer']);\n      return;\n    }\n    setInvalidInputs([]);\n    try {\n      const mintAccounts = await camperGetMintAccounts({ payer });\n      setOutput(JSON.stringify(mintAccounts, null, 2));\n    } catch (e) {\n      console.warn(e);\n      setOutput(\n        'Error getting mint accounts:\\n\\n' + JSON.stringify(e, null, 2)\n      );\n    }\n  };\n  const createTokenAccount: CreateTokenAccountF = async () => {\n    if (!payer || !mintAddress || !ownerAddress) {\n      setInvalidInputs(['payer', 'mintAddress', 'ownerAddress']);\n      return;\n    }\n    setInvalidInputs([]);\n    try {\n      const tokenAccount = await camperCreateTokenAccount({\n        payer,\n        mintAddress,\n        ownerAddress\n      });\n      console.log(tokenAccount);\n      setOutput(tokenAccount.address.toBase58());\n    } catch (e) {\n      console.warn(e);\n      setOutput(\n        'Error creating token account:\\n\\n' + JSON.stringify(e, null, 2)\n      );\n    }\n  };\n  const mintToken: MintTokenF = async () => {\n    if (!payer || !mintAddress || !ownerAddress || !uri) {\n      setInvalidInputs(['payer', 'mintAddress', 'ownerAddress', 'uri']);\n      return;\n    }\n    setInvalidInputs([]);\n    const year = new Date().getFullYear();\n    try {\n      const nft = await camperMintToken({\n        payer,\n        mintAddress,\n        ownerAddress,\n        year,\n        uri\n      });\n      console.log(nft);\n      setOutput(JSON.stringify(nft, null, 2));\n    } catch (e) {\n      console.warn(e);\n      setOutput('Error minting token:\\n\\n' + JSON.stringify(e, null, 2));\n    }\n  };\n  const getNFTs: GetNFTsF = async () => {\n    if (!ownerAddress) {\n      setInvalidInputs(['ownerAddress']);\n      return;\n    }\n    setInvalidInputs([]);\n    try {\n      const nfts = await camperGetNFTs({ ownerAddress });\n      console.log(nfts);\n      setOutput(JSON.stringify(nfts, null, 2));\n    } catch (e) {\n      console.warn(e);\n      setOutput('Error getting NFTs:\\n\\n' + JSON.stringify(e, null, 2));\n    }\n  };\n\n  const imageInput = useRef<HTMLInputElement>(null);\n  const previewImg = useRef<HTMLImageElement>(null);\n  const uriInput = useRef<HTMLInputElement>(null);\n\n  function setAuthority(e: ChangeEvent<HTMLInputElement>) {\n    if (e.target) {\n      try {\n        const keypair = Keypair.fromSecretKey(\n          new Uint8Array(JSON.parse(e.target.value))\n        );\n        setPayer(keypair);\n      } catch (e) {\n        console.warn(e);\n        setOutput('Invalid payer secret key\\n\\n' + JSON.stringify(e, null, 2));\n      }\n    }\n  }\n\n  function setMint(e: ChangeEvent<HTMLInputElement>) {\n    if (e.target) {\n      try {\n        const mint = new PublicKey(e.target.value);\n        setMintAddress(mint);\n      } catch (e) {\n        console.warn(e);\n        setOutput('Invalid mint public key\\n\\n' + JSON.stringify(e, null, 2));\n      }\n    }\n  }\n\n  function setOwner(e: ChangeEvent<HTMLInputElement>) {\n    if (e.target) {\n      try {\n        const owner = new PublicKey(e.target.value);\n        setOwnerAddress(owner);\n      } catch (e) {\n        console.warn(e);\n        setOutput(\n          'Invalid student public key\\n\\n' + JSON.stringify(e, null, 2)\n        );\n      }\n    }\n  }\n\n  function setUriInput(e: ChangeEvent<HTMLInputElement>) {\n    if (e.target) {\n      setUri(e.target.value);\n    }\n  }\n\n  useEffect(() => {\n    if (uri) {\n      if (uriInput.current && uri !== uriInput.current.value) {\n        uriInput.current.value = uri;\n      }\n    }\n  }, [uri]);\n\n  async function uploadFile() {\n    if (imageInput.current) {\n      if (imageInput.current.files) {\n        const file = imageInput.current.files[0];\n        try {\n          const arrayBuffer = await file.arrayBuffer();\n          const metaplexFile = toMetaplexFile(arrayBuffer, file.name);\n          if (!payer) {\n            setInvalidInputs(['payer']);\n            return;\n          }\n          setInvalidInputs([]);\n          const uri = await camperUploadFile({ metaplexFile, payer });\n          setUri(uri);\n          setOutput(uri);\n        } catch (e) {\n          console.warn(e);\n          setOutput('Invalid file\\n\\n' + JSON.stringify(e, null, 2));\n        }\n      }\n    }\n  }\n\n  return (\n    <main>\n      <h1>Solana University Certification Dashboard</h1>\n      <form>\n        <label>\n          Payer Secret Key:{' '}\n          <input type='text' id='authority' onChange={setAuthority}></input>\n        </label>\n        <label>\n          Mint Public Key:{' '}\n          <input type='text' id='mint' onChange={setMint}></input>\n        </label>\n        <label>\n          Student Public Key:{' '}\n          <input type='text' id='owner' onChange={setOwner}></input>\n        </label>\n        <label>\n          NFT Metadata URI:{' '}\n          <input\n            type='text'\n            id='owner'\n            onChange={setUriInput}\n            ref={uriInput}\n          ></input>\n        </label>\n      </form>\n      <form>\n        <label>\n          Image File:{' '}\n          <input\n            type='file'\n            id='image'\n            accept='image/*'\n            ref={imageInput}\n            onChange={e => {\n              if (e.target.files) {\n                const file = e.target.files[0];\n                const reader = new FileReader();\n                reader.onload = ev => {\n                  if (ev.target) {\n                    if (previewImg.current) {\n                      previewImg.current.src = ev.target.result as string;\n                    }\n                  }\n                  if (reader.result) {\n                    console.log(reader.result);\n                  }\n                };\n                reader.readAsDataURL(file);\n              }\n            }}\n          ></input>\n        </label>\n        <img id='preview' alt='nft preview' ref={previewImg} />\n        <button\n          type='submit'\n          onClick={e => {\n            e.preventDefault();\n            uploadFile();\n          }}\n        >\n          Upload Image\n        </button>\n      </form>\n      <div className='controls'>\n        <CreateCertificateProgram {...{ createMintAccount }} />\n        <GetCertificatePrograms {...{ getMintAccounts }} />\n        <RegisterStudent {...{ createTokenAccount }} />\n        <GrantCertificate {...{ mintToken }} />\n        <ViewStudentCertificate {...{ getNFTs }} />\n      </div>\n      {invalidInputs.length > 0 && <ValidationError {...{ invalidInputs }} />}\n      <Output {...{ output }} />\n    </main>\n  );\n}\n\ntype OutputT = {\n  output: string;\n};\n\ntype CreateMintAccountF = () => Promise<void>;\ntype GetMintAccountsF = () => Promise<void>;\ntype CreateTokenAccountF = () => Promise<void>;\ntype MintTokenF = () => Promise<void>;\ntype GetNFTsF = () => Promise<void>;\n\ntype CreateCertificateProgramT = {\n  createMintAccount: CreateMintAccountF;\n};\n\n/**\n * Create a new Mint for a certificate program\n */\nfunction CreateCertificateProgram({\n  createMintAccount\n}: CreateCertificateProgramT) {\n  return (\n    <section>\n      <p>Create Certificate Program</p>\n      <button onClick={() => createMintAccount()}>Create Mint Account</button>\n    </section>\n  );\n}\n\n/**\n * Get all mitns. Useful to get the mint address for a certificate program\n */\nfunction GetCertificatePrograms({\n  getMintAccounts\n}: {\n  getMintAccounts: GetMintAccountsF;\n}) {\n  return (\n    <section>\n      <p>Get Certificate Programs</p>\n      <button onClick={() => getMintAccounts()}>Get Mint Accounts</button>\n    </section>\n  );\n}\n\n/**\n * Create a new token account associated with the public key of the student\n */\nfunction RegisterStudent({\n  createTokenAccount\n}: {\n  createTokenAccount: CreateTokenAccountF;\n}) {\n  return (\n    <section>\n      <p>Register Student</p>\n      <button onClick={() => createTokenAccount()}>Create Token Account</button>\n    </section>\n  );\n}\n\n/**\n * Mint a new NFT to the student's token account\n */\nfunction GrantCertificate({ mintToken }: { mintToken: MintTokenF }) {\n  return (\n    <section>\n      <p>Grant Certificate</p>\n      <button onClick={() => mintToken()}>Mint Token</button>\n    </section>\n  );\n}\n\nfunction ViewStudentCertificate({ getNFTs }: { getNFTs: GetNFTsF }) {\n  return (\n    <section>\n      <p>View Student Certificate</p>\n      <button onClick={() => getNFTs()}>Get NFT</button>\n    </section>\n  );\n}\n\ntype DisplayPngT = {\n  buffer: Buffer;\n};\n\nfunction DisplayPng({ buffer }: DisplayPngT) {\n  return <img src={`data:image/png;base64,${buffer.toString('base64')}`}></img>;\n}\n\nfunction Output({ output }: OutputT) {\n  return (\n    <div>\n      <pre style={{ textAlign: 'left' }}>\n        <code>{output}</code>\n      </pre>\n    </div>\n  );\n}\n\nfunction ValidationError({ invalidInputs }: { invalidInputs: string[] }) {\n  return (\n    <div>\n      <p>Form requires: {invalidInputs.join(', ')}</p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "build-a-university-certification-nft/client/src/index.css",
    "content": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n  color-scheme: light dark;\n  color: rgba(255, 255, 255, 0.87);\n  background-color: #000000;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-text-size-adjust: 100%;\n\n  --solana-purple: #9945ff;\n  --solana-green: #14f195;\n}\n\na {\n  font-weight: 500;\n  color: #646cff;\n  text-decoration: inherit;\n}\na:hover {\n  color: #535bf2;\n}\n\nbody {\n  margin: 0;\n  display: flex;\n  place-items: center;\n  min-width: 320px;\n  min-height: 100vh;\n}\n\nh1 {\n  font-size: 1.8em;\n  line-height: 1.1;\n  background: -webkit-linear-gradient(\n    120deg,\n    var(--solana-green),\n    var(--solana-purple)\n  );\n  -webkit-background-clip: text;\n  background-clip: text;\n  -webkit-text-fill-color: transparent;\n}\n\n.controls {\n  display: flex;\n  flex-direction: row;\n  gap: 1em;\n  padding: 1em;\n  border-radius: 8px;\n  background-color: rgba(40, 40, 40, 0.5);\n  backdrop-filter: blur(8px);\n  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);\n}\n\nbutton {\n  border-radius: 8px;\n  border: 1px solid transparent;\n  padding: 0.6em 1.2em;\n  font-size: 1em;\n  font-weight: 500;\n  font-family: inherit;\n  background-color: var(--solana-purple);\n  cursor: pointer;\n  transition: border-color 0.25s;\n}\nbutton:hover {\n  border-color: #646cff;\n}\nbutton:focus,\nbutton:focus-visible {\n  outline: 4px auto -webkit-focus-ring-color;\n}\n\n@media (prefers-color-scheme: light) {\n  :root {\n    color: #213547;\n    background-color: #ffffff;\n  }\n  a:hover {\n    color: #747bff;\n  }\n  button {\n    background-color: #f9f9f9;\n  }\n}\n"
  },
  {
    "path": "build-a-university-certification-nft/client/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './app';\nimport './index.css';\n\nReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);\n"
  },
  {
    "path": "build-a-university-certification-nft/client/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "build-a-university-certification-nft/client/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": false,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "build-a-university-certification-nft/client/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "build-a-university-certification-nft/client/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nimport { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';\nimport nodePolyfills from 'rollup-plugin-node-polyfills';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n  resolve: {\n    alias: {\n      stream: 'node_modules/rollup-plugin-node-polyfills/polyfills/stream.js',\n      events: 'node_modules/rollup-plugin-node-polyfills/polyfills/events.js',\n      assert: 'assert',\n      crypto: 'node_modules/crypto-browserify/index.js',\n      util: 'util',\n      'near-api-js': 'near-api-js/dist/near-api-js.js'\n    }\n  },\n  define: {\n    'process.env': process.env ?? {}\n  },\n  build: {\n    target: 'esnext',\n    rollupOptions: {\n      plugins: [nodePolyfills({ crypto: true })]\n    }\n  },\n  optimizeDeps: {\n    esbuildOptions: {\n      plugins: [NodeGlobalsPolyfillPlugin({ buffer: true })]\n    }\n  }\n});\n"
  },
  {
    "path": "build-a-university-certification-nft/index.d.ts",
    "content": "import { MetaplexFile, CreateNftOutput, FindNftsByOwnerOutput } from '@metaplex-foundation/js';\nimport { Account, createMint } from '@solana/spl-token';\nimport {\n  AccountInfo,\n  ParsedAccountData,\n  PublicKey,\n  Signer\n} from '@solana/web3.js';\n\ndeclare function uploadFile({\n  metaplexFile,\n  payer\n}: {\n  metaplexFile: MetaplexFile;\n  payer: Signer;\n}): Promise<string>;\n\ndeclare function createMintAccount({\n  payer\n}: {\n  payer: Signer;\n}): ReturnType<typeof createMint>;\n\ndeclare function getMintAccounts({\n  payer\n}: {\n  payer: Signer;\n}): Promise<AccountInfo<ParsedAccountData>[]>;\n\ndeclare function createTokenAccount({\n  payer,\n  mintAddress,\n  ownerAddress\n}: {\n  payer: Signer;\n  mintAddress: PublicKey;\n  ownerAddress: PublicKey;\n}): Promise<Account>;\n\ndeclare function mintToken({\n  payer,\n  mintAddress,\n  ownerAddress,\n  year,\n  uri\n}: {\n  payer: Signer;\n  mintAddress: PublicKey;\n  ownerAddress: PublicKey;\n  year: number;\n  uri: string;\n}): Promise<CreateNftOutput>;\n\ndeclare function getNFTs({\n  ownerAddress\n}: {\n  ownerAddress: PublicKey;\n}): Promise<FindNftsByOwnerOutput>;\n"
  },
  {
    "path": "build-a-university-certification-nft/index.js",
    "content": ""
  },
  {
    "path": "build-a-university-certification-nft/metadatas.json",
    "content": "{}\n"
  },
  {
    "path": "build-a-university-certification-nft/package.json",
    "content": "{\n  \"name\": \"build-a-university-certification-nft\",\n  \"scripts\": {\n    \"start:server\": \"node server.js\",\n    \"start:client\": \"cd client && npm run dev -- --force\"\n  },\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"cors\": \"2.8.5\",\n    \"express\": \"4.20.0\"\n  }\n}\n"
  },
  {
    "path": "build-a-university-certification-nft/server.js",
    "content": "import { readFileSync, writeFileSync } from 'fs';\nimport cors from 'cors';\nimport express from 'express';\nimport { setDefaultResultOrder } from 'dns';\n\nsetDefaultResultOrder('ipv4first');\n\nconst app = express();\n\napp.use(cors());\napp.use((req, res, next) => {\n  console.log(req.method, req.url);\n  next();\n});\napp.use(express.json());\n\napp.get('/status/ping', (_req, res) => {\n  console.log('Got ping');\n  return res.status(200).send('pong');\n});\n\napp.get('/meta/:id', (req, res) => {\n  console.log('GET', req.params.id);\n  const metadatas = getMetadatas();\n  const metadata = metadatas[req.params.id];\n  if (!metadata) {\n    return res.status(404).end();\n  }\n\n  return res.send(Buffer.from(metadata));\n});\napp.put('/meta/:id', (req, res) => {\n  console.log('POST', req.params.id);\n  const metadatas = getMetadatas();\n  metadatas[req.params.id] = req.body;\n  writeMetadatas(metadatas);\n  res.status(200).end();\n});\n\nconst PORT = process.env.PORT || 3002;\napp.listen(PORT, () => {\n  console.log('Server started at', `http://127.0.0.1:${PORT}`);\n});\n\nfunction getMetadatas() {\n  const metadatas = readFileSync('metadatas.json', 'utf8');\n  return JSON.parse(metadatas);\n}\n\nfunction writeMetadatas(metadatas) {\n  writeFileSync('metadatas.json', JSON.stringify(metadatas));\n}\n"
  },
  {
    "path": "build-a-university-certification-nft/utils.js",
    "content": "export function localStorage(options) {\n  return {\n    install(metaplex) {\n      metaplex.storage().setDriver(new LocalStorageDriver(options));\n    }\n  };\n}\n\nclass LocalStorageDriver {\n  constructor(options) {\n    if (!options.baseUrl) {\n      throw new Error('Missing baseUrl option');\n    }\n    this.baseUrl = options.baseUrl;\n    this.costPerByte = 2;\n  }\n  async getUploadPrice(bytes) {\n    const { amount } = await import('@metaplex-foundation/js');\n    return amount(this.costPerByte * bytes, { symbol: 'SOL', decimals: 9 });\n  }\n  async upload(file) {\n    const uri = `${this.baseUrl}meta/${file.uniqueName}`;\n    await fetch(uri, {\n      method: 'PUT',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify(file.buffer)\n    });\n    return uri;\n  }\n  async download(uri) {\n    const { toMetaplexFile } = await import('@metaplex-foundation/js');\n    const res = await fetch(uri);\n    if (!res) {\n      throw new Error(`URI not found: ${uri}`);\n    }\n    const buffer = await res.arrayBuffer();\n    const metaplexFile = toMetaplexFile(buffer, uri);\n    return metaplexFile;\n  }\n}\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/.gitignore",
    "content": "\n.anchor\n.DS_Store\ntarget\n**/*.rs.bk\nnode_modules\ntest-ledger\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/.prettierignore",
    "content": "\n.anchor\n.DS_Store\ntarget\nnode_modules\ndist\nbuild\ntest-ledger\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/Anchor.toml",
    "content": "[features]\nseeds = false\nskip-lint = false\n[programs.localnet]\nrock_destroyer = \"Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS\"\n\n[registry]\nurl = \"https://api.apr.dev\"\n\n[provider]\ncluster = \"Localnet\"\nwallet = \"~/.config/solana/id.json\"\n\n[scripts]\ntest = \"yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts\"\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[profile.release.build-override]\nopt-level = 3\nincremental = false\ncodegen-units = 1\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/migrations/deploy.ts",
    "content": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked from the CLI, injecting a provider\n// configured from the workspace's Anchor.toml.\n\nconst anchor = require(\"@coral-xyz/anchor\");\n\nmodule.exports = async function (provider) {\n  // Configure client to use the provider.\n  anchor.setProvider(provider);\n\n  // Add your deploy script here.\n};\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/package.json",
    "content": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" --check\"\n  },\n  \"dependencies\": {\n    \"@coral-xyz/anchor\": \"^0.27.0\"\n  },\n  \"devDependencies\": {\n    \"chai\": \"^4.3.4\",\n    \"mocha\": \"^9.0.3\",\n    \"ts-mocha\": \"^10.0.0\",\n    \"@types/bn.js\": \"^5.1.0\",\n    \"@types/chai\": \"^4.3.0\",\n    \"@types/mocha\": \"^9.0.0\",\n    \"typescript\": \"^4.3.5\",\n    \"prettier\": \"^2.6.2\"\n  }\n}\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/Cargo.toml",
    "content": "[package]\nname = \"rock-destroyer\"\nversion = \"0.1.0\"\ndescription = \"Created with Anchor\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\nname = \"rock_destroyer\"\n\n[features]\nno-entrypoint = []\nno-idl = []\nno-log-ix-name = []\ncpi = [\"no-entrypoint\"]\ndefault = []\n\n[dependencies]\nanchor-lang = \"0.27.0\"\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/Xargo.toml",
    "content": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/programs/rock-destroyer/src/lib.rs",
    "content": "use anchor_lang::prelude::*;\n\ndeclare_id!(\"Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS\");\n\n#[program]\npub mod rock_destroyer {\n    use super::*;\n\n    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct Initialize {}\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/tests/rock-destroyer.ts",
    "content": "import * as anchor from \"@coral-xyz/anchor\";\nimport { Program } from \"@coral-xyz/anchor\";\nimport { RockDestroyer } from \"../target/types/rock_destroyer\";\n\ndescribe(\"rock-destroyer\", () => {\n  // Configure the client to use the local cluster.\n  anchor.setProvider(anchor.AnchorProvider.env());\n\n  const program = anchor.workspace.RockDestroyer as Program<RockDestroyer>;\n\n  it(\"Is initialized!\", async () => {\n    // Add your test here.\n    const tx = await program.methods.initialize().rpc();\n    console.log(\"Your transaction signature\", tx);\n  });\n});\n"
  },
  {
    "path": "build-an-anchor-leaderboard/rock-destroyer/tsconfig.json",
    "content": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules/@types\"],\n              \"lib\": [\"es2015\"],\n              \"module\": \"commonjs\",\n              \"target\": \"es6\",\n              \"esModuleInterop\": true\n            }\n          }\n          "
  },
  {
    "path": "build-and-deploy-your-freeform-app/.gitkeep",
    "content": ""
  },
  {
    "path": "config/projects.json",
    "content": "[\n  {\n    \"id\": 0,\n    \"title\": \"Learn How to Set Up Solana by Building a Hello World Smart Contract\",\n    \"dashedName\": \"learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\",\n    \"isIntegrated\": false,\n    \"description\": \"In this course, you will learn how to set up Solana by building a simple hello world contract.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 58\n  },\n  {\n    \"id\": 1,\n    \"title\": \"Learn How to Interact with On-Chain Programs\",\n    \"dashedName\": \"learn-how-to-interact-with-on-chain-programs\",\n    \"isIntegrated\": false,\n    \"description\": \"In this course, you will learn how to write the client code to interact with your previously deployed hello world smart contract.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 65\n  },\n  {\n    \"id\": 2,\n    \"title\": \"Build a Smart Contract\",\n    \"dashedName\": \"build-a-smart-contract\",\n    \"isIntegrated\": true,\n    \"description\": \"In this integrated project, you will use what you previously learnt to build a smart contract, and interact with it.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"numberOfLessons\": 1\n  },\n  {\n    \"id\": 3,\n    \"title\": \"Learn Solana's Token Program by Minting a Fungible Token\",\n    \"dashedName\": \"learn-solanas-token-program-by-minting-a-fungible-token\",\n    \"isIntegrated\": false,\n    \"description\": \"In this course, you will learn how to use Solana's token program by minting a fungible token.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 52\n  },\n  {\n    \"id\": 4,\n    \"title\": \"Learn the Metaplex SDK by Minting an NFT\",\n    \"dashedName\": \"learn-the-metaplex-sdk-by-minting-an-nft\",\n    \"isIntegrated\": false,\n    \"description\": \"In this course, you will learn how to use the Metaplex JS SDK to mint an NFT.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 51\n  },\n  {\n    \"id\": 5,\n    \"title\": \"Build a University Certification NFT\",\n    \"dashedName\": \"build-a-university-certification-nft\",\n    \"isIntegrated\": true,\n    \"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.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"numberOfLessons\": 1\n  },\n  {\n    \"id\": 6,\n    \"title\": \"Learn Anchor by Building Tic-Tac-Toe: Part 1\",\n    \"dashedName\": \"learn-anchor-by-building-tic-tac-toe-part-1\",\n    \"isIntegrated\": false,\n    \"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.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 68\n  },\n  {\n    \"id\": 7,\n    \"title\": \"Learn Anchor by Building Tic-Tac-Toe: Part 2\",\n    \"dashedName\": \"learn-anchor-by-building-tic-tac-toe-part-2\",\n    \"isIntegrated\": false,\n    \"description\": \"In this course, you will learn how to test the previously built Tic-Tac-Toe game.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 67\n  },\n  {\n    \"id\": 8,\n    \"title\": \"Build an Anchor Leaderboard\",\n    \"dashedName\": \"build-an-anchor-leaderboard\",\n    \"isIntegrated\": true,\n    \"description\": \"In this integrated project, you will use what you previously learnt to build the program logic for a game leaderboard.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"numberOfLessons\": 1\n  },\n  {\n    \"id\": 9,\n    \"title\": \"Learn How to Build a Client-Side App: Part 1\",\n    \"dashedName\": \"learn-how-to-build-a-client-side-app-part-1\",\n    \"isIntegrated\": false,\n    \"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.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 62\n  },\n  {\n    \"id\": 10,\n    \"title\": \"Learn How to Build a Client-Side App: Part 2\",\n    \"dashedName\": \"learn-how-to-build-a-client-side-app-part-2\",\n    \"isIntegrated\": false,\n    \"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.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 35\n  },\n  {\n    \"id\": 11,\n    \"title\": \"Build a Client-Side App\",\n    \"dashedName\": \"build-a-client-side-app\",\n    \"isIntegrated\": true,\n    \"description\": \"In this integrated project, you will use what you previously learnt to build an app your friends can use to message one another.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"numberOfLessons\": 1\n  },\n  {\n    \"id\": 12,\n    \"title\": \"Learn How to Build for Mainnet\",\n    \"dashedName\": \"learn-how-to-build-for-mainnet\",\n    \"isIntegrated\": false,\n    \"description\": \"In this project, you will learn how to build a dApp from start to finish, preparing for deployment to mainnet-beta.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 1\n  },\n  {\n    \"id\": 13,\n    \"title\": \"Learn How to Deploy to Devnet\",\n    \"dashedName\": \"learn-how-to-deploy-to-devnet\",\n    \"isIntegrated\": false,\n    \"description\": \"In this project, you will learn how to put an app on the public devnet.\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"runTestsOnWatch\": true,\n    \"numberOfLessons\": 1\n  },\n  {\n    \"id\": 14,\n    \"title\": \"Build and Deploy Your Freeform App\",\n    \"dashedName\": \"build-and-deploy-your-freeform-app\",\n    \"isIntegrated\": true,\n    \"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!\",\n    \"isPublic\": true,\n    \"currentLesson\": 1,\n    \"numberOfLessons\": 1\n  }\n]\n"
  },
  {
    "path": "config/state.json",
    "content": "{\n  \"currentProject\": null,\n  \"locale\": \"english\"\n}"
  },
  {
    "path": "curriculum/locales/english/build-a-client-side-app.md",
    "content": "# Solana - Build a Client Side App\n\n## 1\n\n### --description--\n\nYou are developing a client-side app to interact with the `mess.so` program.\n\nThe `mess.so` program consists of a `chat` account that stores a list of <= 20 messages.\n\nYou will be working entirely within the `build-a-client-side-app/` directory.\n\n### User Stories\n\n1. You should generate two new keypairs stored in `messer-1.json` and `messer-2.json`.\n2. You should deploy the `mess.so` program to a local Solana validator.\n3. You should airdrop SOL into each of the two keypairs.\n4. You should initialize the `chat` account, by calling the `init` instruction.\n   1. The payer should be one of the two keypairs.\n5. You should send at least 20 messages using your client app.\n   1. At least 5 messages should be sent from each of the two keypairs.\n\n#### Types\n\n- The `mess` program IDL is stored in `mess.ts`.\n\n### Notes\n\n- The `mess.so` program id is `TODO`.\n- The `chat` account has a seed of `\"global\"`.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe mess program should be deployed.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '\n  {\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"getProgramAccounts\",\n    \"params\": [\n      \"BPFLoader2111111111111111111111111111111111\", {\n        \"encoding\": \"base64\",\n        \"dataSlice\": {\n          \"length\": 0,\n          \"offset\": 0\n        }\n      }\n    ]\n}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.exists(jsonOut.result.find(r => r.pubkey === __programId));\n} catch (e) {\n  assert.fail(\n    e,\n    `Try running \\`solana-test-validator --bpf-program ${__programId} mess.so --reset\\``\n  );\n}\n```\n\nThere should be a keypair named `messer-1.json`.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(join(project.dashedName, 'messer-1.json'), constants.F_OK);\n\ntry {\n  const { Keypair } = await import('@solana/web3.js');\n  const keypair = Keypair.fromSecretKey(Uint8Array.from(__messer1_json));\n} catch (e) {\n  assert.fail(e, 'Try running `solana-keygen new --outfile messer-1.json`.');\n}\n```\n\nThere should be a keypair named `messer-2.json`.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(join(project.dashedName, 'messer-2.json'), constants.F_OK);\n\ntry {\n  const { Keypair } = await import('@solana/web3.js');\n  const keypair = Keypair.fromSecretKey(Uint8Array.from(__messer2_json));\n} catch (e) {\n  assert.fail(e, 'Try running `solana-keygen new --outfile messer-2.json`.');\n}\n```\n\nThe `messer-1.json` keypair should have a balance greater than 0 SOL.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ${project.dashedName}/messer-1.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAbove(\n  parseInt(balance),\n  0,\n  'Try running `solana airdrop 1 ./messer-1.json`.'\n);\n```\n\nThe `messer-2.json` keypair should have a balance greater than 0 SOL.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ${project.dashedName}/messer-2.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAbove(\n  parseInt(balance),\n  0,\n  'Try running `solana airdrop 1 ./messer-2.json`.'\n);\n```\n\nThe `chat` account should be initialized.\n\n```js\nconst accountInfo = await __connection.getAccountInfo(__chatPublicKey);\nassert.exists(accountInfo);\n```\n\nThe `messer-1.json` keypair should have sent at least 5 messages.\n\n```js\nconst pubkey = __messer1_keypair.publicKey;\nconst chatData = await program.account.chat.fetch(pubkey);\nconst messages = chatData.messages.filter(m => m.sender.equals(pubkey));\nassert.isAtLeast(messages.length, 5);\n```\n\nThe `messer-2.json` keypair should have sent at least 5 messages.\n\n```js\nconst pubkey = __messer2_keypair.publicKey;\nconst chatData = await program.account.chat.fetch(pubkey);\nconst messages = chatData.messages.filter(m => m.sender.equals(pubkey));\nassert.isAtLeast(messages.length, 5);\n```\n\nAt least 20 messages should have been sent.\n\n```js\nconst chatData = await program.account.chat.fetch(__chatPublicKey);\nassert.isAtLeast(chatData.messages.length, 20);\n```\n\n### --before-all--\n\n```js\nconst { AnchorProvider, setProvider, Program } = await import(\n  '@coral-xyz/anchor'\n);\nconst { PublicKey, Connection, Keypair } = await import('@solana/web3.js');\n\nsetProvider(AnchorProvider.env());\nconst IDL = JSON.parse(await __helpers.getFile('mess.json'));\nconst PROGRAM_ID = new PublicKey(\n  '8D2EQasXmadK7bWhRPrkryhAGYtERQzzGMJVGiisUqqh'\n);\nconst program = new Program(IDL, PROGRAM_ID);\n\nconst connection = new Connection('http://localhost:8899', 'confirmed');\n\nconst [chatPublicKey, _] = PublicKey.findProgramAddressSync(\n  [Buffer.from('global')],\n  new PublicKey('8D2EQasXmadK7bWhRPrkryhAGYtERQzzGMJVGiisUqqh')\n);\n\ntry {\n  const messer1_keypair = JSON.parse(\n    await __helpers.getFile(join(project.dashedName, 'messer-1.json'))\n  );\n  const messer2_keypair = JSON.parse(\n    await __helpers.getFile(join(project.dashedName, 'messer-2.json'))\n  );\n  global.__messer1_json = messer1_keypair;\n  global.__messer2_json = messer2_keypair;\n\n  const keypair1 = Keypair.fromSecretKey(Uint8Array.from(__messer1_keypair));\n  const keypair2 = Keypair.fromSecretKey(Uint8Array.from(__messer2_keypair));\n\n  global.__messer1_keypair = keypair1;\n  global.__messer2_keypair = keypair2;\n} catch (e) {\n  logover.warn(\n    'You need to create two keypairs. Try running `solana-keygen new --outfile messer-1.json` and `solana-keygen new --outfile messer-2.json`.',\n    e\n  );\n}\n\nglobal.__chatPublicKey = chatPublicKey;\nglobal.__connection = connection;\nglobal.__programId = PROGRAM_ID;\nglobal.__program = program;\n```\n\n### --after-all--\n\n```js\ndelete global.__messer1_keypair;\ndelete global.__messer2_keypair;\ndelete global.__chatPublicKey;\ndelete global.__messer1_json;\ndelete global.__messer2_json;\ndelete global.__connection;\ndelete global.__programId;\ndelete global.__program;\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/build-a-smart-contract.md",
    "content": "# Solana - Build a Smart Contract\n\n## 1\n\n### --description--\n\nYou need to create a smart contract in Rust, deploy the contract to your localnet, and write a Nodejs script to interact with the contract.\n\n**User Stories**\n\n- You should generate a new keypair\n  - The keypair should be stored in `wallet.json`\n  - The keypair should be used to deploy the program account\n  - The keypair should **not** use a BIP39 passphrase\n- You should write a smart contract in Rust\n  - The program should be written in `program/src/lib.rs`\n  - The program should exort a `process_instruction` function with the `solana_program::entrypoint` parameter signature\n  - The program should return the `NotEnoughAccountKeys` variant of `ProgramError` if the number of accounts is less than 1\n  - The program should return the `IncorrectProgramId` variant of `ProgramError` if the account owner does not match the program id\n  - The program should own a data account for storing a text message of 280 characters\n    - The program data account should hold data in the form of:\n      ```rust\n      struct MessageAccount {\n        message: String,\n      }\n      ```\n  - The program should deserialize the `InstructionData` into a `String`, and store the string in the program data account\n    - If the `InstructionData` is not deserializable into a `String`, the program should return the `InvalidInstructionData` variant of `ProgramError`\n    - If the `String` length is greater than 280 characters, the program should return the `InvalidInstructionData` variant of `ProgramError`\n    - The `InstructionData` should be padded with space characters to 280 characters\n  - The program should be built using `cargo-build-sbf`\n    - The resulting `.so` and `.json` files should be stored in the `dist/program/` directory\n- You should write a script interacting with the smart contract\n  - The script entrypoint should be `client/main.js`\n  - The script should expect a string as a command line argument\n    - If no argument is provided, the script should throw an error with the message `\"No message provided\"`\n    - The string should be sent as the instruction data when calling the smart contract\n  - The script should use the account stored in `wallet.json` to pay for transactions\n  - The script should use the `dist/program/` keypair file to get the program id\n  - The script should create a program data account, if one does not already exist\n    - The program data account public key should be created using `\"fcc-seed\"` as the seed\n- You should have a local Solana cluster running at port `8899`\n  - The program should be deployed to the local cluster\n  - The program data account should be created on the local cluster\n\n**NOTES:**\n\n- All referenced paths are relative to `build-a-smart-contract/`\n\n### --tests--\n\nYou should have a `wallet.json` file in the root of your project.\n\n```js\nconst walletExists = __helpers.fileExists(join(__loc, 'wallet.json'));\nassert.isTrue(walletExists, 'wallet.json should exist');\n```\n\nYou should have a local Solana cluster running at port `8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nYou should have a `dist/program/` directory.\n\n```js\nconst distExists = __helpers.fileExists(join(__loc, 'dist'));\nassert.isTrue(distExists, 'dist/ should exist');\nconst programExists = __helpers.fileExists(join(__loc, 'dist', 'program'));\nassert.isTrue(programExists, 'dist/program/ should exist');\n```\n\nYou should have a `.so` file in the `dist/program/` directory.\n\n```js\nconst dir = await __helpers.getDirectory(join(__loc, 'dist', 'program'));\nlet program;\nfor (const file of dir) {\n  if (file.endsWith('.so')) {\n    program = file;\n  }\n}\nassert.exists(program, 'dist/program/ should have a .so file');\n```\n\nYou should have a `.json` file in the `dist/program/` directory.\n\n```js\nconst dir = await __helpers.getDirectory(join(__loc, 'dist', 'program'));\nlet keypair;\nfor (const file of dir) {\n  if (file.endsWith('.json')) {\n    keypair = file;\n  }\n}\nassert.exists(keypair, 'dist/program/ should have a .json file');\n```\n\nYou should deploy the `.so` file as an executable program to the local net.\n\n```js\nconst programKeypair = await __helpers.getProgramKeypair();\nassert.exists(programKeypair, 'dist/program/ does not have a .json file');\nconst programId = programKeypair.publicKey;\nconst connection = __helpers.establishConnection();\nassert.exists(connection, 'unable to establish connection to localnet');\nconst programAccountInfo = await connection.getAccountInfo(programId);\nassert.exists(programAccountInfo, 'Program not deployed to the local net');\nassert.equal(\n  programAccountInfo.executable,\n  true,\n  'Program is not deployed as an executable'\n);\n```\n\nThe owner of the program account should be the associated account of the `wallet.json` keypair.\n\n```js\nconst camperKeypair = await __helpers.getCamperKeypair();\nassert.exists(camperKeypair, 'wallet.json does not exist');\nconst connection = __helpers.establishConnection();\nassert.exists(connection, 'unable to establish connection to localnet');\nconst camperAccount = await connection.getAccountInfo(camperKeypair.publicKey);\nassert.exists(camperAccount, 'wallet.json does not have an associated account');\nconst programKeypair = await __helpers.getProgramKeypair();\nassert.exists(programKeypair, 'dist/program/ does not have a .json file');\nconst programId = programKeypair.publicKey;\nconst programAccountInfo = await connection.getAccountInfo(programId);\nassert.exists(programAccountInfo, 'Program not deployed to the local net');\nassert.equal(\n  programAccountInfo.executable,\n  true,\n  'Program is not deployed as an executable'\n);\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `solana program show ${programId}`\n);\nassert.include(stdout, 'Authority:', \"Program owner not found, run 'solana program show <program_id>' and make sure there's an 'Authority' ID\");\nconst authority = stdout.match(/Authority: \\S+/gm)\nassert.equal(\n  authority[0].split(' ')[1],\n  camperKeypair.publicKey.toBase58(),\n  'Program account owner does not match the wallet.json account owner'\n);\n```\n\nThe program should return the `IncorrectProgramId` variant of `ProgramError` if the account owner does not match the program id.\n\n```js\n// Should pass `owner_not_program_id` test\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `cargo test owner_not_program_id`, `${__loc}/program`\n);\nassert.include(stdout, 'test owner_not_program_id ... ok');\n```\n\nThe program should return the `NotEnoughAccountKeys` variant of `ProgramError` if the number of account keys is less than 1.\n\n```js\n// Should pass `no_accounts` test\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `cargo test no_accounts`, `${__loc}/program`\n);\nassert.include(stdout, 'test no_accounts ... ok');\n```\n\nThe program should own a data account for storing a text message of 280 characters.\n\n```js\nconst programKeypair = await __helpers.getProgramKeypair();\nassert.exists(programKeypair, 'dist/program/ does not have a .json file');\nconst programId = programKeypair.publicKey;\nconst connection = __helpers.establishConnection();\nassert.exists(connection, 'unable to establish connection to localnet');\nconst dataAccountPublicKey = await __helpers.getDataAccountPublicKey();\nassert.exists(dataAccountPublicKey, 'Unable to get data account public key');\nconst dataAccountInfo = await connection.getAccountInfo(dataAccountPublicKey);\nassert.exists(dataAccountInfo, 'Data account does not exist');\nassert.equal(\n  dataAccountInfo.owner.toBase58(),\n  programId.toBase58(),\n  'Data account owner does not match program id'\n);\n```\n\nThe data account should be created using the `wallet.json` public key, `\"fcc-seed\"` as the seed, and the program id as the owner.\n\n```js\nconst expectedDataAccountPublicKey = await __helpers.getDataAccountPublicKey();\nassert.exists(\n  expectedDataAccountPublicKey,\n  'Unable to get data account public key'\n);\n```\n\nThe program should deserialize the `InstructionData` into a `String`, and store the string in the program data account.\n\n```js\n// Should pass `instruction_is_deserialized` test\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `cargo test instruction_is_deserialized`, `${__loc}/program`\n);\nassert.include(stdout, 'test instruction_is_deserialized ... ok');\n```\n\nIf the `InstructionData` is not deserializable into a `String`, the program should return the `InvalidInstructionData` variant of `ProgramError`.\n\n```js\n// Should pass `instruction_not_string` test\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `cargo test instruction_not_string`, `${__loc}/program`\n);\nassert.include(stdout, 'test instruction_not_string ... ok');\n```\n\nIf the `String` length is greater than 280 characters, the program should return the `InvalidInstructionData` variant of `ProgramError`.\n\n```js\n// Should pass `instruction_too_long` test\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `cargo test instruction_too_long`, `${__loc}/program`\n);\nassert.include(stdout, 'test instruction_too_long ... ok');\n```\n\nThe `InstructionData` should be padded with space characters to 280 characters.\n\n```js\n// Should pass `instruction_data_padded` test\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `cargo test instruction_data_padded`, `${__loc}/program`\n);\nassert.include(stdout, 'test instruction_data_padded ... ok');\n```\n\nYou should write a `client/main.js` script interacting with the smart contract.\n\n```js\nconst clientExists = await __helpers.fileExists(join(__loc, 'client'));\nassert.isTrue(clientExists, 'client/ does not exist');\nconst mainExists = await __helpers.fileExists(join(__loc, 'client', 'main.js'));\nassert.isTrue(mainExists, 'client/main.js does not exist');\n```\n\nCalling `node client/main.js` should throw an error with the message `\"No message provided\"`.\n\n```js\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `node client/main.js`, __loc\n);\nassert.include(stderr, 'No message provided');\n```\n\nCalling `node client/main.js \"Test string\"` should change the message stored in the program data account.\n\n```js\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  `node client/main.js \"Test string\"`, __loc\n);\nconst connection = __helpers.establishConnection();\nassert.exists(connection, 'unable to establish connection to localnet');\nconst dataAccountPublicKey = await __helpers.getDataAccountPublicKey();\nassert.exists(dataAccountPublicKey, 'Unable to get data account public key');\nconst message = await __helpers.getMessage(connection, dataAccountPublicKey);\nassert.include(message, 'Test string');\nassert.equal(message, 'Test string'.padEnd(280, ' '));\n```\n\n### --before-all--\n\n```js\nglobal.__loc = 'build-a-smart-contract';\n```\n\n### --after-all--\n\n```js\ndelete global.__loc;\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/build-a-university-certification-nft.md",
    "content": "# Solana - Build a University Certification NFT\n\n## 1\n\n### --description--\n\nYou 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.\n\n**User Stories**\n\n1. You should generate a new keypair and store it in a file called `solana-university-wallet.json`\n2. You should use the `solana-university-wallet.json` keypair as the payer for all transactions\n3. You should generate two more keypairs stored in `student-1.json` and `student-2.json`\n4. You should deploy the Metaplex Token Metadata program to your local Solana cluster\n5. You should create a connection to your local cluster which should be used for all transactions\n6. You should use the provided `localStorage` function in `utils.js` for the Metaplex storage driver\n7. You should export a function named `uploadFile` from `index.js` with the signature defined in `index.d.ts`\n   1. `uploadFile` should upload the provided `metaplexFile` parameter to the storage driver\n   2. `uploadFile` should upload metadata consiting of the image URL returned from the storage driver, and the `fileName` property of the `metaplexFile`\n   3. `uploadFile` should use the provided `payer` parameter as the fee payer for the metadata upload transaction\n8. You should export a function named `createMintAccount` from `index.js` with the signature defined in `index.d.ts`\n   1. `createMintAccount` should create and initialise a new NFT mint, using the provided `payer` parameter as the fee payer, mint authority, and freeze authority\n9. You should export a function named `getMintAccounts` from `index.js` with the signature defined in `index.d.ts`\n   1. `getMintAccounts` should return all mint accounts owned by the provided `payer` parameter\n10. You should export a function named `createTokenAccount` from `index.js` with the signatrure defined in `index.d.ts`\n11. `createTokenAccount` should get or create an associated token account for the provided `ownerAddress` parameter\n12. `createTokenAccount` should use the provided `payer` parameter to pay for the transaction fee\n13. `createTokenAccount` should use the provided `mintAddress` parameter as the mint associated with the token account\n14. You should export a function named `mintToken` from `index.js` with the signature defined in `index.d.ts`\n    1. `mintToken` should mint an NFT to the associated token account of the provided account (`ownerAddress`), using the existing mint account (`mintAddress`)\n    2. `mintToken` should use the provided `uri` parameter to point to the JSON metadata\n    3. `mintToken` should use the provided `year` parameter to give the NFT a `name` of `SOL-{year}`\n    4. `mintToken` should mint an NFT with `0` royalties when resold\n    5. `mintToken` should mint an NFT with a `symbol` of `SOLU`\n    6. `mintToken` should mint an NFT that is set to immutable\n    7. `mintToken` should mint an NFT owned by the associated token account of the provided account (`ownerAddress`)\n    8. `mintToken` should mint an NFT with an update authority set to the provided `payer` parameter\n    9. `mintToken` should mint an NFT with an mint authority set to the provided `payer` parameter\n15. You should export a function named `getNFTs` from `index.js` with the signature defined in `index.d.ts`\n    1. `getNFTs` should return all NFTs owned by the provided `ownerAddress` parameter\n16. You should use the Solana Univeristy Dashboard (`client/` _see below_) to create a new mint account\n    1. The `payer` should be the `solana-university-wallet.json` keypair\n17. 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\n18. You should use the Solana University Dashboard to upload a metaplex file to the storage driver\n    1. You can use any image file for this, but one is provided: `solanaLogoMark.png`\n19. You should use the Solana University Dashboard to mint one token to each new token account\n\n**Types**\n\nThe expected signatures for your functions are visible in the `index.d.ts` file. This file should **not** be modified.\n\n**Commands**\n\n| Command                | Description                           |\n| ---------------------- | ------------------------------------- |\n| `npm run start:server` | Start the local storage driver        |\n| `npm run start:client` | Start the Solana University dashboard |\n\n**Notes**\n\n- You should work entirely within the `build-a-university-certification-nft` directory.\n- You can use provided Solana University dashboard (`client/`) to test and play around with your code.\n- Useful links to API documentation:\n  - [Solana JS SDK](https://solana-labs.github.io/solana-web3.js/)\n  - [Metaplex JS SDK](https://github.com/metaplex-foundation/js)\n\n### --tests--\n\nYou should deploy the Metaplex Token Metadata program to the local Solana cluster.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '\n  {\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"getProgramAccounts\",\n    \"params\": [\n      \"BPFLoaderUpgradeab1e11111111111111111111111\", {\n        \"encoding\": \"base64\",\n        \"dataSlice\": {\n          \"length\": 0,\n          \"offset\": 0\n        }\n      }\n    ]\n}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.exists(\n    jsonOut.result.find(\n      r => r.pubkey === 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'\n    )\n  );\n} catch (e) {\n  assert.fail(\n    e,\n    'Try running `solana-test-validator --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./mlp_token.so --reset`'\n  );\n}\n```\n\nThe `~/.config/solana/cli/config.yml` file should have the URL set to `localhost`.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput('solana config get');\nconst toMatch = 'RPC URL: http://localhost:8899';\nassert.include(stdout, toMatch);\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe local storage driver should be running at `http://localhost:3002/`.\n\n```js\ntry {\n  const res = await fetch('http://localhost:3002/status/ping');\n  // Response should be 200 with text \"pong\"\n  if (res.status === 200) {\n    const text = await res.text();\n    if (text !== 'pong') {\n      throw new Error(`Expected response text \"pong\", got ${text}`);\n    }\n  } else {\n    throw new Error(`Expected status code 200, got ${res.status}`);\n  }\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should create a new keypair named `student-1.json`.\n\n```js\nconst walletPath = join(__projectDir, 'student-1.json');\nconst walletJsonExists = __helpers.fileExists(walletPath);\nassert.isTrue(walletJsonExists, 'The `student-1.json` file should exist');\nconst walletJson = JSON.parse(await __helpers.getFile(walletPath));\nassert.isArray(\n  walletJson,\n  'The `student-1.json` file should be an array of numbers.\\nRun `solana-keygen new --outfile student-1.json` to create a new keypair.'\n);\n```\n\nYou should create a new keypair named `student-2.json`.\n\n```js\nconst walletPath = join(__projectDir, 'student-2.json');\nconst walletJsonExists = __helpers.fileExists(walletPath);\nassert.isTrue(walletJsonExists, 'The `student-2.json` file should exist');\nconst walletJson = JSON.parse(await __helpers.getFile(walletPath));\nassert.isArray(\n  walletJson,\n  'The `student-2.json` file should be an array of numbers.\\nRun `solana-keygen new --outfile student-2.json` to create a new keypair.'\n);\n```\n\nYou should create a new keypair named `solana-university-wallet.json`.\n\n```js\nconst walletPath = join(__projectDir, 'solana-university-wallet.json');\nconst walletJsonExists = __helpers.fileExists(walletPath);\nassert.isTrue(\n  walletJsonExists,\n  'The `solana-university-wallet.json` file should exist'\n);\nconst walletJson = JSON.parse(await __helpers.getFile(walletPath));\nassert.isArray(\n  walletJson,\n  '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.'\n);\n```\n\nThe `index.js` file should export a `uploadFile` function.\n\n```js\nconst { uploadFile } = await __helpers.importSansCache(\n  join(__projectDir, 'index.js')\n);\nassert.isFunction(uploadFile);\n```\n\nThe `uploadFile` function should match the `index.d.ts` signature definition.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id.name === 'uploadFile';\n});\nassert.exists(\n  functionDeclaration,\n  'A function named `uploadFile` should exist'\n);\n\nconst exports = babelisedCode.getType('ExportNamedDeclaration');\nconst functionIsExported = exports.some(e => {\n  return (\n    e.declaration?.id?.name === 'uploadFile' ||\n    e.specifiers?.find(s => s.exported.name === 'uploadFile')\n  );\n});\nassert.isTrue(\n  functionIsExported,\n  'The `uploadFile` function should be exported'\n);\n```\n\nThe `index.js` file should export a `createMintAccount` function.\n\n```js\nconst { createMintAccount } = await __helpers.importSansCache(\n  './' + join(__projectDir, 'index.js')\n);\nassert.isFunction(createMintAccount);\n```\n\nThe `createMintAccount` function should match the `index.d.ts` signature definition.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id.name === 'createMintAccount';\n});\nassert.exists(\n  functionDeclaration,\n  'A function named `createMintAccount` should exist'\n);\n\nconst exports = babelisedCode.getType('ExportNamedDeclaration');\nconst functionIsExported = exports.some(e => {\n  return (\n    e.declaration?.id?.name === 'createMintAccount' ||\n    e.specifiers?.find(s => s.exported.name === 'createMintAccount')\n  );\n});\nassert.isTrue(\n  functionIsExported,\n  'The `createMintAccount` function should be exported'\n);\n```\n\nThe `createMintAccount` function should create a new mint account for an NFT.\n\n```js\n// `payer` should be payer\n// `payer` should be mint authority and freeze authority\n// The mint should have 0 decimal places\ntry {\n  const { createMintAccount } = await __helpers.importSansCache(\n    './' + join(__projectDir, 'index.js')\n  );\n  const { Keypair, Connection } = await import('@solana/web3.js');\n  const { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');\n\n  const connection = new Connection('http://127.0.0.1:8899', 'finalized');\n  const payer = Keypair.generate();\n\n  async function airdrop() {\n    const airdropSignature = await connection.requestAirdrop(\n      payer.publicKey,\n      1000000000\n    );\n    // Confirm transaction\n    await connection.confirmTransaction(airdropSignature);\n  }\n  await airdrop();\n\n  const mint = await createMintAccount({ payer });\n\n  const mintAccounts = await connection.getParsedProgramAccounts(\n    TOKEN_PROGRAM_ID,\n    {\n      filters: [\n        {\n          dataSize: 82\n        },\n        {\n          memcmp: {\n            offset: 4,\n            bytes: payer.publicKey.toBase58()\n          }\n        }\n      ]\n    }\n  );\n  const mintAccount = mintAccounts[0];\n  assert.exists(mintAccount, 'The mint account should exist');\n  assert.equal(\n    mintAccount.account.data.parsed.info.mintAuthority,\n    payer.publicKey.toBase58(),\n    'The mint authority should be the payer'\n  );\n  assert.equal(\n    mintAccount.account.data.parsed.info.freezeAuthority,\n    payer.publicKey.toBase58(),\n    'The freeze authority should be the payer'\n  );\n  assert.equal(\n    mintAccount.account.data.parsed.info.decimals,\n    0,\n    'The mint should have 0 decimal places'\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nThe `createMintAccount` function should return the `PublicKey` of the mint account.\n\n```js\ntry {\n  const { createMintAccount } = await __helpers.importSansCache(\n    './' + join(__projectDir, 'index.js')\n  );\n  const { Keypair, Connection } = await import('@solana/web3.js');\n  const { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');\n\n  const connection = new Connection('http://127.0.0.1:8899', 'finalized');\n\n  const payer = Keypair.generate();\n\n  async function airdrop() {\n    const airdropSignature = await connection.requestAirdrop(\n      payer.publicKey,\n      1000000000\n    );\n    // Confirm transaction\n    await connection.confirmTransaction(airdropSignature);\n  }\n  await airdrop();\n\n  const mint = await createMintAccount({ payer });\n\n  const mintAccounts = await connection.getParsedProgramAccounts(\n    TOKEN_PROGRAM_ID,\n    {\n      filters: [\n        {\n          dataSize: 82\n        },\n        {\n          memcmp: {\n            offset: 4,\n            bytes: payer.publicKey.toBase58()\n          }\n        }\n      ]\n    }\n  );\n  const mintAccount = mintAccounts[0];\n\n  assert.equal(\n    mintAccount.pubkey.toBase58(),\n    mint.toBase58(),\n    'The mint account should be returned'\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nThe `index.js` file should export a `getMintAccounts` function.\n\n```js\nconst { getMintAccounts } = await __helpers.importSansCache(\n  './' + join(__projectDir, 'index.js')\n);\nassert.isFunction(getMintAccounts);\n```\n\nThe `getMintAccounts` function should match the `index.d.ts` signature definition.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id.name === 'getMintAccounts';\n});\nassert.exists(\n  functionDeclaration,\n  'A function named `getMintAccounts` should exist'\n);\n\nconst exports = babelisedCode.getType('ExportNamedDeclaration');\nconst functionIsExported = exports.some(e => {\n  return (\n    e.declaration?.id?.name === 'getMintAccounts' ||\n    e.specifiers?.find(s => s.exported.name === 'getMintAccounts')\n  );\n});\nassert.isTrue(\n  functionIsExported,\n  'The `getMintAccounts` function should be exported'\n);\n```\n\nThe `getMintAccounts` function should return all mint accounts owned by the `payer` argument.\n\n```js\ntry {\n  const { getMintAccounts, createMintAccount } =\n    await __helpers.importSansCache('./' + join(__projectDir, 'index.js'));\n  const { Keypair, Connection } = await import('@solana/web3.js');\n  const { createMint } = await import('@solana/spl-token');\n\n  const connection = new Connection('http://127.0.0.1:8899', 'finalized');\n\n  const payer = Keypair.generate();\n\n  async function airdrop() {\n    const airdropSignature = await connection.requestAirdrop(\n      payer.publicKey,\n      1000000000\n    );\n    // Confirm transaction\n    await connection.confirmTransaction(airdropSignature);\n  }\n  await airdrop();\n\n  const mintAuthority = payer.publicKey;\n  const freezeAuthority = payer.publicKey;\n  const mint = await createMint(\n    connection,\n    payer,\n    mintAuthority,\n    freezeAuthority,\n    0\n  );\n\n  const mintAccounts = await getMintAccounts({ payer });\n  assert.isArray(mintAccounts, '`getMintAccounts` should return an array');\n  const mintAccount = mintAccounts[0];\n  assert.exists(\n    mintAccount,\n    'This test creates a mint account. At least one account should exist'\n  );\n  assert.equal(\n    mintAccount.pubkey.toBase58(),\n    mint.toBase58(),\n    'The mint account should match the payer'\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nThe `index.js` file should export a `createTokenAccount` function.\n\n```js\nconst { createTokenAccount } = await __helpers.importSansCache(\n  './' + join(__projectDir, 'index.js')\n);\nassert.isFunction(createTokenAccount);\n```\n\nThe `createTokenAccount` function should match the `index.d.ts` signature definition.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id.name === 'createTokenAccount';\n});\nassert.exists(\n  functionDeclaration,\n  'A function named `createTokenAccount` should exist'\n);\n\nconst exports = babelisedCode.getType('ExportNamedDeclaration');\nconst functionIsExported = exports.some(e => {\n  return (\n    e.declaration?.id?.name === 'createTokenAccount' ||\n    e.specifiers?.find(s => s.exported.name === 'createTokenAccount')\n  );\n});\nassert.isTrue(\n  functionIsExported,\n  'The `createTokenAccount` function should be exported'\n);\n```\n\nThe `index.js` file should export a `mintToken` function.\n\n```js\nconst { mintToken } = await __helpers.importSansCache(\n  './' + join(__projectDir, 'index.js')\n);\nassert.isFunction(mintToken);\n```\n\nThe `mintToken` function should match the `index.d.ts` signature definition.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id.name === 'mintToken';\n});\nassert.exists(functionDeclaration, 'A function named `mintToken` should exist');\n\nconst exports = babelisedCode.getType('ExportNamedDeclaration');\nconst functionIsExported = exports.some(e => {\n  return (\n    e.declaration?.id?.name === 'mintToken' ||\n    e.specifiers?.find(s => s.exported.name === 'mintToken')\n  );\n});\nassert.isTrue(\n  functionIsExported,\n  'The `mintToken` function should be exported'\n);\n```\n\nThe `index.js` file should export a `getNFTs` function.\n\n```js\nconst { getNFTs } = await __helpers.importSansCache(\n  './' + join(__projectDir, 'index.js')\n);\nassert.isFunction(getNFTs);\n```\n\nThe `getNFTs` function should match the `index.d.ts` signature definition.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id.name === 'getNFTs';\n});\nassert.exists(functionDeclaration, 'A function named `getNFTs` should exist');\n\nconst exports = babelisedCode.getType('ExportNamedDeclaration');\nconst functionIsExported = exports.some(e => {\n  return (\n    e.declaration?.id?.name === 'getNFTs' ||\n    e.specifiers?.find(s => s.exported.name === 'getNFTs')\n  );\n});\nassert.isTrue(functionIsExported, 'The `getNFTs` function should be exported');\n```\n\nThe `getNFTs` function should return all NFTs owned by the `ownerAddress` argument.\n\n```js\ntry {\n  const { Connection, Keypair } = await import('@solana/web3.js');\n  const { Metaplex } = await import('@metaplex-foundation/js');\n  const { getNFTs } = await __helpers.importSansCache(\n    './' + join(__projectDir, 'index.js')\n  );\n\n  // Create two NFTs owned by `ownerAddress`\n  const connection = new Connection('http://127.0.0.1:8899', 'finalized');\n  const payer = Keypair.generate();\n  const owner = Keypair.generate();\n  const ownerAddress = owner.publicKey;\n\n  async function airdrop(acc) {\n    const airdropSignature = await connection.requestAirdrop(\n      acc.publicKey,\n      1000000000\n    );\n    // Confirm transaction\n    await connection.confirmTransaction(airdropSignature);\n  }\n\n  await airdrop(payer);\n  await airdrop(owner);\n\n  const metaplex = Metaplex.make(connection);\n\n  const createResponse = await metaplex.nfts().create(\n    {\n      tokenOwner: ownerAddress,\n      uri: 'http://localhost:1213',\n      name: `Test`,\n      sellerFeeBasisPoints: 0,\n      maxSupply: 1,\n      symbol: 'fCCTest',\n      isMutable: false,\n      updateAuthority: payer,\n      mintAuthority: payer\n    },\n    { payer }\n  );\n\n  // Call `getNFTs`\n\n  const nfts = await getNFTs({ ownerAddress, payer });\n  assert.isArray(nfts, '`getNFTs` should return an array');\n  assert.equal(\n    nfts.length,\n    1,\n    'The `getNFTs` function should return all NFTs owned by the `ownerAddress` argument'\n  );\n\n  const createResponse2 = await metaplex.nfts().create(\n    {\n      tokenOwner: ownerAddress,\n      uri: 'http://localhost:1213',\n      name: `Test2`,\n      sellerFeeBasisPoints: 0,\n      maxSupply: 1,\n      symbol: 'fCCTest',\n      isMutable: false,\n      updateAuthority: payer,\n      mintAuthority: payer\n    },\n    { payer }\n  );\n\n  const nfts2 = await getNFTs({ ownerAddress, payer });\n  assert.isArray(nfts2, '`getNFTs` should return an array');\n  assert.equal(\n    nfts2.length,\n    2,\n    'The `getNFTs` function should return all NFTs owned by the `ownerAddress` argument'\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n### --before-all--\n\n```js\nconst __projectDir = 'build-a-university-certification-nft';\nconst codeString = await __helpers.getFile(\n  './' + join(__projectDir, 'index.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nglobal.__projectDir = __projectDir;\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.__projectDir;\ndelete global.babelisedCode;\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/build-an-anchor-leaderboard.md",
    "content": "# Solana - Build an Anchor Leaderboard\n\n## 1\n\n### --description--\n\nYou 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.\n\nYou 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.\n\n### User Stories\n\n#### Setup\n\n1. You should generate a new keypair and store it in a file called `game-owner.json`.\n2. You should add the correct program id to the `programs.localnet.rock_destroyer` key in the `Anchor.toml` file.\n\n#### Program\n\n1. You should add the correct program id to the `declare_id!` call in the `lib.rs` file.\n\n**`initialize_leaderboard`**\n\n1. The `rock_destroyer` program should expose an `initialize_leaderboard` instruction handler.\n2. The `initialize_leaderboard` instruction handler should take a context generic over an `InitializeLeaderboard` accounts struct.\n3. The `initialize_leaderboard` instruction handler should initialize the `leaderboard` account with the `players` field set to an empty vector.\n\n**`InitializeLeaderboard`**\n\n1. The `leaderboard` account should be initialized, if it does not already exist.\n   - This should be payed for by the `game_owner` account\n   - The correct amount of space for 5 players should be allocated\n   - The PDA should be seeded with `\"leaderboard\"` and the `game_owner` public key\n2. The `game_owner` account should be a signer.\n   - The following constraints should be enforced:\n     - The account should be mutable\n     - The account public key should match the `game-owner.json` file public key\n     - The account owner should be the system program\n\n**`new_game`**\n\n1. The `rock_destroyer` program should expose a `new_game` instruction handler.\n2. The `new_game` instruction handler should take a context generic over a `NewGame` accounts struct.\n3. The `new_game` instruction handler should take a `String` argument.\n4. The `new_game` instruction handler should transfer 1 SOL from the `user` account to the `game_owner` account.[^1]\n5. The `new_game` instruction handler should add a new `Player` to the leaderboard with:\n   - `username` set to the `String` argument\n   - `pubkey` set to the `user` account public key\n   - `score` set to `0`\n   - `has_payed` set to `true`\n6. If the leaderboard is full, the player with the lowest score should be replaced.\n\n**`NewGame`**\n\n1. The `user` account should be a signer.\n   - The following constraints should be enforced:\n     - The account should be mutable\n2. The `game_owner` account should be an unchecked account.\n   - The following constraints should be enforced:\n     - The account should be mutable\n     - The account public key should match the `game-owner.json` file public key\n     - The account owner should be the system program\n3. The `leaderboard` account should be mutable.\n\n**`add_player_to_leaderboard`**\n\n1. The `rock_destroyer` program should expose an `add_player_to_leaderboard` instruction handler.\n2. The `add_player_to_leaderboard` instruction handler should take a context generic over an `AddPlayerToLeaderboard` accounts struct.\n3. The `add_player_to_leaderboard` instruction handler should take a `u64` argument.\n4. The player matching the user account public key should be updated with:\n   - `score` set to the `u64` argument\n   - `has_payed` set to `false`\n5. If no player matching the user account public key exists and has payed, an Anchor error variant of `PlayerNotFound` should be returned.\n\n**`AddPlayerToLeaderboard`**\n\n1. The `leaderboard` account should be mutable.\n2. The `user` account should be a signer.\n   - The following constraints should be enforced:\n     - The account should be mutable\n\n#### Tests\n\n1. There should be an `it` block named `\"initializes leaderboard\"`.\n\n- Call the `initialize_leaderboard` instruction\n- Assert the `leaderboard` account equals `{ players: [] }`\n\n2. There should be an `it` block named `\"creates a new game\"`.\n\n- Call the `new_game` instruction with a `username` argument of `\"camperbot\"`\n- Assert the `leaderboard` account has at least one player\n- Assert the player has the correct `username`\n- Assert the player has the correct `pubkey`\n- Assert the player has a `hasPayed` value of `true`\n- Assert the player has a `score` value of `0`\n- Assert the balance of the `user` account has decreased by at least 1 SOL (_remember transaction fees_)\n\n3. There should be an `it` block named `\"adds a player to the leaderboard\"`.\n\n- Call the `add_player_to_leaderboard` instruction with an argument of `100`\n- Assert a player has a `score` value of `100`\n- Assert a player has a `hasPayed` value of `false`\n\n4. There should be an `it` block named `\"\n\n- Assert the `PlayerNotFound` error variant is returned when the `user` account has not payed\n\n#### Types\n\n<details>\n  <summary><code>InitializeLeaderboard</code></summary>\n\n```rust\nleaderboard: Account<'info, Leaderboard>,\ngame_owner: Signer<'info>,\nsystem_program: Program<'info, System>,\n```\n\n</details>\n\n<details>\n  <summary><code>NewGame</code></summary>\n\n```rust\nuser: Signer<'info>,\ngame_owner: AccountInfo<'info>,\nleaderboard: Account<'info, Leaderboard>,\nsystem_program: Program<'info, System>,\n```\n\n</details>\n\n<details>\n  <summary><code>AddPlayerToLeaderboard</code></summary>\n\n```rust\nleaderboard: Account<'info, Leaderboard>,\nuser: Signer<'info>,\n```\n\n</details>\n\n<details>\n  <summary><code>Leaderboard</code></summary>\n\n```rust\nplayers: Vec<Player>\n```\n\n</details>\n\n<details>\n  <summary><code>Player</code></summary>\n\n```rust\nusername: String, // max length 32\npubkey: Pubkey,\nscore: u64,\nhas_payed: bool,\n```\n\n</details>\n\n### Notes\n\n- You should not add any external dependencies to the `package.json` file for the tests\n  - You have access to `chai`\n- Many tests rely on previous user stories being correctly implemented\n\n[^1]: Hint: You can use the `transfer` function from the `system_instruction` module in the `solana_program` crate.\n\n### --tests--\n\nYou should generate a new keypair and store it in a file called `game-owner.json`.\n\n```js\ntry {\n  const fileExists = await __fsp.access(\n    __path.join(__projectDir, './game-owner.json'),\n    __fsp.constants.F_OK\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should add the correct program id to the `programs.localnet.rock_destroyer` key in the `Anchor.toml` file.\n\n```js\nconst anchorToml = await __fsp.readFile(\n  __path.join(__projectDir, 'Anchor.toml'),\n  'utf-8'\n);\nconst actualProgramId = anchorToml.match(/rock_destroyer = \"(.*)\"/)?.[1];\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  __projectDir\n);\nconst expectedProgramId = stdout.match(/rock_destroyer: (.*)/)?.[1];\nassert.equal(actualProgramId, expectedProgramId);\n```\n\nYou should add the correct program id to the `declare_id!` call in the `lib.rs` file.\n\n```js\nconst librs = await __fsp.readFile(\n  __path.join(__projectDir, 'programs/rock-destroyer/src/lib.rs'),\n  'utf-8'\n);\nconst actualProgramId = librs.match(/declare_id\\!\\((.*)\\)/)?.[1];\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  __projectDir\n);\nconst expectedProgramId = stdout.match(/rock_destroyer: (.*)/)?.[1];\nassert.equal(actualProgramId.replaceAll(/['\"`]/g), expectedProgramId);\n```\n\nThe `rock_destroyer` program should expose an `initialize_leaderboard` instruction handler.\n\n```js\nconst testDir = await __createTestDir(4);\nawait __buildTestDir(4);\nconst { RockDestroyer } = await __helpers.importSansCache(\n  __path.join(testDir, 'target/types/rock_destroyer')\n);\nconst ixs = RockDestroyer.instructions;\nconst initializeLeaderboardIx = ixs.find(\n  ix => ix.name === 'initializeLeaderboard'\n);\nassert.exists(\n  initializeLeaderboardIx,\n  'The `RockDestroyer` object in `target/types/rock_destroyer` should have an `instructions[].name` property equal to `initializeLeaderboard`'\n);\n```\n\nThe `initialize_leaderboard` instruction handler should take a context generic over an `InitializeLeaderboard` accounts struct.\n\n```js\nconst testDir = await __createTestDir(5);\nawait __buildTestDir(5);\nconst { RockDestroyer } = await __helpers.importSansCache(\n  __path.join(testDir, 'target/types/rock_destroyer')\n);\nconst ixs = RockDestroyer.instructions;\nconst initializeLeaderboardIx = ixs.find(\n  ix => ix.name === 'initializeLeaderboard'\n);\nconst accounts = initializeLeaderboardIx.accounts;\nassert.deepInclude(accounts, {\n  name: 'leaderboard',\n  isMut: true,\n  isSigner: false\n});\nassert.deepInclude(accounts, {\n  name: 'gameOwner',\n  isMut: true,\n  isSigner: true\n});\nassert.deepInclude(accounts, {\n  name: 'systemProgram',\n  isMut: false,\n  isSigner: false\n});\n```\n\nThe `rock_destroyer` program should expose a `new_game` instruction handler.\n\n```js\nconst testDir = await __createTestDir(4);\nawait __buildTestDir(4);\nconst { RockDestroyer } = await __helpers.importSansCache(\n  __path.join(testDir, 'target/types/rock_destroyer')\n);\nconst ixs = RockDestroyer.instructions;\nconst newGameIx = ixs.find(ix => ix.name === 'newGame');\nassert.exists(\n  newGameIx,\n  'The `RockDestroyer` object in `target/types/rock_destroyer` should have an `instructions[].name` property equal to `newGame`'\n);\n```\n\nThe `new_game` instruction handler should take a context generic over a `NewGame` accounts struct.\n\n```js\nconst testDir = await __createTestDir(6);\nawait __buildTestDir(6);\nconst { RockDestroyer } = await __helpers.importSansCache(\n  __path.join(testDir, 'target/types/rock_destroyer')\n);\nconst ixs = RockDestroyer.instructions;\nconst newGameIx = ixs.find(ix => ix.name === 'newGame');\nconst accounts = newGameIx.accounts;\nassert.deepInclude(accounts, {\n  name: 'user',\n  isMut: true,\n  isSigner: true\n});\nassert.deepInclude(accounts, {\n  name: 'leaderboard',\n  isMut: true,\n  isSigner: false\n});\nassert.deepInclude(accounts, {\n  name: 'gameOwner',\n  isMut: true,\n  isSigner: false\n});\nassert.deepInclude(accounts, {\n  name: 'systemProgram',\n  isMut: false,\n  isSigner: false\n});\n```\n\nThe `rock_destroyer` program should expose an `add_player_to_leaderboard` instruction handler.\n\n```js\nconst testDir = await __createTestDir(4);\nawait __buildTestDir(4);\nconst { RockDestroyer } = await __helpers.importSansCache(\n  __path.join(testDir, 'target/types/rock_destroyer')\n);\nconst ixs = RockDestroyer.instructions;\nconst initializeLeaderboardIx = ixs.find(\n  ix => ix.name === 'addPlayerToLeaderboard'\n);\nassert.exists(\n  initializeLeaderboardIx,\n  'The `RockDestroyer` object in `target/types/rock_destroyer` should have an `instructions[].name` property equal to `addPlayerToLeaderboard`'\n);\n```\n\nThe `add_player_to_leaderboard` instruction handler should take a context generic over an `AddPlayerToLeaderboard` accounts struct.\n\n```js\nconst testDir = await __createTestDir(7);\nawait __buildTestDir(7);\nconst { RockDestroyer } = await __helpers.importSansCache(\n  __path.join(testDir, 'target/types/rock_destroyer')\n);\nconst ixs = RockDestroyer.instructions;\nconst ix = ixs.find(ix => ix.name === 'addPlayerToLeaderboard');\nconst accounts = ix.accounts;\nassert.deepInclude(accounts, {\n  name: 'leaderboard',\n  isMut: true,\n  isSigner: false\n});\nassert.deepInclude(accounts, {\n  name: 'user',\n  isMut: true,\n  isSigner: true\n});\n```\n\nThere should be an `it` block named `\"initializes leaderboard\"`.\n\n```js\nconst callExpressions = babelisedCode\n  .getType('CallExpression')\n  .filter(c => {\n    return;\n    c.callee?.name === 'it';\n  })\n  .map(c => c.arguments?.[1]?.value);\nassert.include(callExpressions, 'initializes leaderboard');\n```\n\nThere should be an `it` block named `\"creates a new game\"`.\n\n```js\nconst callExpressions = babelisedCode\n  .getType('CallExpression')\n  .filter(c => {\n    return;\n    c.callee?.name === 'it';\n  })\n  .map(c => c.arguments?.[1]?.value);\nassert.include(callExpressions, 'creates a new game');\n```\n\nThere should be an `it` block named `\"adds a player to the leaderboard\"`.\n\n```js\nconst callExpressions = babelisedCode\n  .getType('CallExpression')\n  .filter(c => {\n    return;\n    c.callee?.name === 'it';\n  })\n  .map(c => c.arguments?.[1]?.value);\nassert.include(callExpressions, 'adds a player to the leaderboard');\n```\n\nThere should be an `it` block named `\"throws an error when the user has not payed\"`.\n\n```js\nconst callExpressions = babelisedCode\n  .getType('CallExpression')\n  .filter(c => {\n    return;\n    c.callee?.name === 'it';\n  })\n  .map(c => c.arguments?.[1]?.value);\nassert.include(callExpressions, 'throws an error when the user has not payed');\n```\n\n### --before-all--\n\n```js\nconst __fsp = await import('fs/promises');\nconst __path = await import('path');\nconst __projectDir = 'build-an-anchor-leaderboard/_answer/rock-destroyer';\nconst __testDir = 'build-an-anchor-leaderboard/__test/rock-destroyer';\nconst codeString = await __helpers.getFile(\n  './' + join(__projectDir, 'tests/index.ts')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\n\nasync function __createTestDir(num) {\n  const testDir = `${__testDir}-${num}`;\n  // Remove old test dir\n  logover.debug('Removing old test dir');\n  await __fsp.rm(testDir, { recursive: true, force: true });\n  // Create new test dir\n  logover.debug('Creating new test dir');\n  await __fsp.cp(__projectDir, testDir, { recursive: true });\n  return testDir;\n}\n\nasync function __buildTestDir(num) {\n  const { stdout, stderr } = await __helpers.getCommandOutput(\n    'anchor build',\n    `${__testDir}-${num}`\n  );\n  if (stderr) {\n    throw new Error(stderr);\n  }\n  return stdout;\n}\n\nglobal.__projectDir = __projectDir;\nglobal.__testDir = __testDir;\nglobal.__fsp = __fsp;\nglobal.__path = __path;\nglobal.__buildTestDir = __buildTestDir;\nglobal.__createTestDir = __createTestDir;\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.__projectDir;\ndelete global.__testDir;\ndelete global.__fsp;\ndelete global.__path;\ndelete global.babelisedCode;\n\ndelete global.__buildTestDir;\ndelete global.__createTestDir;\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/build-and-deploy-your-freeform-app.md",
    "content": "# Solana - Build and Deploy Your Freeform App\n\n## 1\n\n### --description--\n\nCongratulations on making it to the final project for this course! 🚀 🧨 🌟\n\nYou 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`.\n\nYou 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.\n\n- You can use <https://beta.solpg.io/> to develop most of your dApp in your browser.\n\n### Concepts Covered\n\n- Accounts\n  - Program (executable) accounts\n  - State (data) accounts\n  - Serialization and Deserialization\n- Rent\n- Tokens\n  - Fungible tokens\n  - Non-fungible tokens\n  - Creating and Minting\n  - Metadata\n- Transactions\n  - Instructions\n  - Signatures\n\n### Tools Covered\n\n- Solana CLI\n- `@solana/web3.js`\n- `@solana/spl-token`\n- Anchor CLI\n- `@coral-xyz/anchor`\n- `@metaplex-foundation/js`\n\n### Project Ideas\n\n- A Twitter clone\n- A blogging platform\n- An NFT marketplace\n- A decentralized exchange\n- A CLI game\n\n### --tests--\n\nOnce you are done, enter `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-anchor-by-building-tic-tac-toe-part-1.md",
    "content": "# Solana - Learn Anchor by Building Tic-Tac-Toe: Part 1\n\n## 1\n\n### --description--\n\nPreviously, 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.\n\nAnchor offers quick and convenient tools and modules for building and deploying programs.\n\nOpen a new terminal, and install the Anchor Version Manager (AVM) to get started.\n\n```bash\ncargo install --git https://github.com/coral-xyz/anchor avm --locked --force\n```\n\n### --tests--\n\nYou should have `avm` installed.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput('avm --version');\nassert.include(stdout, 'avm');\n```\n\n## 2\n\n### --description--\n\nAnchor Version Manager is a tool for using multiple versions of the Anchor CLI.\n\nUse `avm` to install the Anchor CLI.\n\n```bash\navm install 0.28.0\n```\n\n### --tests--\n\nYou should have version `0.28.0` of the Anchor CLI installed.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput('avm list');\nassert.include(stdout, 'installed, current)');\n```\n\n## 3\n\n### --description--\n\nInstruct `avm` to use the latest version of the Anchor CLI:\n\n```bash\navm use 0.28.0\n```\n\n### --tests--\n\nYou should be using version `0.28.0` of the Anchor CLI.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput('avm list');\nassert.include(stdout, 'installed, current)');\n```\n\n## 4\n\n### --description--\n\nVerify you are using the latest version of the Anchor CLI:\n\n```bash\nanchor --version\n```\n\n### --tests--\n\nYou should see version `0.28` printed to the console.\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, '0.28');\n```\n\n## 5\n\n### --description--\n\nYou will be building a Tic-Tac-Toe game on the Solana blockchain.\n\nWithin `learn-anchor-by-building-tic-tac-toe-part-1/`, create a new project named `tic-tac-toe`:\n\n```bash\nanchor init --no-git tic-tac-toe\n```\n\n**Note:** The `--no-git` flag is used to prevent the project from being initialized as a git repository.\n\n### --tests--\n\nYou should be in the `learn-anchor-by-building-tic-tac-toe-part-1` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/?$`);\nassert.match(cwd, dirRegex);\n```\n\nYou should run `anchor init --no-git tic-tac-toe` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(lastCommand.trim(), 'anchor init --no-git tic-tac-toe');\n```\n\nYou should have a `tic-tac-toe` directory.\n\n```js\nconst exists = __helpers.fileExists(`${project.dashedName}/tic-tac-toe`);\nassert.isTrue(exists);\n```\n\n## 6\n\n### --description--\n\nAnchor has created a `tic-tac-toe` directory with the following structure:\n\n```bash\ntic-tac-toe/\n├── app/\n├── migrations/\n│   └── deploy.ts\n├── programs/\n│   └── tic-tac-toe/\n│       ├── src/\n│       │   └── lib.rs\n│       ├── Cargo.toml\n│       └── Xargo.toml\n├── tests/\n│   └── tic-tac-toe.ts\n├── Anchor.toml\n├── Cargo.toml\n├── package.json\n├── tsconfig.json\n└── yarn.lock\n```\n\nIn your terminal, change into the `tic-tac-toe` directory.\n\n### --tests--\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 7\n\n### --description--\n\nThe `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.\n\nYou will be mostly working in `programs/tic_tac_toe/src/lib.rs`.\n\nGet the program id (public key) of the `tic-tac-toe` program:\n\n```bash\nanchor keys list\n```\n\n### --tests--\n\nThe public key of your `tic-tac-toe` program should be printed to the console.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/tic-tac-toe`\n);\nconst publicKey = stdout.match(/[^\\s]{44}/)[0];\n\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, publicKey);\n```\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 8\n\n### --description--\n\nThe Anchor CLI provides an `anchor test` command that:\n\n1. Builds all programs\n2. Starts a local Solana cluster\n3. Deploys the programs to the cluster\n4. Calls the `test` script in the `scripts` table in `Anchor.toml`\n5. Cleans up the local Solana cluster\n\nRun the `anchor test` command:\n\n```bash\nanchor test\n```\n\n### --tests--\n\nThe `anchor test` command should error ❌.\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, 'Error: failed to send transaction');\n```\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 9\n\n### --description--\n\nYou should see the following error:\n\n```bash\nError: failed to send transaction: Transaction simulation failed: This program may not be used for executing instructions\n```\n\nFor your program, you will need to manually start a local Solana validator. Do so, in a new terminal.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 10\n\n### --description--\n\nWith the local validator running, pass the `--skip-local-validator` flag to tell Anchor to not start its own local validator when running tests.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` command should error.\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, 'Error: AnchorError occurred.');\n```\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 11\n\n### --description--\n\nYou should see the following error:\n\n```bash\nError: AnchorError occurred. Error Code: DeclaredProgramIdMismatch. Error Number: 4100. Error Message: The declared program id does not match the actual program id.\n```\n\nDouble-check the program id in the `Anchor.toml` file.\n\nRun `anchor keys list` again to see if it matches the `programs.localnet.tic_tac_toe` value.\n\n### --tests--\n\nYou should run `anchor keys list` and see the program id printed to the console.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/tic-tac-toe`\n);\nconst publicKey = stdout.match(/[^\\s]{44}/)?.[0];\n\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, publicKey);\n```\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 12\n\n### --description--\n\nCopy the program id, and replace the default values with it in two locations:\n\n1. The string value within the `declare_id` macro in `programs/tic-tac-toe/src/lib.rs`\n2. The `tic_tac_toe` key within `Anchor.toml`\n\n### --tests--\n\nThe `lib.rs` file should contain the program id within the `declare_id!()` call.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/tic-tac-toe`\n);\nconst expectedProgramId = stdout.match(/[^\\s]{44}/)?.[0];\nconst actualProgramId = librs.match(/declare_id!\\(\"([^\\)]+)\"\\)/)?.[1];\nassert.equal(actualProgramId, expectedProgramId);\n```\n\nThe `Anchor.toml` file should contain the program id as the value for the `tic_tac_toe` key.\n\n```js\nconst toml = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/Anchor.toml`\n);\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/tic-tac-toe`\n);\nconst expectedProgramId = stdout.match(/[^\\s]{44}/)?.[0];\nconst actualProgramId = toml.match(/tic_tac_toe = \"([^\\\"]+)\"/)?.[1];\nassert.equal(actualProgramId, expectedProgramId);\n```\n\n## 13\n\n### --description--\n\nRun the test command again:\n\n```bash\nanchor test --skip-local-validator\n```\n\n### --tests--\n\nThe `anchor test --skip-local-validator` command should succeed.\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, '1 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 14\n\n### --description--\n\nShifting focus to the `lib.rs` file, you will see a few similarities to the native Solana program development workflow.\n\nInstead of an entrypoint function, the `program` attribute defines the module containing all instruction handlers defining all entries into a Solana program.\n\nThe `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.\n\nRename the `initialize` function to `setup_game`.\n\n### --tests--\n\nThe `setup_game` function should exist in the `lib.rs` file.\n\n```js\nassert.match(__librs, /fn setup_game/);\n```\n\nThe `initialize` function should not exist in the `lib.rs` file.\n\n```js\nassert.notMatch(__librs, /fn initialize/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 15\n\n### --description--\n\nThe `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.\n\nRename the `Initialize` struct to `SetupGame`.\n\n### --tests--\n\nThe `SetupGame` struct should exist in the `lib.rs` file.\n\n```js\nassert.match(__librs, /struct SetupGame/);\n```\n\nThe `setup_game` function should take a `Context<SetupGame>` as an argument.\n\n```js\nassert.match(__librs, /pub fn setup_game/);\nassert.match(__librs, /ctx: Context<SetupGame>/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 16\n\n### --description--\n\nSetting 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).\n\nYou 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:\n\n```rust\n#[derive(Accounts)]\npub struct AccountsInContext<'info> {\n    #[account(init)]\n    pub derived_account: Account<'info, AccountStruct>\n}\n```\n\nThe `#[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.\n\nWithin `SetupGame`, add a public field `game` with a type of `Account<'info, Game>`. Annotate the field such that it is initialized when required.\n\n### --tests--\n\n`SetupGame` should contain a field `game`.\n\n```js\nassert.match(__librs, /pub game:/);\n```\n\n`game` should be typed `Account<'info, Game>`.\n\n```js\nassert.match(__librs, /pub game: Account<'info\\s*,\\s*Game>/);\n```\n\n`game` should be annotated with `#[account(init)]`.\n\n```js\nassert.match(__librs, /#\\[\\s*account\\s*\\(\\s*init\\s*\\)\\s*\\]\\s*pub game:/);\n```\n\n`SetupGame` should be punctuated with a lifetime `'info`.\n\n```js\nassert.match(__librs, /pub struct SetupGame\\s*<'info>/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 17\n\n### --description--\n\nWhen an account is initialized, another account must pay for the rent and transaction fees.\n\nDeclare another account in `SetupGame` called `player_one`. Give `player_one` a type of `Signer<'info>`.\n\n**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.\n\n### --tests--\n\n`SetupGame` should contain a field `player_one`.\n\n```js\nassert.match(__librs, /pub player_one:/);\n```\n\n`player_one` should be typed `Signer<'info>`.\n\n```js\nassert.match(__librs, /pub player_one: Signer\\s*<'info>/);\n```\n\n`player_one` should be annotated with `#[account()]`.\n\n```js\nassert.match(__librs, /#\\[\\s*account\\s*\\(\\s*\\)\\s*\\]\\s*pub player_one:/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 18\n\n### --description--\n\nNow, mark the `player_one` account as the payer for the `game` account:\n\n```rust\n#[derive(Accounts)]\npub struct AccountsInContext<'info> {\n    #[account(\n        init,\n        payer = payer_account\n    )]\n    pub derived_account: Account<'info, AccountStruct>,\n    #[account()]\n    pub payer_account: Signer<'info>\n}\n```\n\n### --tests--\n\n`game` should be annotated with `payer = player_one`.\n\n```js\nconst librs = (\n  await __helpers.getFile(\n    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n  )\n)?.replaceAll(/[ \\t]{2,}/g, ' ');\nassert.match(\n  librs,\n  /#\\[\\s*account\\s*\\(\\s*init\\s*,\\s*payer\\s*=\\s*player_one\\s*\\)\\s*\\]\\s*pub game:/\n);\n```\n\n## 19\n\n### --description--\n\nIn order for any data in an account to be changed, the account must be mutable:\n\n```rust\n#[account(mut)]\npub mutable_account: Signer<'info>\n```\n\nMark the `player_one` account as mutable.\n\n### --tests--\n\n`player_one` should be annotated with `#[account(mut)]`.\n\n```js\nconst librs = (\n  await __helpers.getFile(\n    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n  )\n)?.replaceAll(/[ \\t]{2,}/g, ' ');\nassert.match(librs, /#\\[\\s*account\\s*\\(\\s*mut\\s*\\)\\s*\\]\\s*pub player_one:/);\n```\n\n## 20\n\n### --description--\n\nThe `#[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:\n\n```rust\n#[account(init, space = <AMOUNT_IN_BYTES>)]\npub derived_account: Account<'info, AccountStruct>\n```\n\nAdd a `space` parameter to the `game` account with a value of `10`. This means the account will be initialised with 10 bytes of space.\n\n### --tests--\n\n`game` should be annotated with `space = 10`.\n\n```js\nconst librs = (\n  await __helpers.getFile(\n    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n  )\n)?.replaceAll(/[ \\t]{2,}/g, ' ');\nassert.match(\n  librs,\n  /#\\[\\s*account\\s*\\([^\\)]*?space\\s*=\\s*10[^\\)]*?\\)]\\s*pub game:/\n);\n```\n\n## 21\n\n### --description--\n\nCreating 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.\n\nDefine a public struct `Game`, and annotate it with `#[account]` to indicate it is a Solana account.\n\n### --tests--\n\n`pub struct Game` should exist in the `lib.rs` file.\n\n```js\nassert.match(__librs, /pub struct Game/);\n```\n\n`Game` should be annotated with `#[account]`.\n\n```js\nassert.match(__librs, /#\\[\\s*account\\s*\\]\\s*pub struct Game/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 22\n\n### --description--\n\nA game of tic-tac-toe consists of two players.\n\nKeep track of the players, by adding a `players` field in `Game` with a type of `[Pubkey; 2]`.\n\n### --tests--\n\n`Game` should contain a field `players`.\n\n```js\nconst game = __librs.match(/pub struct Game\\s*{([^}]*)}/s)?.[1];\nassert.match(game, /players:/);\n```\n\n`players` should be typed `[Pubkey; 2]`.\n\n```js\nassert.match(__librs, /players: \\[\\s*Pubkey\\s*;\\s*2\\s*\\]/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 23\n\n### --description--\n\nKeep track of the current turn number, by adding a `turn` field in `Game` with a type of `u8`.\n\n### --tests--\n\n`Game` should contain a field `turn`.\n\n```js\nconst game = __librs.match(/pub struct Game\\s*{([^}]*)}/s)?.[1];\nassert.match(game, /turn:/);\n```\n\n`turn` should be typed `u8`.\n\n```js\nassert.match(__librs, /turn: u8/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 24\n\n### --description--\n\nKeep 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]`.\n\n### --tests--\n\n`Game` should contain a field `board`.\n\n```js\nconst game = __librs.match(/pub struct Game\\s*{([^}]*)}/s)?.[1];\nassert.match(game, /board:/);\n```\n\n`board` should be typed `[[Option<Sign>; 3]; 3]`.\n\n```js\nassert.match(\n  __librs,\n  /board: \\[\\[\\s*Option\\s*<\\s*Sign\\s*>\\s*;\\s*3\\s*\\]\\s*;\\s*3\\s*\\]/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 25\n\n### --description--\n\nKeep track of the current game condition, by adding a `state` field in `Game` with a type of `GameState`.\n\n### --tests--\n\n`Game` should contain a field `state`.\n\n```js\nconst game = __librs.match(/pub struct Game\\s*{([^}]*)}/s)?.[1];\nassert.match(game, /state:/);\n```\n\n`state` should be typed `GameState`.\n\n```js\nassert.match(__librs, /state: GameState/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 26\n\n### --description--\n\nDefine a public enum `Sign` with variants `X` and `O`.\n\n### --tests--\n\n`pub enum Sign` should exist in the `lib.rs` file.\n\n```js\nassert.match(__librs, /pub enum Sign/);\n```\n\n`Sign` should have a variant `X`.\n\n```js\nconst sign = __librs.match(/pub enum Sign\\s*{([^}]*)}/s)?.[1];\nassert.match(sign, /X/);\n```\n\n`Sign` should have a variant `O`.\n\n```js\nconst sign = __librs.match(/pub enum Sign\\s*{([^}]*)}/s)?.[1];\nassert.match(sign, /O/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 27\n\n### --description--\n\nDefine a public enum `GameState` with variants `Active`, `Tie`, and `Won`. The `Won` variant should contain a named field `winner` with a type of `Pubkey`.\n\n### --tests--\n\n`pub enum GameState` should exist in the `lib.rs` file.\n\n```js\nassert.match(__librs, /pub enum GameState/);\n```\n\n`GameState` should have a variant `Active`.\n\n```js\nconst gameState = __librs.match(/pub enum GameState\\s*{([^}]*)}/s)?.[1];\nassert.match(gameState, /Active/);\n```\n\n`GameState` should have a variant `Tie`.\n\n```js\nconst gameState = __librs.match(/pub enum GameState\\s*{([^}]*)}/s)?.[1];\nassert.match(gameState, /Tie/);\n```\n\n`GameState` should have a variant `Won { winner: Pubkey }`.\n\n```js\nconst gameState = __librs.match(\n  /pub enum GameState ({[\\s\\S]*?({[\\s\\S]*?}[\\s\\S]*?}|}))/s\n)?.[1];\nassert.match(gameState, /Won\\s*{\\s*winner:\\s*Pubkey\\s*,?\\s*}/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 28\n\n### --description--\n\nIn order for Anchor to serialize and deserialize the `Game` account data, `GameState` and `Sign` must implement the `AnchorSerialize` and `AnchorDeserialize` traits.\n\nDerive the `AnchorSerialize` and `AnchorDeserialize` traits for `GameState` and `Sign`.\n\n### --tests--\n\n`GameState` should be annotated with `#[derive(AnchorSerialize, AnchorDeserialize)]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*derive\\s*\\(\\s*AnchorSerialize\\s*,\\s*AnchorDeserialize\\s*\\)\\s*\\]\\s*pub enum GameState/\n);\n```\n\n`Sign` should be annotated with `#[derive(AnchorSerialize, AnchorDeserialize)]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*derive\\s*\\(\\s*AnchorSerialize\\s*,\\s*AnchorDeserialize\\s*\\)\\s*\\]\\s*pub enum Sign/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 29\n\n### --description--\n\nOn top of `AnchorSerialize` and `AnchorDeserialize`, both `GameState` and `Sign` must also implement the `Clone` trait.\n\nDerive the `Clone` trait for `GameState` and `Sign`.\n\n### --tests--\n\n`GameState` should be annotated with `#[derive(Clone)]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*derive\\s*\\([^\\]]*?Clone[^\\]]*?\\)\\s*\\]\\s*pub enum GameState/\n);\n```\n\n`Sign` should be annotated with `#[derive(Clone)]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*derive\\s*\\([^\\]]*?Clone[^\\]]*?\\)\\s*\\]\\s*pub enum Sign/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 30\n\n### --description--\n\nFinally, because `Sign` is within a slice, it must also implement the `Copy` trait.\n\nDerive the `Copy` trait for `Sign`.\n\n### --tests--\n\n`Sign` should be annotated with `#[derive(Copy)]`.\n\n```js\nconst librs = (\n  await __helpers.getFile(\n    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n  )\n)?.replaceAll(/[ \\t]{2,}/g, ' ');\nassert.match(\n  librs,\n  /#\\[\\s*derive\\s*\\([^\\]]*?Copy[^\\]]*?\\)\\s*\\]\\s*pub enum Sign/\n);\n```\n\n## 31\n\n### --description--\n\nIn 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.\n\nAdd a public `system_program` field to the `SetupGame` struct, and type it as `Program<'info, System>`.\n\n### --tests--\n\n`SetupGame` should contain a field `system_program`.\n\n```js\nconst setupGame = __librs.match(\n  /pub struct SetupGame\\s*<'info\\s*>\\s*{([^}]*?)}/s\n)?.[1];\nassert.match(setupGame, /system_program:/);\n```\n\n`system_program` should be typed `Program<'info, System>`.\n\n```js\nconst setupGame = __librs.match(\n  /pub struct SetupGame\\s*<'info\\s*>\\s*{([^}]*)}/s\n)?.[1];\nassert.match(setupGame, /system_program: Program\\s*<\\s*'info\\s*,\\s*System\\s*>/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 32\n\n### --description--\n\nNow 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>.\n\nFor `Game`:\n\n| Field   | Unit Size | Quantity | Total Size |\n| ------- | --------- | -------- | ---------- |\n| players | 32        | 2        | 64         |\n| turn    | 1         | 1        | 1          |\n| board   | 1 + 1     | 3 \\* 3   | 18         |\n| state   | 1 + 32    | 1        | 33         |\n\nAnchor provides a table of sizes for each Rust type: `https://www.anchor-lang.com/docs/space`\n\nReplace the `10` bytes allocated for the `Game` account with the correct size.\n\n### --tests--\n\nThe `game` field in `SetupGame` should be annotated with `#[account(space = 8 + (32*2) + (1) + ((1+1)*(3*3)) + (1+32))]`.\n\n```js\nconst librs = (\n  await __helpers.getFile(\n    `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n  )\n)?.replaceAll(/[ \\t]{2,}/g, ' ');\n\nconst setupGame = librs.match(\n  /pub struct SetupGame\\s*<'info\\s*>\\s*{([^}]*)}/s\n)?.[1];\nconst mat = setupGame?.match(\n  /#\\[\\s*account\\s*\\([^\\]]*?space\\s*=\\s*([^\\]]+?)\\s*\\)\\s*\\]\\s*pub game:/\n)?.[1];\nassert.exists(\n  mat,\n  `game field should be annotated with #[account(space = <SIZE>)]`\n);\nconst math = eval(mat);\nassert.equal(\n  math,\n  8 + 32 * 2 + 1 + (1 + 1) * (3 * 3) + (1 + 32),\n  `space should sum up to correct size`\n);\n```\n\n## 33\n\n### --description--\n\n**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.\n\nJust a few things to fix. First, derive `PartialEq` for `GameState` and `Sign`.\n\n### --tests--\n\n`GameState` should be annotated with `#[derive(PartialEq)]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*derive\\s*\\([^\\]]*?PartialEq[^\\]]*?\\)\\s*\\]\\s*pub enum GameState/\n);\n```\n\n`Sign` should be annotated with `#[derive(PartialEq)]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*derive\\s*\\([^\\]]*?PartialEq[^\\]]*?\\)\\s*\\]\\s*pub enum Sign/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --force--\n\n#### --\"learn-anchor-by-building-tic-tac-toe-part-1/tic-tac-toe/programs/tic-tac-toe/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"BUfb6FXLkiSpMnJnMR4Q5uGZYZkaNGytjhLwiiJQsE8F\");\n\n#[program]\npub mod tic_tac_toe {\n    use super::*;\n\n    pub fn setup_game(ctx: Context<SetupGame>) -> Result<()> {\n      Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SetupGame<'info> {\n    #[account(init, payer = player_one, space = 8 + Game::MAXIMUM_SIZE)]\n    pub game: Account<'info, Game>,\n    #[account(mut)]\n    pub player_one: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct Game {\n    players: [Pubkey; 2],          // (32 * 2)\n    turn: u8,                      // 1\n    board: [[Option<Sign>; 3]; 3], // 9 * (1 + 1) = 18\n    state: GameState,              // 32 + 1\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub enum GameState {\n    Active,\n    Tie,\n    Won { winner: Pubkey },\n}\n\n#[derive(\n    AnchorSerialize,\n    AnchorDeserialize,\n    Copy,\n    Clone,\n)]\npub enum Sign {\n    X,\n    O,\n}\n\n/// A tile on the game board.\npub struct Tile {\n    row: u8,\n    column: u8,\n}\n\nimpl Game {\n    pub const MAXIMUM_SIZE: usize = (32 * 2) + 1 + ((1 + 1) * 9) + (1 + 32);\n\n    pub fn start(&mut self, players: [Pubkey; 2]) -> Result<()> {\n        // TODO: Ensure the game is not already started.\n\n        self.players = players;\n        self.turn = 1;\n        Ok(())\n    }\n\n    pub fn is_active(&self) -> bool {\n        self.state == GameState::Active\n    }\n\n    fn current_player_index(&self) -> usize {\n        ((self.turn - 1) % 2) as usize\n    }\n\n    pub fn current_player(&self) -> Pubkey {\n        self.players[self.current_player_index()]\n    }\n\n    pub fn play(&mut self, tile: &Tile) -> Result<()> {\n        // TODO: Ensure the game is active.\n\n        match tile {\n            tile @ Tile {\n                row: 0..=2,\n                column: 0..=2,\n            } => match self.board[tile.row as usize][tile.column as usize] {\n                Some(_) => {\n                  // TODO: Return an error that the tile is already set.\n                  return Err();\n                },\n                None => {\n                    self.board[tile.row as usize][tile.column as usize] =\n                        Some(Sign::from_usize(self.current_player_index()).unwrap());\n                }\n            },\n            _ => {\n              // TODO: Return an error that the tile is out of bounds.\n              return Err();\n            },\n        }\n\n        self.update_state();\n\n        if GameState::Active == self.state {\n            self.turn += 1;\n        }\n\n        Ok(())\n    }\n\n    fn is_winning_trio(&self, trio: [(usize, usize); 3]) -> bool {\n        let [first, second, third] = trio;\n        self.board[first.0][first.1].is_some()\n            && self.board[first.0][first.1] == self.board[second.0][second.1]\n            && self.board[first.0][first.1] == self.board[third.0][third.1]\n    }\n\n    fn update_state(&mut self) {\n        for i in 0..=2 {\n            // three of the same in one row\n            if self.is_winning_trio([(i, 0), (i, 1), (i, 2)]) {\n                self.state = GameState::Won {\n                    winner: self.current_player(),\n                };\n                return;\n            }\n            // three of the same in one column\n            if self.is_winning_trio([(0, i), (1, i), (2, i)]) {\n                self.state = GameState::Won {\n                    winner: self.current_player(),\n                };\n                return;\n            }\n        }\n\n        // three of the same in one diagonal\n        if self.is_winning_trio([(0, 0), (1, 1), (2, 2)])\n            || self.is_winning_trio([(0, 2), (1, 1), (2, 0)])\n        {\n            self.state = GameState::Won {\n                winner: self.current_player(),\n            };\n            return;\n        }\n\n        // reaching this code means the game has not been won,\n        // so if there are unfilled tiles left, it's still active\n        for row in 0..=2 {\n            for column in 0..=2 {\n                if self.board[row][column].is_none() {\n                    return;\n                }\n            }\n        }\n\n        // game has not been won\n        // game has no more free tiles\n        // -> game ends in a tie\n        self.state = GameState::Tie;\n    }\n}\n\n```\n\n## 34\n\n### --description--\n\nSecond, in order to be able to match the correct `Sign` variant with the current player, `num_traits::FromPrimitive` should be derived for `Sign`.\n\nTo derive `FromPrimitive`, you need to add `num-traits` and `num-derive` to the dependencies in `programs/tic-tac-toe/Cargo.toml`.\n\n**Note:** `num-derive` enables the use of `#[derive(FromPrimitive)]` on a struct or enum.\n\n### --tests--\n\nYou should add `num-traits` to the dependencies in `programs/tic-tac-toe/Cargo.toml`.\n\n```js\nassert.match(__cargo_toml, /num-traits/);\n```\n\nYou should add `num-derive` to the dependencies in `programs/tic-tac-toe/Cargo.toml`.\n\n```js\nassert.match(__cargo_toml, /num-derive/);\n```\n\n### --before-all--\n\n```js\nconst __cargo_toml = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/Cargo.toml`\n);\nglobal.__cargo_toml = __cargo_toml?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__cargo_toml;\n```\n\n## 35\n\n### --description--\n\nNow, derive `num_derive::FromPrimitive` for `Sign`.\n\n### --tests--\n\n`Sign` should derive `num_derive::FromPrimitive`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*derive\\s*\\([^\\]]*?num_derive\\s*::\\s*FromPrimitive[^\\]]*?\\)\\s*\\]\\s*pub enum Sign/\n);\n```\n\n`num_traits::FromPrimitive` should be brought into the module scope.\n\n```js\nassert.match(__librs, /use num_traits::FromPrimitive;/);\n```\n\n`num_derive` should be brought into the module scope.\n\n```js\nassert.match(__librs, /use num_derive/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 36\n\n### --description--\n\nThe third fix is to add errors to the `play` method.\n\nDefine a public enum `TicTacToeError` with the variants `TileAlreadySet` and `TileOutOfBounds`.\n\n### --tests--\n\nA `TicTacToeError` enum should be defined.\n\n```js\nassert.match(__librs, /pub enum TicTacToeError/);\n```\n\n`TicTacToeError` should have the variant `TileAlreadySet`.\n\n```js\nconst ticTacToeError = __librs.match(\n  /pub enum TicTacToeError\\s*{([^}]*)}/s\n)?.[1];\nassert.match(ticTacToeError, /TileAlreadySet/);\n```\n\n`TicTacToeError` should have the variant `TileOutOfBounds`.\n\n```js\nconst ticTacToeError = __librs.match(\n  /pub enum TicTacToeError\\s*{([^}]*)}/s\n)?.[1];\nassert.match(ticTacToeError, /TileOutOfBounds/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 37\n\n### --description--\n\nIn the appropriate location, return the `TileAlreadySet` error.\n\n### --tests--\n\nThe first `return Err()` should return the `TileAlreadySet` error.\n\n```js\nconst librs = await __helpers\n  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)\n  ?.replaceAll(/[ \\t]{2,}/g, ' ');\n\nconst firstReturnErr = librs.match(/return Err\\s*\\(([^\\)]*?)\\),/)?.[1];\nassert.match(firstReturnErr, /TicTacToeError\\s*::\\s*TileAlreadySet/);\n```\n\n## 38\n\n### --description--\n\nAnchor 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.\n\n```rust\n#[error_code]\npub enum MyCustomError { ... }\n```\n\n### --tests--\n\nYour `TicTacToeError` enum should have the `error_code` attribute macro.\n\n```js\nconst librs = await __helpers\n  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)\n  ?.replaceAll(/[ \\t]{2,}/g, ' ');\n\nassert.match(librs, /#\\[\\s*error_code\\s*\\]\\s*pub enum TicTacToeError/);\n```\n\n## 39\n\n### --description--\n\nConvert the `TileAlreadySet` error into an `anchor_lang::error::Error` by calling the derived `into` method on it.\n\n### --tests--\n\nYou should have `return Err(TicTacToeError::TileAlreadySet.into());`.\n\n```js\nconst librs = await __helpers\n  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)\n  ?.replaceAll(/[ \\t]{2,}/g, ' ');\n\nconst firstReturnErr = librs.match(/return Err\\s*\\(([^\\)]*?)\\),/)?.[1];\n\nassert.match(\n  firstReturnErr,\n  /TicTacToeError\\s*::\\s*TileAlreadySet\\s*.\\s*into\\(\\)/\n);\n```\n\n## 40\n\n### --description--\n\nIn the appropriate location, return the `TileOutOfBounds` error.\n\n### --tests--\n\nThe second `return Err()` should return the `TileOutOfBounds` error.\n\n```js\nconst secondReturnErr = [\n  ...__librs.matchAll(/return Err\\s*\\(([^\\)]*?)\\),/g)\n]?.[1]?.[1];\nassert.match(secondReturnErr, /TicTacToeError\\s*::\\s*TileOutOfBounds/);\n```\n\nThe `TileOutOfBounds` error should be converted into an `anchor_lang::error::Error`.\n\n```js\nconst secondReturnErr = [\n  ...__librs.matchAll(/return Err\\s*\\(([^\\)]*?)\\),/g)\n]?.[1]?.[1];\nassert.match(\n  secondReturnErr,\n  /TicTacToeError\\s*::\\s*TileOutOfBounds\\s*.\\s*into\\(\\)/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 41\n\n### --description--\n\nAnother way to return an error is to use the `require!` macro:\n\n```rust\nrequire!(condition, MyCustomError::MyCustomErrorVariant);\n```\n\nIn 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`.\n\n### --tests--\n\nThe `require!` macro should be used to return early if the game state is not `Active`.\n\n```js\n// `require!(` comes after `-> Result<()> {`, and before `match tile {`\nconst playFunction = __librs.match(\n  /pub fn play\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^}]*)}/s\n)?.[2];\nassert.match(playFunction, /require!\\(/);\n```\n\nThe `require!` condition should use the provided `is_active` method.\n\n```js\nconst playFunction = __librs.match(\n  /pub fn play\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^}]*)}/s\n)?.[2];\nconst requireCondition = playFunction?.match(\n  /require!\\s*\\(([^\\)]*?)\\)\\s*;\\s*match\\s*tile\\s*{/\n)?.[1];\nassert.match(requireCondition, /self\\s*.\\s*is_active\\s*\\(\\s*\\)/);\n```\n\nThe `require!` macro should return the `GameAlreadyOver` error.\n\n```js\nconst playFunction = __librs.match(\n  /pub fn play\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^}]*)}/s\n)?.[2];\nconst requireCondition = playFunction?.match(\n  /require!\\s*\\(([^\\)]*?)\\)\\s*;\\s*match\\s*tile\\s*{/\n)?.[1];\nassert.match(requireCondition, /TicTacToeError\\s*::\\s*GameAlreadyOver/);\n```\n\nThe `TicTacToeError` enum should have the variant `GameAlreadyOver`.\n\n```js\nconst ticTacToeError = __librs.match(\n  /pub enum TicTacToeError\\s*{([^}]*)}/s\n)?.[1];\nassert.match(ticTacToeError, /GameAlreadyOver/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 42\n\n### --description--\n\nThe final _TODO_ in the game logic is to return early if the `start` method is called when the `turn` is greater than `0`.\n\nWithin 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`.\n\n### --tests--\n\nThe `require_eq!` macro should be used to return early if the `turn` is not equal to `0`.\n\n```js\nconst startFn = __librs.match(\n  /pub fn start\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^}]*)}/s\n)?.[2];\nconst requireEq = startFn?.match(/require_eq!\\s*\\(([^\\)]*?)\\)\\s*;/)?.[1];\nassert.exists(requireEq, '`require_eq!` should be called');\nassert.match(\n  requireEq,\n  /self\\s*.\\s*turn\\s*,\\s*0/,\n  '`require_eq!` should be called with `self.turn` and `0`'\n);\n```\n\nThe `require_eq!` macro should return the `GameAlreadyStarted` error.\n\n```js\nconst startFn = __librs.match(\n  /pub fn start\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^}]*)}/s\n)?.[2];\nconst requireEq = startFn?.match(/require_eq!\\s*\\(([^\\)]*?)\\)\\s*;/)?.[1];\nassert.match(\n  requireEq,\n  /TicTacToeError\\s*::\\s*GameAlreadyStarted/,\n  'the third argument to `require_eq!` should be `TicTacToeError::GameAlreadyStarted`'\n);\n```\n\nThe `TicTacToeError` enum should have the variant `GameAlreadyStarted`.\n\n```js\nconst ticTacToeError = __librs.match(\n  /pub enum TicTacToeError\\s*{([^}]*)}/s\n)?.[1];\nassert.match(ticTacToeError, /GameAlreadyStarted/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 43\n\n### --description--\n\nFocussing 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:\n\n```rust\n#[derive(Accounts)]\npub struct AccountsStruct<'info> {\n    pub account_1: AccountInfo<'info>,\n    pub account_2: AccountInfo<'info>,\n    pub account_3: ProgramAccount<'info, TicTacToe>,\n}\npub fn instruction_handler(ctx: Context<AccountsStruct>) -> Result<()> {\n    let account_1 = &ctx.accounts.account_1;\n    let account_2 = &ctx.accounts.account_2;\n    let account_3 = &ctx.accounts.account_3;\n}\n```\n\nWithin the `setup_game` instruction handler, declare a variable `player_one` and assign the corresponding account reference to it.\n\n### --tests--\n\nThe `player_one` variable should be declared.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(setupGame, /let\\s*player_one/);\n```\n\nThe `player_one` variable should be assigned `&ctx.accounts.player_one`.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nconst playerOne = setupGame?.match(/let\\s*player_one\\s*=\\s*([^\\;]*?)\\;/)?.[1];\nassert.match(playerOne, /&\\s*ctx\\.accounts\\.player_one/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 44\n\n### --description--\n\nSetting up the game requires three steps:\n\n1. The public address of the first player\n2. The public address of the second player\n3. To call the `start` method on the `game` account\n\nWithing `setup_game` declare a variable `player_one_pubkey` and assign the return of the `key` method provided by the `player_one` account to it.\n\n### --tests--\n\nThe `player_one_pubkey` variable should be declared.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(setupGame, /let\\s*player_one_pubkey/);\n```\n\nThe `player_one_pubkey` variable should be assigned `player_one.key()`.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nconst playerOnePubkey = setupGame?.match(\n  /let\\s*player_one_pubkey\\s*=\\s*([^\\;]*?)\\;/\n)?.[1];\nassert.match(playerOnePubkey, /player_one\\s*.\\s*key\\s*\\(\\s*\\)/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 45\n\n### --description--\n\nInstruction handlers can be called with arguments, and the values accessed through parameters:\n\n```rust\npub fn instruction_handler(ctx: Context<AccountsStruct>, arg1: u8, arg2: u8) -> Result<()> {}\n```\n\nIn 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`.\n\n### --tests--\n\nThe `setup_game` instruction handler should have a `player_two_pubkey` parameter.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(setupGame, /player_two_pubkey\\s*:/);\n```\n\nThe `player_two_pubkey` parameter should be of type `Pubkey`.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(setupGame, /player_two_pubkey\\s*:\\s*Pubkey/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 46\n\n### --description--\n\nWithin the `setup_game` instruction handler, declare a variable `game`, and assign a mutable reference to the `game` account to it.\n\n### --tests--\n\nThe `game` variable should be declared.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(setupGame, /let\\s*game/);\n```\n\nThe `game` variable should be assigned `&mut ctx.accounts.game`.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nconst game = setupGame?.match(/let\\s+game\\s*=\\s*([^\\;]*?)\\;/)?.[1];\nassert.match(game, /&\\s*mut\\s+ctx\\.accounts\\.game/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 47\n\n### --description--\n\nWithin 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.\n\n### --tests--\n\n`setup_game` should have `game.start([player_one_pubkey, player_two_pubkey])`.\n\n```js\nconst librs = await __helpers\n  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)\n  ?.replaceAll(/[ \\t]{2,}/g, ' ');\n\nconst setupGame = librs.match(\n  /pub fn setup_game\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)}/s\n)?.[2];\nassert.match(\n  setupGame,\n  /game\\s*.\\s*start\\s*\\(\\s*\\[\\s*player_one_pubkey\\s*,\\s*player_two_pubkey\\s*\\]\\s*\\)/\n);\n```\n\n## 48\n\n### --description--\n\nCurrently, 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.\n\nInstead, you can make use of a PDA to generate a deterministic address for the `game` account:\n\n```rust\n#[derive(Accounts)]\npub struct InitialisePDAAccount<'info> {\n    #[account(\n      init,\n      payer = payer,\n      seeds = [b\"<SEED>\", payer.key().as_ref()],\n      bump\n      )\n    ]\n    pub pda_account: Account<'info, PDAAccount>,\n}\n```\n\nWithin `lib.rs`, initialize the `game` account using two seeds: the first the byte string `\"game\"`, and the second the payer's public key.\n\n### --tests--\n\n`SetupGame` should annotate the `game` field with `#[account(seeds = [b\"game\", player_one.key().as_ref()])]`.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nconst setupGameStruct = librs.match(/struct\\s*SetupGame\\s*{([^\\}]*)}/s)?.[1];\nconst accountAttribute = setupGameStruct?.match(\n  /#\\[account\\s*\\(([^\\]]*?)\\]\\s*pub\\s*game/\n)?.[1];\nassert.match(\n  accountAttribute,\n  /seeds\\s*=\\s*\\[\\s*b\"game\"\\s*,\\s*player_one\\s*.\\s*key\\s*\\(\\s*\\)\\s*.\\s*as_ref\\s*\\(\\s*\\)\\s*\\]/\n);\n```\n\n## 49\n\n### --description--\n\nThe 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>.\n\nTo 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>.\n\nExplicitly tell Anchor to generate the bump seed, by annotating the `game` field with `#[account(bump)]`.\n\n### --tests--\n\n`SetupGame` should annotate the `game` field with `#[account(bump)]`.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nconst setupGameStruct = librs.match(/struct\\s*SetupGame\\s*{([^\\}]*)}/s)?.[1];\nconst accountAttribute = setupGameStruct?.match(\n  /#\\[account\\s*\\(([^\\]]*?)\\]\\s*pub\\s*game/\n)?.[1];\nassert.match(accountAttribute, /bump/);\n```\n\n## 50\n\n### --description--\n\nRun the tests to see if the `setup_game` instruction handler is working correctly.\n\n### --tests--\n\nThe test for `setup_game` should pass for `anchor test --skip-local-validator`.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '1 passing');\n```\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --force--\n\n#### --\"learn-anchor-by-building-tic-tac-toe-part-1/tic-tac-toe/tests/tic-tac-toe.ts\"--\n\n```typescript\nimport {\n  AnchorError,\n  Program,\n  AnchorProvider,\n  setProvider,\n  workspace\n} from '@coral-xyz/anchor';\nimport { TicTacToe } from '../target/types/tic_tac_toe';\nimport { expect } from 'chai';\nimport { Keypair, PublicKey } from '@solana/web3.js';\n\ndescribe('tic-tac-toe', () => {\n  // Configure the client to use the local cluster.\n  setProvider(AnchorProvider.env());\n\n  const program = workspace.TicTacToe as Program<TicTacToe>;\n  const programProvider = program.provider as AnchorProvider;\n\n  it('initializes a game', async () => {\n    const playerOne = Keypair.generate();\n    const playerTwo = Keypair.generate();\n\n    const [gamePublicKey, _] = PublicKey.findProgramAddressSync(\n      [Buffer.from('game'), playerOne.publicKey.toBuffer()],\n      program.programId\n    );\n\n    // Airdrop to playerOne\n    const sg = await programProvider.connection.requestAirdrop(\n      playerOne.publicKey,\n      1_000_000_000\n    );\n    await programProvider.connection.confirmTransaction(sg);\n\n    await program.methods\n      .setupGame(playerTwo.publicKey)\n      .accounts({\n        game: gamePublicKey,\n        playerOne: playerOne.publicKey\n      })\n      .signers([playerOne])\n      .rpc();\n\n    const gameData = await program.account.game.fetch(gamePublicKey);\n\n    expect(gameData.turn).to.equal(1);\n    expect(gameData.players).to.eql([playerOne.publicKey, playerTwo.publicKey]);\n\n    expect(gameData.state).to.eql({ active: {} });\n    expect(gameData.board).to.eql([\n      [null, null, null],\n      [null, null, null],\n      [null, null, null]\n    ]);\n  });\n});\n```\n\n## 51\n\n### --description--\n\nUsing 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:\n\n```rust\npub fn initialize(ctx: Context<Init>, arg_1: String, arg_2: u8) -> Result<()> {}\n\n#[derive(Accounts)]\n#[instruction(arg_1: String, arg_2: u8)]\npub struct Init<'info> {\n    #[account(\n      init,\n      payer = payer,\n      seeds = [arg_1.as_bytes(), payer.key().as_ref(), arg_2],\n      bump\n    )]\n    pub pda: Account<'info, Game>,\n    pub payer: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n```\n\nThe `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.\n\nWithin `lib.rs`, add a third parameter `game_id` of type `String` to the `setup_game` instruction handler.\n\n### --tests--\n\nThe `setup_game` instruction handler should have a `game_id` parameter.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/s\n)?.[1];\nassert.match(setupGame, /game_id\\s*:/);\n```\n\nThe `game_id` parameter should be of type `String`.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/s\n)?.[1];\nassert.match(setupGame, /game_id\\s*:\\s*String/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 52\n\n### --description--\n\nWithin `lib.rs`, annotate the `SetupGame` struct with the `instruction` attribute, passing in the required parameters to access the `game_id` parameter.\n\n### --tests--\n\nThe `SetupGame` struct should be annotated with `#[instruction(player_two_pubkey: Pubkey, game_id: String)]`.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nassert.match(\n  librs,\n  /(?<=#\\[\\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/\n);\n```\n\n## 53\n\n### --description--\n\nWithin `lib.rs`, add the `game_id` parameter as a seed to the `seeds` value of the `game` account.\n\n### --tests--\n\nThe `game` field should be annotated with `#[account(seeds = [b\"game\", player_one.key().as_ref(), game_id.as_bytes()])]`.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nconst setupGameStruct = librs.match(/struct\\s*SetupGame\\s*{([^\\}]*)}/s)?.[1];\nconst accountAttribute = setupGameStruct?.match(\n  /#\\[account\\s*\\(([^\\]]*?)\\]\\s*pub\\s*game/\n)?.[1];\nassert.match(\n  accountAttribute,\n  /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*\\]/\n);\n```\n\n## 54\n\n### --description--\n\nTo prevent Rust from complaining about the `game_id` parameter not being used, prefix it with an underscore.\n\n### --tests--\n\nThe `game_id` parameter should be prefixed with an underscore in the `setup_game` instruction handler.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(setupGame, /_game_id\\s*:/);\n```\n\nThe `game_id` parameter should be prefixed with an underscore in the `instruction` attribute.\n\n```js\nconst setupGame = __librs.match(\n  /pub fn setup_game\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nconst instructionAttribute = setupGame?.match(\n  /#\\[instruction\\s*\\(([^\\]]*?)\\)\\s*\\]/\n)?.[1];\nassert.match(instructionAttribute, /_game_id\\s*:\\s*String/);\n```\n\nThe `game_id` parameter should be prefixed with an underscore in the `game` account's `seeds` value.\n\n```js\nconst setupGameStruct = __librs.match(/struct\\s*SetupGame\\s*{([^\\}]*)}/s)?.[1];\nconst accountAttribute = setupGameStruct?.match(\n  /#\\[account\\s*\\(([^\\]]*?)\\]\\s*pub\\s*game/\n)?.[1];\nassert.match(\n  accountAttribute,\n  /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*\\]/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 55\n\n### --description--\n\nRun the tests to see if the `setup_game` instruction handler is working correctly.\n\n### --tests--\n\nThe test for `setup_game` should pass when `anchor test --skip-local-validator`.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '1 passing');\n```\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --force--\n\n#### --\"learn-anchor-by-building-tic-tac-toe-part-1/tic-tac-toe/tests/tic-tac-toe.ts\"--\n\n```typescript\nimport {\n  AnchorError,\n  Program,\n  AnchorProvider,\n  setProvider,\n  workspace\n} from '@coral-xyz/anchor';\nimport { TicTacToe } from '../target/types/tic_tac_toe';\nimport { expect } from 'chai';\nimport { Keypair, PublicKey } from '@solana/web3.js';\n\ndescribe('tic-tac-toe', () => {\n  // Configure the client to use the local cluster.\n  setProvider(AnchorProvider.env());\n\n  const program = workspace.TicTacToe as Program<TicTacToe>;\n  const programProvider = program.provider as AnchorProvider;\n\n  it('initializes a game', async () => {\n    const playerOne = Keypair.generate();\n    const playerTwo = Keypair.generate();\n\n    const gameId = 'game-1';\n\n    const [gamePublicKey, _] = PublicKey.findProgramAddressSync(\n      [\n        Buffer.from('game'),\n        playerOne.publicKey.toBuffer(),\n        Buffer.from(gameId)\n      ],\n      program.programId\n    );\n\n    // Airdrop to playerOne\n    const sg = await programProvider.connection.requestAirdrop(\n      playerOne.publicKey,\n      1_000_000_000\n    );\n    await programProvider.connection.confirmTransaction(sg);\n\n    await program.methods\n      .setupGame(playerTwo.publicKey, gameId)\n      .accounts({\n        game: gamePublicKey,\n        playerOne: playerOne.publicKey\n      })\n      .signers([playerOne])\n      .rpc();\n\n    const gameData = await program.account.game.fetch(gamePublicKey);\n\n    expect(gameData.turn).to.equal(1);\n    expect(gameData.players).to.eql([playerOne.publicKey, playerTwo.publicKey]);\n\n    expect(gameData.state).to.eql({ active: {} });\n    expect(gameData.board).to.eql([\n      [null, null, null],\n      [null, null, null],\n      [null, null, null]\n    ]);\n  });\n});\n```\n\n## 56\n\n### --description--\n\nWithin `lib.rs`, define another instruction handler called `play`. It should take a `ctx` parameter of type `Context<Play>`, and return a `Result<()>`.\n\n### --tests--\n\nThe `play` instruction handler should be defined.\n\n```js\nassert.match(__librs, /pub fn play\\s*\\([^\\)]*?\\)/);\n```\n\nThe `play` instruction handler should take a `ctx` parameter of type `Context<Play>`.\n\n```js\nconst playFn = __librs.match(/pub fn play\\s*\\(([^\\)]*?)\\)/)?.[1];\nassert.match(playFn, /ctx\\s*:\\s*Context\\s*<\\s*Play\\s*>/);\n```\n\nThe `play` instruction handler should return a `Result<()>`.\n\n```js\nconst playReturn = __librs.match(/pub fn play\\s*\\([^\\)]*?\\)([^\\{]*){/)?.[1];\nassert.match(playReturn, /->\\s*Result\\s*<\\s*\\(\\s*\\)\\s*>\\s*/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 57\n\n### --description--\n\nWithin `lib.rs`, define a new public struct `Play` that implements the `Accounts` trait.\n\n### --tests--\n\nThe `Play` struct should be defined.\n\n```js\nassert.match(__librs, /pub struct Play/);\n```\n\nThe `Play` struct should implement the `Accounts` trait.\n\n```js\nassert.match(\n  __librs,\n  /(?<=#\\[derive\\s*\\(\\s*Accounts\\s*\\)\\s*\\])\\s*pub struct Play/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 58\n\n### --description--\n\nThe `play` instruction handler will need access to the `game` account.\n\nWithin `Play`, define a field called `game` of type `Account<'info, Game>`.\n\n### --tests--\n\nThe `game` field should be defined.\n\n```js\nconst playStruct = __librs.match(/pub struct Play[^\\{]*?{([^\\}]*)}/)?.[1];\nassert.match(playStruct, /game\\s*:/);\n```\n\nThe `game` field should be of type `Account<'info, Game>`.\n\n```js\nconst playStruct = __librs.match(/pub struct Play[^\\{]*?{([^\\}]*?)}/)?.[1];\nassert.match(playStruct, /game\\s*:\\s*Account\\s*<\\s*'info\\s*,\\s*Game\\s*>/);\n```\n\nThe `Play` struct should take a generic lifetime parameter `'info`.\n\n```js\nassert.match(__librs, /pub struct Play\\s*<'info>/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 59\n\n### --description--\n\nThe `play` instruction handler will need access to the player who called it.\n\nWithin `Play`, define a field called `player` of type `Signer<'info>`.\n\n### --tests--\n\nThe `player` field should be defined.\n\n```js\nconst playStruct = __librs.match(/pub struct Play[^\\{]*?{([^\\}]*)}/)?.[1];\nassert.match(playStruct, /player\\s*:/);\n```\n\nThe `player` field should be of type `Signer<'info>`.\n\n```js\nconst playStruct = __librs.match(/pub struct Play[^\\{]*?{([^\\}]*?)}/)?.[1];\nassert.match(playStruct, /player\\s*:\\s*Signer\\s*<\\s*'info\\s*>/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 60\n\n### --description--\n\nWithin the `play` instruction handler, declare a variable `game`, and assign a mutable reference to the `game` account to it.\n\n### --tests--\n\nThe `game` variable should be declared.\n\n```js\nconst playFn = __librs.match(\n  /pub fn play\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(playFn, /let game/);\n```\n\nThe `game` variable should be assigned `&mut ctx.accounts.game`.\n\n```js\nconst playFn = __librs.match(\n  /pub fn play\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(playFn, /let game\\s*=\\s*&\\s*mut\\s*ctx\\s*.\\s*accounts\\s*.\\s*game/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 61\n\n### --description--\n\nAlong with the `require!` macro, Anchor provides a `require_keys_eq!` macro. This macro takes two public keys, and ensures they are equal:\n\n```rust\nrequire_keys_eq!(\n  ctx.accounts.account_1.key(),\n  ctx.accounts.account_2.key(),\n  OptionalCustomError::MyError\n);\n```\n\n**Note:** This is specifically provided, because the `require_eq!` macro should not be used to compare public keys.\n\nWithin 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.\n\n### --tests--\n\n`play` should have `require_keys_eq!(game.current_player(), ctx.accounts.player.key());`.\n\n```js\nconst librs = await __helpers\n  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)\n  ?.replaceAll(/[ \\t]{2,}/g, ' ');\nconst playFn = librs.match(\n  /pub fn play\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(\n  playFn,\n  /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*\\)/\n);\n```\n\n## 62\n\n### --description--\n\nAdd a third argument of `TicTacToeError::NotPlayersTurn` to the `require_keys_eq!` macro. Also, define the `NotPlayersTurn` error variant in the `TicTacToeError` enum.\n\n### --tests--\n\n`play` should have `require_keys_eq!(game.current_player(), ctx.accounts.player.key(), TicTacToeError::NotPlayersTurn);`.\n\n```js\nconst playFn = __librs.match(\n  /pub fn play\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(\n  playFn,\n  /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*\\)/\n);\n```\n\n`TicTacToeError` should have a `NotPlayersTurn` variant.\n\n```js\nconst ticTacToError = __librs.match(/enum\\s*TicTacToeError\\s*{([^\\}]*)}/)?.[1];\nassert.match(ticTacToError, /NotPlayersTurn/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nglobal.__librs = __librs?.replaceAll(/[ \\t]{2,}/g, ' ');\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n## 63\n\n### --description--\n\nWithin the `play` instruction handler, call the `play` method on the `game` account. Pass in a reference to a variable `tile`.\n\n### --tests--\n\n`play` should have `game.play(&tile);`.\n\n```js\nconst librs = await __helpers\n  .getFile(`${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`)\n  ?.replaceAll(/[ \\t]{2,}/g, ' ');\nconst playFn = librs.match(\n  /pub fn play\\s*\\([^\\)]*?\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(playFn, /game\\s*.\\s*play\\s*\\(\\s*&\\s*tile\\s*\\)/);\n```\n\n## 64\n\n### --description--\n\nAdjust the `play` instruction handler signature to take a `tile` parameter of type `Tile`.\n\n### --tests--\n\n`play` should take a `tile` parameter of type `Tile`.\n\n```js\nconst playFnParams = __librs.match(\n  /pub fn play\\s*\\(([^\\)]*?)\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{([^\\}]*)\\}/\n)?.[1];\nassert.match(playFnParams, /tile\\s*:\\s*Tile/);\n```\n\n## 65\n\n### --description--\n\nRun the tests.\n\n### --tests--\n\nThe tests for the `play` instruction handler should error ❌.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '3 failing');\n```\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --force--\n\n#### --\"learn-anchor-by-building-tic-tac-toe-part-1/tic-tac-toe/tests/tic-tac-toe.ts\"--\n\n```typescript\nimport {\n  AnchorError,\n  Program,\n  AnchorProvider,\n  setProvider,\n  workspace\n} from '@coral-xyz/anchor';\nimport { TicTacToe } from '../target/types/tic_tac_toe';\nimport { expect } from 'chai';\nimport { Keypair, PublicKey } from '@solana/web3.js';\n\ndescribe('tic-tac-toe', () => {\n  // Configure the client to use the local cluster.\n  setProvider(AnchorProvider.env());\n\n  const program = workspace.TicTacToe as Program<TicTacToe>;\n  const programProvider = program.provider as AnchorProvider;\n\n  it('initializes a game', async () => {\n    const playerOne = Keypair.generate();\n    const playerTwo = Keypair.generate();\n\n    const gameId = 'game-1';\n\n    const [gamePublicKey, _] = PublicKey.findProgramAddressSync(\n      [\n        Buffer.from('game'),\n        playerOne.publicKey.toBuffer(),\n        Buffer.from(gameId)\n      ],\n      program.programId\n    );\n\n    // Airdrop to playerOne\n    const sg = await programProvider.connection.requestAirdrop(\n      playerOne.publicKey,\n      1_000_000_000\n    );\n    await programProvider.connection.confirmTransaction(sg);\n\n    await program.methods\n      .setupGame(playerTwo.publicKey, gameId)\n      .accounts({\n        game: gamePublicKey,\n        playerOne: playerOne.publicKey\n      })\n      .signers([playerOne])\n      .rpc();\n\n    const gameData = await program.account.game.fetch(gamePublicKey);\n\n    expect(gameData.turn).to.equal(1);\n    expect(gameData.players).to.eql([playerOne.publicKey, playerTwo.publicKey]);\n\n    expect(gameData.state).to.eql({ active: {} });\n    expect(gameData.board).to.eql([\n      [null, null, null],\n      [null, null, null],\n      [null, null, null]\n    ]);\n  });\n\n  it('has player one win', async () => {\n    const playerOne = Keypair.generate();\n    const playerTwo = Keypair.generate();\n\n    const gameId = 'game-2';\n\n    const [gamePublicKey, _bump] = PublicKey.findProgramAddressSync(\n      [\n        Buffer.from('game'),\n        playerOne.publicKey.toBuffer(),\n        Buffer.from(gameId)\n      ],\n      program.programId\n    );\n\n    // Airdrop to playerOne\n    const sg = await programProvider.connection.requestAirdrop(\n      playerOne.publicKey,\n      1_000_000_000\n    );\n    await programProvider.connection.confirmTransaction(sg);\n\n    await program.methods\n      .setupGame(playerTwo.publicKey, gameId)\n      .accounts({\n        game: gamePublicKey,\n        playerOne: playerOne.publicKey\n      })\n      .signers([playerOne])\n      .rpc();\n\n    let gameData = await program.account.game.fetch(gamePublicKey);\n\n    expect(gameData.turn).to.equal(1);\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 0, column: 0 },\n      2,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [null, null, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerTwo,\n      { row: 1, column: 0 },\n      3,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [{ o: {} }, null, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 0, column: 1 },\n      4,\n      { active: {} },\n      [\n        [{ x: {} }, { x: {} }, null],\n        [{ o: {} }, null, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerTwo,\n      { row: 1, column: 1 },\n      5,\n      { active: {} },\n      [\n        [{ x: {} }, { x: {} }, null],\n        [{ o: {} }, { o: {} }, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 0, column: 2 },\n      5,\n      { won: { winner: playerOne.publicKey } },\n      [\n        [{ x: {} }, { x: {} }, { x: {} }],\n        [{ o: {} }, { o: {} }, null],\n        [null, null, null]\n      ]\n    );\n  });\n\n  it('handles ties', async () => {\n    const playerOne = Keypair.generate();\n    const playerTwo = Keypair.generate();\n\n    const gameId = 'game-3';\n\n    const [gamePublicKey, _bump] = PublicKey.findProgramAddressSync(\n      [\n        Buffer.from('game'),\n        playerOne.publicKey.toBuffer(),\n        Buffer.from(gameId)\n      ],\n      program.programId\n    );\n\n    // Airdrop to playerOne\n    const sg = await programProvider.connection.requestAirdrop(\n      playerOne.publicKey,\n      1_000_000_000\n    );\n    await programProvider.connection.confirmTransaction(sg);\n\n    await program.methods\n      .setupGame(playerTwo.publicKey, gameId)\n      .accounts({\n        game: gamePublicKey,\n        playerOne: playerOne.publicKey\n      })\n      .signers([playerOne])\n      .rpc();\n\n    let gameState = await program.account.game.fetch(gamePublicKey);\n    expect(gameState.turn).to.equal(1);\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 0, column: 0 },\n      2,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [null, null, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerTwo,\n      { row: 1, column: 1 },\n      3,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [null, { o: {} }, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 2, column: 0 },\n      4,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [null, { o: {} }, null],\n        [{ x: {} }, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerTwo,\n      { row: 1, column: 0 },\n      5,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [{ o: {} }, { o: {} }, null],\n        [{ x: {} }, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 1, column: 2 },\n      6,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [{ o: {} }, { o: {} }, { x: {} }],\n        [{ x: {} }, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerTwo,\n      { row: 0, column: 1 },\n      7,\n      { active: {} },\n      [\n        [{ x: {} }, { o: {} }, null],\n        [{ o: {} }, { o: {} }, { x: {} }],\n        [{ x: {} }, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 2, column: 1 },\n      8,\n      { active: {} },\n      [\n        [{ x: {} }, { o: {} }, null],\n        [{ o: {} }, { o: {} }, { x: {} }],\n        [{ x: {} }, { x: {} }, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerTwo,\n      { row: 2, column: 2 },\n      9,\n      { active: {} },\n      [\n        [{ x: {} }, { o: {} }, null],\n        [{ o: {} }, { o: {} }, { x: {} }],\n        [{ x: {} }, { x: {} }, { o: {} }]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 0, column: 2 },\n      9,\n      { tie: {} },\n      [\n        [{ x: {} }, { o: {} }, { x: {} }],\n        [{ o: {} }, { o: {} }, { x: {} }],\n        [{ x: {} }, { x: {} }, { o: {} }]\n      ]\n    );\n  });\n\n  it('handles invalid plays', async () => {\n    const playerOne = Keypair.generate();\n    const playerTwo = Keypair.generate();\n\n    const gameId = 'game-4';\n\n    const [gamePublicKey, _bump] = PublicKey.findProgramAddressSync(\n      [\n        Buffer.from('game'),\n        playerOne.publicKey.toBuffer(),\n        Buffer.from(gameId)\n      ],\n      program.programId\n    );\n\n    // Airdrop to playerOne\n    const sg = await programProvider.connection.requestAirdrop(\n      playerOne.publicKey,\n      1_000_000_000\n    );\n    await programProvider.connection.confirmTransaction(sg);\n\n    await program.methods\n      .setupGame(playerTwo.publicKey, gameId)\n      .accounts({\n        game: gamePublicKey,\n        playerOne: playerOne.publicKey\n      })\n      .signers([playerOne])\n      .rpc();\n\n    let gameData = await program.account.game.fetch(gamePublicKey);\n\n    expect(gameData.turn).to.equal(1);\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 0, column: 0 },\n      2,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [null, null, null],\n        [null, null, null]\n      ]\n    );\n\n    try {\n      await play(\n        program,\n        gamePublicKey,\n        playerOne, // same player in subsequent turns\n        // change sth about the tx because\n        // duplicate tx that come in too fast\n        // after each other may get dropped\n        { row: 1, column: 0 },\n        2,\n        { active: {} },\n        [\n          [{ x: {} }, null, null],\n          [null, null, null],\n          [null, null, null]\n        ]\n      );\n      chai.assert(false, \"should've failed but didn't \");\n    } catch (_err) {\n      expect(_err).to.be.instanceOf(AnchorError);\n      const err: AnchorError = _err;\n      expect(err.error.errorCode.code).to.equal('NotPlayersTurn');\n      expect(err.error.errorCode.number).to.equal(6003);\n      expect(err.program.equals(program.programId)).is.true;\n      expect(err.error.comparedValues).to.deep.equal([\n        playerTwo.publicKey,\n        playerOne.publicKey\n      ]);\n    }\n\n    try {\n      await play(\n        program,\n        gamePublicKey,\n        playerTwo,\n        { row: 5, column: 1 }, // out of bounds row\n        3,\n        { active: {} },\n        [\n          [{ x: {} }, null, null],\n          [null, null, null],\n          [null, null, null]\n        ]\n      );\n      chai.assert(false, \"should've failed but didn't \");\n    } catch (_err) {\n      expect(_err).to.be.instanceOf(AnchorError);\n      const err: AnchorError = _err;\n      expect(err.error.errorCode.number).to.equal(6000);\n      expect(err.error.errorCode.code).to.equal('TileOutOfBounds');\n    }\n\n    try {\n      await play(\n        program,\n        gamePublicKey,\n        playerTwo,\n        { row: 0, column: 0 },\n        3,\n        { active: {} },\n        [\n          [{ x: {} }, null, null],\n          [null, null, null],\n          [null, null, null]\n        ]\n      );\n      chai.assert(false, \"should've failed but didn't \");\n    } catch (_err) {\n      expect(_err).to.be.instanceOf(AnchorError);\n      const err: AnchorError = _err;\n      expect(err.error.errorCode.number).to.equal(6001);\n      expect(err.error.errorCode.code).to.equal('TileAlreadySet');\n    }\n\n    await play(\n      program,\n      gamePublicKey,\n      playerTwo,\n      { row: 1, column: 0 },\n      3,\n      { active: {} },\n      [\n        [{ x: {} }, null, null],\n        [{ o: {} }, null, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 0, column: 1 },\n      4,\n      { active: {} },\n      [\n        [{ x: {} }, { x: {} }, null],\n        [{ o: {} }, null, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerTwo,\n      { row: 1, column: 1 },\n      5,\n      { active: {} },\n      [\n        [{ x: {} }, { x: {} }, null],\n        [{ o: {} }, { o: {} }, null],\n        [null, null, null]\n      ]\n    );\n\n    await play(\n      program,\n      gamePublicKey,\n      playerOne,\n      { row: 0, column: 2 },\n      5,\n      { won: { winner: playerOne.publicKey } },\n      [\n        [{ x: {} }, { x: {} }, { x: {} }],\n        [{ o: {} }, { o: {} }, null],\n        [null, null, null]\n      ]\n    );\n\n    try {\n      await play(\n        program,\n        gamePublicKey,\n        playerOne,\n        { row: 0, column: 2 },\n        6,\n        { won: { winner: playerOne.publicKey } },\n        [\n          [{ x: {} }, { x: {} }, null],\n          [{ o: {} }, { o: {} }, null],\n          [null, null, null]\n        ]\n      );\n      chai.assert(false, \"should've failed but didn't \");\n    } catch (_err) {\n      expect(_err).to.be.instanceOf(AnchorError);\n      const err: AnchorError = _err;\n      expect(err.error.errorCode.number).to.equal(6002);\n      expect(err.error.errorCode.code).to.equal('GameAlreadyOver');\n    }\n  });\n});\n\nasync function play(\n  program: Program<TicTacToe>,\n  game: PublicKey,\n  player: Keypair,\n  tile: { row: number; column: number },\n  expectedTurn: number,\n  expectedGameState:\n    | { active: {} }\n    | { won: { winner: PublicKey } }\n    | { tie: {} },\n  expectedBoard: Array<Array<{ x: {} } | { o: {} } | null>>\n) {\n  await program.methods\n    .play(tile)\n    .accounts({\n      player: player.publicKey,\n      game\n    })\n    .signers([player])\n    .rpc();\n\n  const gameData = await program.account.game.fetch(game);\n\n  expect(gameData.turn).to.equal(expectedTurn);\n  expect(gameData.state).to.eql(expectedGameState);\n  expect(gameData.board).to.eql(expectedBoard);\n}\n```\n\n## 66\n\n### --description--\n\nThe tests failed, because a mutable reference to the `game` account is required, but the account is not marked as `mut`.\n\nMark the `game` account as `mut`.\n\n### --tests--\n\nThe `Play` struct should have `game` annotated with `#[account(mut)]`.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nconst playStruct = librs.match(/pub\\s+struct\\s+Play[^\\{]*?{([^\\}]*)}/)?.[1];\nassert.match(\n  playStruct,\n  /#[\\s\\n]*account[\\s\\n]*\\([\\s\\n]*mut[\\s\\n]*\\)\\s*pub\\s+game/\n);\n```\n\n## 67\n\n### --description--\n\nRun the tests to ensure everything is working as expected.\n\n### --tests--\n\nAll tests should pass for the `anchor test --skip-local-validator` command ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '4 passing');\n```\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 68\n\n### --description--\n\n**Summary**\n\n- Derive `Accounts` for context structs\n- Annotate custom accounts with `#[account]`\n- Annotate custom errors with `#[error_code]`\n- Use the `instruction` attribute to access the instruction data\n- Anchor provides various account constraints:\n  - `init` - Initialises an account, setting the owner field of the created account to the currently executing program\n  - `mut` - Checks the given account is mutable, and persists any state changes\n- The `Account` struct verifies program ownership\n- The `Signer` struct verifies the account in the transaction also signed the transaction\n- The `Program` struct validates the account provided is the given program\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-anchor-by-building-tic-tac-toe-part-2.md",
    "content": "# Solana - Learn How to Test an Anchor Program: Part 2\n\n## 1\n\n### --description--\n\nIn 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.\n\nAnchor automatically generated some test boilerplate for the `tic-tac-toe` program in the `tests/` directory. You will be mostly working in this directory.\n\nWithin a new terminal, change into the `tic-tac-toe` directory.\n\n### --tests--\n\nYou should be in the `tic-tac-toe` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 2\n\n### --description--\n\nThe boilerplate includes a `program` variable that uses the generated `TicTacToe` IDL to create a program instance. The program can send transactions, fetch deserialized accounts, decode instruction data, subscribe to account changes, and listen to events.\n\nWithin `tic-tac-toe/tests/tic-tac-toe.ts`, immediately below the `program` variable declaration, declare a `programProvider` variable and assign the following to it:\n\n```typescript\nprogram.provider as AnchorProvider;\n```\n\n### --tests--\n\nThe `programProvider` variable should be declared.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'programProvider';\n});\nassert.exists(\n  variableDeclaration,\n  'A variable named `programProvider` should exist'\n);\n```\n\nThe `programProvider` variable should be assigned `program.provider as AnchorProvider`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'programProvider';\n});\nassert.exists(\n  variableDeclaration,\n  'A variable named `programProvider` should exist'\n);\nconst tAsExpression = variableDeclaration.declarations?.[0]?.init;\nconst { object, property } = tAsExpression.expression;\nassert.equal(\n  object.name,\n  'program',\n  'The `programProvider` variable should be assigned `program.provider`'\n);\nassert.equal(\n  property.name,\n  'provider',\n  'The `programProvider` variable should be assigned `program.provider`'\n);\nconst tAnnotation = tAsExpression.typeAnnotation;\nassert.equal(\n  tAnnotation.typeName.name,\n  'AnchorProvider',\n  'The `programProvider` variable should be assigned `program.provider as AnchorProvider`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --force--\n\n#### --\"learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/tests/tic-tac-toe.ts\"--\n\n```typescript\nimport {\n  AnchorProvider,\n  workspace,\n  setProvider,\n  Program\n} from '@coral-xyz/anchor';\nimport { TicTacToe } from '../target/types/tic_tac_toe';\n\ndescribe('TicTacToe', () => {\n  // Configure the client to use the local cluster.\n  setProvider(AnchorProvider.env());\n\n  const program = workspace.TicTacToe as Program<TicTacToe>;\n\n  it('Is initialized!', async () => {\n    // Add your test here.\n    const tx = await program.methods.initialize().rpc();\n    console.log('Your transaction signature', tx);\n  });\n});\n```\n\n## 3\n\n### --description--\n\nTo get autocomplete for the program, build your program with:\n\n```bash\nanchor build\n```\n\nAnchor creates an IDL from your program, and stores it in the `target/types/tic_tac_toe.ts` file.\n\n### --tests--\n\nThe `target/types/tic_tac_toe.ts` file should exist.\n\n```js\nconst isFile = __helpers.fileExists(\n  `${project.dashedName}/tic-tac-toe/target/types/tic_tac_toe.ts`\n);\nassert.isTrue(isFile);\n```\n\n## 4\n\n### --description--\n\nWithin the `it` callback, change the `initialize` call to `setupGame`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const tx = await program.methods.setupGame().rpc();`.\n\n```js\nconst memberExpression = babelisedCode.getType('MemberExpression').find(m => {\n  return (\n    m.object?.object?.name === 'program' &&\n    m.object?.property?.name === 'methods'\n  );\n});\nassert.exists(memberExpression, '`program.methods.` should exist');\nconst { property } = memberExpression;\nassert.equal(\n  property.name,\n  'setupGame',\n  '`program.methods.setupGame` should exist'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 5\n\n### --description--\n\nNow, you need to create the accounts to pass to the `setupGame` instruction handler.\n\nAt the top of the `it` callback, generate two new keypairs, and assign them to two new variables: `playerOne`, and `playerTwo`.\n\n### --tests--\n\nThe `playerOne` variable should be declared.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclarationNames = blockStatement?.body?.map(v => {\n  return v?.declarations?.[0]?.id?.name;\n});\nassert.include(\n  variableDeclarationNames,\n  'playerOne',\n  'A variable named `playerOne` should exist'\n);\n```\n\nThe `playerOne` variable should be assigned `Keypair.generate()`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'playerOne';\n});\nassert.exists(variableDeclaration, 'A variable named `playerOne` should exist');\nconst memberExpression_playerOne =\n  variableDeclaration.declarations?.[0]?.init?.callee;\nconst { property, object } = memberExpression_playerOne;\nassert.equal(\n  object.name,\n  'Keypair',\n  'The `playerOne` variable should be assigned `Keypair.generate()`'\n);\nassert.equal(\n  property.name,\n  'generate',\n  'The `playerOne` variable should be assigned `Keypair.generate()`'\n);\n```\n\nThe `playerTwo` variable should be declared.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclarationNames = blockStatement?.body?.map(v => {\n  return v?.declarations?.[0]?.id?.name;\n});\nassert.include(\n  variableDeclarationNames,\n  'playerTwo',\n  'A variable named `playerTwo` should exist'\n);\n```\n\nThe `playerTwo` variable should be assigned `Keypair.generate()`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'playerTwo';\n});\nassert.exists(variableDeclaration, 'A variable named `playerTwo` should exist');\nconst memberExpression_playerTwo =\n  variableDeclaration.declarations?.[0]?.init?.callee;\nconst { property, object } = memberExpression_playerTwo;\nassert.equal(\n  object.name,\n  'Keypair',\n  'The `playerTwo` variable should be assigned `Keypair.generate()`'\n);\nassert.equal(\n  property.name,\n  'generate',\n  'The `playerTwo` variable should be assigned `Keypair.generate()`'\n);\n```\n\nThe `Keypair` class should be imported from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source?.value === '@solana/web3.js';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\n\nconst specifierNames = importDeclaration.specifiers?.map(s => {\n  return s?.local?.name;\n});\nassert.include(\n  specifierNames,\n  'Keypair',\n  'The `Keypair` class should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 6\n\n### --description--\n\nWithin the `it` callback, create a new `gameId` variable, and assign it a value of `\"game-1\"`.\n\n### --tests--\n\nThe `gameId` variable should be declared.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclarationNames = blockStatement?.body?.map(v => {\n  return v?.declarations?.[0]?.id?.name;\n});\nassert.include(\n  variableDeclarationNames,\n  'gameId',\n  'A variable named `gameId` should exist'\n);\n```\n\nThe `gameId` variable should be assigned `\"game-1\"`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameId';\n});\nassert.exists(variableDeclaration, 'A variable named `gameId` should exist');\nconst { value } = variableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  value,\n  'game-1',\n  'The `gameId` variable should be assigned `\"game-1\"`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 7\n\n### --description--\n\nNow, PDA's will make more sense - they can be <dfn title=\"You can generate the same public key before the program is initialized\">repeatably, programmatically generated</dfn>:\n\n```typescript\nconst [pda, bump] = PublicKey.findProgramAddressSync(\n  [Buffer.from(seed)],\n  program.programId\n);\n```\n\nDestructure a variable `gamePublicKey` from `PublicKey.findProgramAddressSync`, using `\"game\"`, the payer's public key, and `gameId` as seeds.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const [gamePublicKey, _] = PublicKey.findProgramAddressSync([Buffer.from('game'), playerOne.publicKey.toBuffer(), Buffer.from(gameId)], program.programId);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `const[gamePublicKey,_]=PublicKey.findProgramAddressSync([Buffer.from('game'),playerOne.publicKey.toBuffer(),Buffer.from(gameId)],program.programId)`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 8\n\n### --description--\n\nPass the public key of `playerTwo` as an argument to the `setupGame` instruction call.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const tx = await program.methods.setupGame(playerTwo.publicKey).rpc();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `const tx=await program.methods.setupGame(playerTwo.publicKey).rpc()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 9\n\n### --description--\n\nPass `gameId` as the second argument to the `setupGame` instruction call.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const tx = await program.methods.setupGame(playerTwo.publicKey, gameId).rpc();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `const tx=await program.methods.setupGame(playerTwo.publicKey,gameId).rpc()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 10\n\n### --description--\n\nIn a new terminal, start a clean local cluster:\n\n```bash\nsolana-test-validator --reset\n```\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 11\n\n### --description--\n\nRun the tests, using the local validator you just started:\n\n```bash\nanchor test --skip-local-validator\n```\n\n### --tests--\n\n`anchor test` should error with `Error: Invalid arguments: game not provided`.\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, 'Error: Invalid arguments: game not provided');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 12\n\n### --description--\n\nYou called `setupGame` without passing in any accounts. So, Anchor tried to create the `game` account using the transaction payer - your local Solana wallet.\n\nChain a `.accounts` call to the `setupGame` call, and pass in:\n\n```typescript\n{\n  game: gamePublicKey,\n  playerOne: playerOne.publicKey,\n}\n```\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const tx = await program.methods.setupGame(playerTwo.publicKey, gameId).accounts({ game: gamePublicKey, playerOne: playerOne.publicKey }).rpc();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `const tx=await program.methods.setupGame(playerTwo.publicKey,gameId).accounts({game:gamePublicKey,playerOne:playerOne.publicKey}).rpc()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 13\n\n### --description--\n\nRun the tests again.\n\n### --tests--\n\nYou `anchor test --skip-local-validator` test should error with `Error: Signature verification failed`.\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, 'Error: Signature verification failed');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 14\n\n### --description--\n\nSeeing as the `game` and `playerOne` accounts are mutated (e.g. funds taken for fees, data changed), these accounts need to sign the transaction.\n\nHowever, one of the main benefits with PDAs is the owner is the program. So, a PDA does not need to sign transactions mutating it within the owner program.\n\nChain a `.signers` call to the `setupGame` call, and pass in an array of the `playerOne` keypair.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const tx = await program.methods.setupGame(playerTwo.publicKey, gameId).accounts({ game: gamePublicKey, playerOne: playerOne.publicKey }).signers([playerOne]).rpc();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `const tx=await program.methods.setupGame(playerTwo.publicKey,gameId).accounts({game:gamePublicKey,playerOne:playerOne.publicKey}).signers([playerOne]).rpc()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 15\n\n### --description--\n\nRun the tests again.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` test should error with `Error: failed to send transaction`.\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, 'Error: failed to send transaction');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 16\n\n### --description--\n\nThis time, the transaction failed, because `playerOne` is used as the payer, but has not even been added to the blockchain, let alone have any funds 😱\n\nWithin the `it` callback, before the transaction is sent, declare a variable `sg` and assign the transaction signature for requesting an airdrop of 1 SOL to `playerOne`:\n\n```typescript\nawait programProvider.connection.requestAirdrop(<PUBLIC_KEY>, <AMOUNT_IN_LAMPORTS>);\n```\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const sg = await programProvider.connection.requestAirdrop(playerOne.publicKey, 1_000_000_000);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `const sg=await programProvider.connection.requestAirdrop(playerOne.publicKey,1_000_000_000)`,\n  `const sg=await programProvider.connection.requestAirdrop(playerOne.publicKey,1000000000)`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 17\n\n### --description--\n\nBefore trying to spend any funds, you need to ensure the transaction has been confirmed.\n\nBelow the `requestAirdrop` call, add:\n\n```typescript\nawait programProvider.connection.confirmTransaction(<TRANSACTION_SIGNATURE>);\n```\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `await programProvider.connection.confirmTransaction(sg);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `await programProvider.connection.confirmTransaction(sg)`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 18\n\n### --description--\n\nFinally, run the tests again.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` tests should pass ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '1 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 19\n\n### --description--\n\nTo clarify what is happening behind the scenes when Anchor calls your program, split your instruction and transaction up into two lines:\n\n```typescript\nconst ix = program.methods.<PROGRAM_METHOD>(<PROGRAM_METHOD_ARGS>).accounts(<PROGRAM_METHOD_ACCOUNTS>).signers(<PROGRAM_METHOD_SIGNERS>);\nawait ix.rpc();\n```\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const ix = program.methods.setupGame(playerTwo.publicKey, gameId).accounts({ game: gamePublicKey, playerOne: playerOne.publicKey }).signers([playerOne]);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'ix';\n});\nassert.exists(variableDeclaration, 'A variable named `ix` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const ix=program.methods.setupGame(playerTwo.publicKey,gameId).accounts({game:gamePublicKey,playerOne:playerOne.publicKey}).signers([playerOne])`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n`tests/tic-tac-toe.ts` should have `await ix.rpc();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `await ix.rpc()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 20\n\n### --description--\n\nRun the tests again to ensure everything still works.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` test should pass ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '1 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 21\n\n### --description--\n\nAfter the transaction is sent, you can fetch an account's data to see if it was correctly set up:\n\n```typescript\nconst accountData = await program.account.<ACCOUNT_NAME>.fetch(<ACCOUNT_PUBLIC_KEY>);\n```\n\nDeclare a variable `gameData` and assign it the `game` account's data.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const gameData = await program.account.game.fetch(gamePublicKey);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nassert.exists(variableDeclaration, 'A variable named `gameData` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gameData=await program.account.game.fetch(gamePublicKey)`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 22\n\n### --description--\n\nAssert the `game` account has a `turn` property equal to `1`.\n\n_Hint:_ The boilerplate created by Anchor comes with `chai.js`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should throw if `gameData.turn !== 1`.\n\n```js\n// Get all code in the `it` callback\n// Remove everything defined before `const gameData`, and remove the `gameData` declaration\n// Eval with fixture `gameData`, `playerOne`, and `playerTwo`\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const gameData = {\n    turn: 0\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  await eval(`(async () => {\n    const gameData= {\n      turn: 1\n    };\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 23\n\n### --description--\n\nAssert the `game` account has a `players` property equal to an array of the public keys of `playerOne` and `playerTwo`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should throw if `gameData.players[0] !== playerOne.publicKey`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const playerOne = { publicKey: 'playerOne' };\n  const playerTwo = { publicKey: 'playerTwo' };\n  const gameData = {\n    turn: 1,\n    players: ['notPlayerOne', playerTwo.publicKey]\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const playerOne = { publicKey: 'playerOne' };\n  const playerTwo = { publicKey: 'playerTwo' };\n  const gameData = {\n    turn: 1,\n    players: [playerOne.publicKey, playerTwo.publicKey]\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n`tests/tic-tac-toe.ts` should throw if `gameData.players[1] !== playerTwo.publicKey`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const playerOne = { publicKey: 'playerOne' };\n  const playerTwo = { publicKey: 'playerTwo' };\n  const gameData = {\n    turn: 1,\n    players: [playerOne.publicKey, 'notPlayerOne']\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const playerOne = { publicKey: 'playerOne' };\n  const playerTwo = { publicKey: 'playerTwo' };\n  const gameData = {\n    turn: 1,\n    players: [playerOne.publicKey, playerTwo.publicKey]\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 24\n\n### --description--\n\nAssert the `game` account has a `state` property equal to `active: {}`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should throw if `gameData.state.active !== {}`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const playerOne = { publicKey: 'playerOne' };\n  const playerTwo = { publicKey: 'playerTwo' };\n  const gameData = {\n    turn: 1,\n    players: [playerOne.publicKey, playerTwo.publicKey],\n    state: {\n      active: ''\n    }\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    \"The `it` callback should throw when `gameData.state.active == ''\"\n  );\n  logover.debug(e);\n}\ntry {\n  const playerOne = { publicKey: 'playerOne' };\n  const playerTwo = { publicKey: 'playerTwo' };\n  const gameData = {\n    turn: 1,\n    players: [playerOne.publicKey, playerTwo.publicKey],\n    state: {\n      active: {}\n    }\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(\n    e,\n    'The `it` callback should not throw when `gameData.state.active == {}'\n  );\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 25\n\n### --description--\n\nAssert the `game` account has a `board` property equal to a 3x3 array of `null` values.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should throw if `gameData.board !== [[null,null,null],[null,null,null],[null,null,null]]`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it';\n});\nconst blockStatement = callExpression?.arguments?.[0]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const playerOne = { publicKey: 'playerOne' };\n  const playerTwo = { publicKey: 'playerTwo' };\n  const gameData = {\n    turn: 1,\n    players: [playerOne.publicKey, playerTwo.publicKey],\n    state: {\n      active: {}\n    },\n    board: [\n      [false, null, null],\n      [null, null, null],\n      [null, null, null]\n    ]\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'The `it` callback should throw when `gameData.state.board == [[false,null,null],[null,null,null],[null,null,null]]'\n  );\n  logover.debug(e);\n}\ntry {\n  const playerOne = { publicKey: 'playerOne' };\n  const playerTwo = { publicKey: 'playerTwo' };\n  const gameData = {\n    turn: 1,\n    players: [playerOne.publicKey, playerTwo.publicKey],\n    state: {\n      active: {}\n    },\n    board: [\n      [null, null, null],\n      [null, null, null],\n      [null, null, null]\n    ]\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(\n    e,\n    'The `it` callback should not throw when `gameData.state.board == [[null,null,null],[null,null,null],[null,null,null]]'\n  );\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 26\n\n### --description--\n\nRun the tests again to ensure everything still works.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` test should pass ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '1 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 27\n\n### --description--\n\nWithin the `tic-tac-toe.ts` file, within the `describe` callback, add a new `it` function call with a title of `\"has player one win\"`, and an empty, asynchronous callback function.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `it('has player one win', async () => {});`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'describe';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `it('has player one win',async()=>{})`,\n  `it(\"has player one win\",async()=>{})`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 28\n\n### --description--\n\nWithin the second `it` callback, generate two keypairs, and assign them to new variables `playerOne` and `playerTwo`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const playerOne = Keypair.generate();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'playerOne';\n});\nassert.exists(variableDeclaration, 'A variable named `playerOne` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const playerOne=Keypair.generate()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n`tests/tic-tac-toe.ts` should have `const playerTwo = Keypair.generate();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'playerTwo';\n});\nassert.exists(variableDeclaration, 'A variable named `playerTwo` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const playerTwo=Keypair.generate()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 29\n\n### --description--\n\nDeclare a variable `gameId`, and assign `\"game-2\"` to it.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const gameId = \"game-2\";`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameId';\n});\nassert.exists(variableDeclaration, 'A variable named `gameId` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gameId=\"game-2\"`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 30\n\n### --description--\n\nDestructure a `gamePublicKey` variable from deriving the program address.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const [gamePublicKey] = PublicKey.findProgramAddressSync([Buffer.from(\"game\"),playerOne.publicKey.toBuffer(),Buffer.from(gameId)], program.programId);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.elements?.[0]?.name === 'gamePublicKey';\n});\nassert.exists(\n  variableDeclaration,\n  'A variable named `gamePublicKey` should be destructured'\n);\nconst awaitExpression = variableDeclaration?.declarations?.[0]?.init;\nconst actualCodeString = babelisedCode.generateCode(awaitExpression, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `PublicKey.findProgramAddressSync([Buffer.from(\"game\"),playerOne.publicKey.toBuffer(),Buffer.from(gameId)],program.programId)`,\n  `PublicKey.findProgramAddressSync([Buffer.from('game'),playerOne.publicKey.toBuffer(),Buffer.from(gameId)],program.programId)`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 31\n\n### --description--\n\nRequest an airdrop for the first player, and await confirmation.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const sg = await programProvider.connection.requestAirdrop(playerOne.publicKey, 1_000_000_000)`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'sg';\n});\nassert.exists(variableDeclaration, 'A variable named `sg` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `const sg=await programProvider.connection.requestAirdrop(playerOne.publicKey,1_000_000_000)`,\n  `const sg=await programProvider.connection.requestAirdrop(playerOne.publicKey,1000000000)`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n`tests/tic-tac-toe.ts` should have `await programProvider.connection.confirmTransaction(sg);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `await programProvider.connection.confirmTransaction(sg)`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 32\n\n### --description--\n\nCall your program's `setupGame` method, passing in the required arguments, and adding the necessary accounts and signers.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `await program.methods.setupGame(playerTwo.publicKey, gameId).accounts({ game: gamePublicKey, playerOne: playerOne.publicKey }).signers([playerOne]).rpc();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `await program.methods.setupGame(playerTwo.publicKey,gameId).accounts({game:gamePublicKey,playerOne:playerOne.publicKey}).signers([playerOne]).rpc()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 33\n\n### --description--\n\nFetch the `game` account's data, and assign it to a variable `gameData`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const gameData = await program.account.game.fetch(gamePublicKey);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nassert.exists(variableDeclaration, 'A variable named `gameData` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gameData=await program.account.game.fetch(gamePublicKey)`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 34\n\n### --description--\n\nAssert the `game` account has a `turn` property equal to `1`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should throw if `gameData.turn !== 1`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const gameData = {\n    turn: 0\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const gameData = {\n    turn: 1\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 35\n\n### --description--\n\nNow, to play a move, call the `play` method of your program. Pass in `{ row: 0, column: 0 }` as the tile to play.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `await program.methods.play({ row: 0, column: 0 });`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await program.methods.play({row:0,column:0})`,\n  `await program.methods.play({column:0,row:0})`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 36\n\n### --description--\n\nOff of the `play` method call, chain a call to `accounts`, passing in the `game` and correct `player` public keys.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `await program.methods.play({ row: 0, column: 0 }).accounts({ game: gamePublicKey, player: playerOne.publicKey });`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await program.methods.play({row:0,column:0}).accounts({game:gamePublicKey,player:playerOne.publicKey})`,\n  `await program.methods.play({column:0,row:0}).accounts({game:gamePublicKey,player:playerOne.publicKey})`,\n  `await program.methods.play({row:0,column:0}).accounts({player:playerOne.publicKey,game:gamePublicKey})`,\n  `await program.methods.play({column:0,row:0}).accounts({player:playerOne.publicKey,game:gamePublicKey})`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 37\n\n### --description--\n\nOff of the `accounts` method call, chain a call to `signers`, passing in the correct keypair.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `await program.methods.play({ row: 0, column: 0 }).accounts({ game: gamePublicKey, player: playerOne.publicKey }).signers([playerOne]);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await program.methods.play({row:0,column:0}).accounts({game:gamePublicKey,player:playerOne.publicKey}).signers([playerOne])`,\n  `await program.methods.play({column:0,row:0}).accounts({game:gamePublicKey,player:playerOne.publicKey}).signers([playerOne])`,\n  `await program.methods.play({row:0,column:0}).accounts({player:playerOne.publicKey,game:gamePublicKey}).signers([playerOne])`,\n  `await program.methods.play({column:0,row:0}).accounts({player:playerOne.publicKey,game:gamePublicKey}).signers([playerOne])`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 38\n\n### --description--\n\nOff of the `signers` method call, chain the `rpc` method call in order to send the transaction to the network.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `await program.methods.play({ row: 0, column: 0 }).accounts({ game: gamePublicKey, player: playerOne.publicKey }).signers([playerOne]).rpc();`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst awaitExpression = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(awaitExpression, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await program.methods.play({row:0,column:0}).accounts({game:gamePublicKey,player:playerOne.publicKey}).signers([playerOne]).rpc()`,\n  `await program.methods.play({column:0,row:0}).accounts({game:gamePublicKey,player:playerOne.publicKey}).signers([playerOne]).rpc()`,\n  `await program.methods.play({row:0,column:0}).accounts({player:playerOne.publicKey,game:gamePublicKey}).signers([playerOne]).rpc()`,\n  `await program.methods.play({column:0,row:0}).accounts({player:playerOne.publicKey,game:gamePublicKey}).signers([playerOne]).rpc()`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 39\n\n### --description--\n\nAfter the `play` method call, fetch the `game` account's data, and assign it to a variable `gameData2`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `const gameData2 = await program.account.game.fetch(gamePublicKey);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData2';\n});\nassert.exists(variableDeclaration, 'A variable named `gameData2` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gameData2=await program.account.game.fetch(gamePublicKey)`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 40\n\n### --description--\n\nAssert the `game` account has a `turn` property equal to `2`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should throw if `gameData2.turn !== 2`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData2';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const gameData2 = {\n    turn: 1\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const gameData2 = {\n    turn: 2\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 41\n\n### --description--\n\nAssert the `game` account has a `board` property equal to `[[{x:{}}, null, null], [null, null, null], [null, null, null]]`.\n\n**Note:** The Rust code uses an enum to denote `X` and `O` tiles. This is deserialized to a JavaScript object with a single key, `x` or `o`, depending on the tile. This is why the `x` tile is represented as `{x:{}}` in the assertion.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should throw if `gameData2.board !== [[{x:{}}, null, null], [null, null, null], [null, null, null]]`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData2';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const gameData2 = {\n    turn: 2,\n    board: [\n      [null, null, null],\n      [null, null, null],\n      [null, null, null]\n    ]\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const gameData2 = {\n    turn: 2,\n    board: [\n      [{ x: {} }, null, null],\n      [null, null, null],\n      [null, null, null]\n    ]\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 42\n\n### --description--\n\nAssert the `game` account has a `state` property equal to `{ active: {} }`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should throw if `gameData2.state !== { active: {} }`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData2';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const gameData2 = {\n    turn: 2,\n    board: [\n      [{ x: {} }, null, null],\n      [null, null, null],\n      [null, null, null]\n    ],\n    state: { won: { winner: playerOne.publicKey } }\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const gameData2 = {\n    turn: 2,\n    board: [\n      [{ x: {} }, null, null],\n      [null, null, null],\n      [null, null, null]\n    ],\n    state: { active: {} }\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 43\n\n### --description--\n\nRun the tests to see if everything is working as expected.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` tests should pass ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '2 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 44\n\n### --description--\n\nYou will be making many of these `play` calls. So, abstract the logic into a function.\n\nOutwith the `describe` call, define an async function `play` with the following signature:\n\n```ts\nasync function play(\n  program: Program<TicTacToe>,\n  game: PublicKey,\n  player: Keypair,\n  tile: { row: number; column: number },\n  expectedTurn: number,\n  expectedGameState:\n    | { active: {} }\n    | { won: { winner: PublicKey } }\n    | { tie: {} },\n  expectedBoard: Array<Array<{ x: {} } | { o: {} } | null>>\n): Promise<void>;\n```\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have an async function `play` with the correct signature.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getType('FunctionDeclaration')\n  .find(f => {\n    return f?.id?.name === 'play';\n  });\nassert.exists(functionDeclaration, 'A function named `play` should exist');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 45\n\n### --description--\n\nCut the `play` call from the `it` block, and paste it into the `play` function, as well as the `fetch` call and subsequent assertions.\n\nChange the arguments to use the `play` function parameters, and rename `gameData2` to `gameData`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have a `play` function that calls `await program.methods.play(tile).accounts({player: player.publicKey, game}).signers([player]).rpc();`\n\n```js\nconst functionDeclaration = babelisedCode\n  .getType('FunctionDeclaration')\n  .find(f => {\n    return f?.id?.name === 'play';\n  });\nconst blockStatement = functionDeclaration?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await program.methods.play(tile).accounts({player:player.publicKey,game}).signers([player]).rpc()`,\n  `await program.methods.play(tile).accounts({game,player:player.publicKey}).signers([player]).rpc()`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n`tests/tic-tac-toe.ts` should have a `play` function that calls `const gameData = await program.account.game.fetch(game);`\n\n```js\nconst functionDeclaration = babelisedCode\n  .getType('FunctionDeclaration')\n  .find(f => {\n    return f?.id?.name === 'play';\n  });\nconst blockStatement = functionDeclaration?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nassert.exists(variableDeclaration, 'A variable named `gameData` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gameData=await program.account.game.fetch(game)`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n`tests/tic-tac-toe.ts` should have a `play` function that asserts `gameData.turn === expectedTurn`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getType('FunctionDeclaration')\n  .find(f => {\n    return f?.id?.name === 'play';\n  });\nconst blockStatement = functionDeclaration?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const expectedTurn = 1;\n  const expectedGameState = { active: {} };\n  const expectedBoard = [\n    [{ x: {} }, null, null],\n    [null, null, null],\n    [null, null, null]\n  ];\n  const gameData = {\n    turn: 0,\n    board: expectedBoard,\n    state: expectedGameState\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const expectedTurn = 1;\n  const expectedGameState = { active: {} };\n  const expectedBoard = [\n    [{ x: {} }, null, null],\n    [null, null, null],\n    [null, null, null]\n  ];\n  const gameData = {\n    turn: expectedTurn,\n    board: expectedBoard,\n    state: expectedGameState\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n`tests/tic-tac-toe.ts` should have a `play` function that asserts `gameData.state === expectedGameState`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getType('FunctionDeclaration')\n  .find(f => {\n    return f?.id?.name === 'play';\n  });\nconst blockStatement = functionDeclaration?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const expectedTurn = 1;\n  const expectedGameState = { won: { winner: playerOne.publicKey } };\n  const expectedBoard = [\n    [{ x: {} }, null, null],\n    [null, null, null],\n    [null, null, null]\n  ];\n  const gameData = {\n    turn: expectedTurn,\n    board: expectedBoard,\n    state: { active: {} }\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const expectedTurn = 1;\n  const expectedGameState = { active: {} };\n  const expectedBoard = [\n    [{ x: {} }, null, null],\n    [null, null, null],\n    [null, null, null]\n  ];\n  const gameData = {\n    turn: expectedTurn,\n    board: expectedBoard,\n    state: expectedGameState\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n`tests/tic-tac-toe.ts` should have a `play` function that asserts `gameData.board === expectedBoard`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getType('FunctionDeclaration')\n  .find(f => {\n    return f?.id?.name === 'play';\n  });\nconst blockStatement = functionDeclaration?.body;\nconst ind = blockStatement?.body?.findIndex(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameData';\n});\nblockStatement?.body?.splice(0, ind + 1);\nconst assertionCodeString = babelisedCode.generateCode(blockStatement);\n\n// Bring chai and `chai.expect` into scope for eval\nconst chai = await import('chai');\nconst { expect } = chai;\ntry {\n  const expectedTurn = 1;\n  const expectedGameState = { active: {} };\n  const expectedBoard = [\n    [{ x: {} }, null, null],\n    [null, null, null],\n    [null, null, null]\n  ];\n  const gameData = {\n    turn: expectedTurn,\n    board: [\n      [null, null, null],\n      [null, null, null],\n      [null, null, null]\n    ],\n    state: expectedGameState\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n  assert(false, 'fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'The `it` callback should throw');\n  logover.debug(e);\n}\ntry {\n  const expectedTurn = 1;\n  const expectedGameState = { active: {} };\n  const expectedBoard = [\n    [{ x: {} }, null, null],\n    [null, null, null],\n    [null, null, null]\n  ];\n  const gameData = {\n    turn: expectedTurn,\n    board: expectedBoard,\n    state: expectedGameState\n  };\n  await eval(`(async () => {\n    ${assertionCodeString}\n  })()`);\n} catch (e) {\n  assert.fail(e, 'The `it` callback should not throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 46\n\n### --description--\n\nBack within the second `it` callback, use the `play` function to play that same move for `playerOne`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should call `await play(program, gamePublicKey, playerOne, { row: 0, column: 0 }, 2, { active: {} }, [[{x:{}}, null, null], [null, null, null], [null, null, null]]);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await play(program,gamePublicKey,playerOne,{row:0,column:0},2,{active:{}},[[{x:{}},null,null],[null,null,null],[null,null,null]])`,\n  `await play(program,gamePublicKey,playerOne,{column:0,row:0},2,{active:{}},[[{x:{}},null,null],[null,null,null],[null,null,null]])`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 47\n\n### --description--\n\nRun the tests to confirm everything is still working.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` tests should pass ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '2 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 48\n\n### --description--\n\nCall the play function two more times. Once for player two at `{row: 1, column: 0}`, and once for player one at `{row: 0, column: 1}`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should call `await play(program, gamePublicKey, playerTwo, {row:1,column:0}, 3, {active:{}}, [[{x:{}},null,null],[{o:{}},null,null],[null,null,null]]);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await play(program,gamePublicKey,playerTwo,{row:1,column:0},3,{active:{}},[[{x:{}},null,null],[{o:{}},null,null],[null,null,null]])`,\n  `await play(program,gamePublicKey,playerTwo,{column:0,row:1},3,{active:{}},[[{x:{}},null,null],[{o:{}},null,null],[null,null,null]])`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n`tests/tic-tac-toe.ts` should call `await play(program, gamePublicKey, playerOne, {row:0,column:1}, 4, {active:{}}, [[{x:{}},{x:{}},null],[{o:{}},null,null],[null,null,null]]);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await play(program,gamePublicKey,playerOne,{row:0,column:1},4,{active:{}},[[{x:{}},{x:{}},null],[{o:{}},null,null],[null,null,null]])`,\n  `await play(program,gamePublicKey,playerOne,{column:1,row:0},4,{active:{}},[[{x:{}},{x:{}},null],[{o:{}},null,null],[null,null,null]])`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 49\n\n### --description--\n\nRun the tests to confirm everything is still working.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` tests should pass ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '2 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 50\n\n### --description--\n\nCall the play function two more times. Once for player two at `{row: 1, column: 1}`, and once for player one at `{row: 0, column: 2}`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should call `await play(program, gamePublicKey, playerTwo, {row:1,column:1}, 5, {active:{}}, [[{x:{}},{x:{}},null],[{o:{}},{o:{}},null],[null,null,null]]);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await play(program,gamePublicKey,playerTwo,{row:1,column:1},5,{active:{}},[[{x:{}},{x:{}},null],[{o:{}},{o:{}},null],[null,null,null]])`,\n  `await play(program,gamePublicKey,playerTwo,{column:1,row:1},5,{active:{}},[[{x:{}},{x:{}},null],[{o:{}},{o:{}},null],[null,null,null]])`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n`tests/tic-tac-toe.ts` should call `await play(program, gamePublicKey, playerOne, {row:0,column:2}, 5, {won:{winner:playerOne.publicKey}}, [[{x:{}},{x:{}},{x:{}}],[{o:{}},{o:{}},null],[null,null,null]]);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' && c.arguments?.[0]?.value === 'has player one win'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await play(program,gamePublicKey,playerOne,{row:0,column:2},5,{won:{winner:playerOne.publicKey}},[[{x:{}},{x:{}},{x:{}}],[{o:{}},{o:{}},null],[null,null,null]])`,\n  `await play(program,gamePublicKey,playerOne,{column:2,row:0},5,{won:{winner:playerOne.publicKey}},[[{x:{}},{x:{}},{x:{}}],[{o:{}},{o:{}},null],[null,null,null]])`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 51\n\n### --description--\n\nRun the tests to confirm everything is still working.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` tests should pass ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '2 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 52\n\n### --description--\n\nWithin the `describe` callback, create a new `it` call with a title of `\"handles ties\"`, and an asynchronous callback.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have a `describe` callback that calls `it` with a title of `\"handles ties\"`.\n\n```js\nconst callExpressions = babelisedCode.getType('CallExpression').filter(c => {\n  return c.callee?.name === 'it';\n});\nassert.include(\n  callExpressions.map(c => c.arguments?.[0]?.value),\n  'handles ties'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 53\n\n### --description--\n\nWithin the `\"handles ties\"` callback, set up a new game similarly to the previous tests, but with a `gameId` of `\"game-3\"`.\n\n### --tests--\n\nA `playerOne` variable should be declared and assigned `Keypair.generate()`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it' && c.arguments?.[0]?.value === 'handles ties';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'playerOne';\n});\nassert.exists(variableDeclaration, 'A variable named `playerOne` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const playerOne=Keypair.generate()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\nA `playerTwo` variable should be declared and assigned `Keypair.generate()`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it' && c.arguments?.[0]?.value === 'handles ties';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'playerTwo';\n});\nassert.exists(variableDeclaration, 'A variable named `playerTwo` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const playerTwo=Keypair.generate()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\nA `gameId` variable should be declared and assigned `\"game-3\"`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it' && c.arguments?.[0]?.value === 'handles ties';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameId';\n});\nassert.exists(variableDeclaration, 'A variable named `gameId` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gameId=\"game-3\"`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\nA `gamePublicKey` variable should be destructed from a `PublicKey.findProgramAddressSync` call.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it' && c.arguments?.[0]?.value === 'handles ties';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.elements?.[0]?.name === 'gamePublicKey';\n});\nassert.exists(\n  variableDeclaration,\n  'A variable named `gamePublicKey` should be destructured'\n);\nconst awaitExpression = variableDeclaration?.declarations?.[0]?.init;\nconst actualCodeString = babelisedCode.generateCode(awaitExpression, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `PublicKey.findProgramAddressSync([Buffer.from(\"game\"),playerOne.publicKey.toBuffer(),Buffer.from(gameId)],program.programId)`,\n  `PublicKey.findProgramAddressSync([Buffer.from('game'),playerOne.publicKey.toBuffer(),Buffer.from(gameId)],program.programId)`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\nAn airdrop should be requested and confirmed for `playerOne`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it' && c.arguments?.[0]?.value === 'handles ties';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst babelisedCode2 = new __helpers.Babeliser(actualCodeString, {\n  plugins: ['typescript']\n});\nconst callExpression2 = babelisedCode2.getType('CallExpression').find(c => {\n  return c.callee?.object?.object?.name === 'programProvider';\n});\nassert.exists(callExpression2, 'Airdrop should be requested for playerOne');\n```\n\nThe `setupGame` method should be called on the program.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it' && c.arguments?.[0]?.value === 'handles ties';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `await program.methods.setupGame(playerTwo.publicKey,gameId).accounts({game:gamePublicKey,playerOne:playerOne.publicKey}).signers([playerOne]).rpc()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 54\n\n### --description--\n\nWithin the `\"handles ties\"` callback, use the `play` function to play the game until the players tie each other.\n\n### --tests--\n\nThe `\"handles ties\"` callback should all `play` until a tie.\n\n```js\n// Get all tile plays in `it` callback\n// Compare to possible tie board states\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return c.callee?.name === 'it' && c.arguments?.[0]?.value === 'handles ties';\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst plays = blockStatement?.body?.filter(v => {\n  return v.expression?.argument?.callee?.name === 'play';\n});\nconst assertionCodeString = babelisedCode.generateCode({\n  type: 'BlockStatement',\n  body: plays,\n  directives: []\n});\n\nlet program,\n  gamePublicKey,\n  playerOne = 1,\n  playerTwo = 2;\nconst board = [\n  [null, null, null],\n  [null, null, null],\n  [null, null, null]\n];\nconst play = (p, g, pl, tile) => {\n  board[tile.row][tile.column] = pl;\n};\nawait eval(`(async () => {${assertionCodeString}})()`);\n\nfunction checkTie(board) {\n  for (const row of board) {\n    for (const column of row) {\n      if (column === null) {\n        return false;\n      }\n    }\n  }\n\n  for (let i = 0; i < board.length; i++) {\n    if (board[i][0] === board[i][1] && board[i][1] === board[i][2]) {\n      return false;\n    }\n    if (board[0][i] === board[1][i] && board[1][i] === board[2][i]) {\n      return false;\n    }\n    if (board[0][0] === board[1][1] && board[1][1] === board[2][2]) {\n      return false;\n    }\n    if (board[0][2] === board[1][1] && board[1][1] === board[2][0]) {\n      return false;\n    }\n  }\n  return true;\n}\n\nassert.isTrue(checkTie(board));\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 55\n\n### --description--\n\nRun the tests to confirm everything is still working.\n\n### --tests--\n\nThe `anchor test --skip-local-validator` tests should pass ✅.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.include(terminalOutput, '3 passing');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 56\n\n### --description--\n\nWithin the `describe` callback, create a new `it` call with a title of `\"handles invalid plays\"`, and an asynchroneous callback.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have a `describe` callback that calls `it` with a title of `\"handles invalid plays\"`.\n\n```js\nconst callExpressions = babelisedCode.getType('CallExpression').filter(c => {\n  return c.callee?.name === 'it';\n});\nassert.include(\n  callExpressions.map(c => c.arguments?.[0]?.value),\n  'handles invalid plays'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 57\n\n### --description--\n\nWithin the `\"handles invalid plays\"` callback, set up a new game similarly to the previous tests, but with a `gameId` of `\"game-4\"`.\n\n### --tests--\n\nA `playerOne` variable should be declared and assigned `Keypair.generate()`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'playerOne';\n});\nassert.exists(variableDeclaration, 'A variable named `playerOne` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const playerOne=Keypair.generate()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\nA `playerTwo` variable should be declared and assigned `Keypair.generate()`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'playerTwo';\n});\nassert.exists(variableDeclaration, 'A variable named `playerTwo` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const playerTwo=Keypair.generate()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\nA `gameId` variable should be declared and assigned `\"game-4\"`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.name === 'gameId';\n});\nassert.exists(variableDeclaration, 'A variable named `gameId` should exist');\nconst actualCodeString = babelisedCode.generateCode(variableDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [`const gameId=\"game-4\"`, `const gameId='game-4'`];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\nA `gamePublicKey` variable should be destructed from a `PublicKey.findProgramAddressSync` call.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst variableDeclaration = blockStatement?.body?.find(v => {\n  return v?.declarations?.[0]?.id?.elements?.[0]?.name === 'gamePublicKey';\n});\nassert.exists(\n  variableDeclaration,\n  'A variable named `gamePublicKey` should be destructured'\n);\nconst awaitExpression = variableDeclaration?.declarations?.[0]?.init;\nconst actualCodeString = babelisedCode.generateCode(awaitExpression, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `PublicKey.findProgramAddressSync([Buffer.from(\"game\"),playerOne.publicKey.toBuffer(),Buffer.from(gameId)],program.programId)`,\n  `PublicKey.findProgramAddressSync([Buffer.from('game'),playerOne.publicKey.toBuffer(),Buffer.from(gameId)],program.programId)`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\nAn airdrop should be requested and confirmed for `playerOne`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst babelisedCode2 = new __helpers.Babeliser(actualCodeString, {\n  plugins: ['typescript']\n});\nconst callExpression2 = babelisedCode2.getType('CallExpression').find(c => {\n  return c.callee?.object?.object?.name === 'programProvider';\n});\nassert.exists(callExpression2, 'Airdrop should be requested for playerOne');\n```\n\nThe `setupGame` method should be called on the program.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeString = `await program.methods.setupGame(playerTwo.publicKey,gameId).accounts({game:gamePublicKey,playerOne:playerOne.publicKey}).signers([playerOne]).rpc()`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 58\n\n### --description--\n\nWithin the `\"handles invalid plays\"` callback, use the `play` function to have player one place an `X` in the top left corner.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `await play(program, gamePublicKey, playerOne, {row:0,column:0}, 2, {active:{}}, [[{x:{}},null,null],[null,null,null],[null,null,null]]);`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst actualCodeString = babelisedCode.generateCode(blockStatement, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `await play(program,gamePublicKey,playerOne,{row:0,column:0},2,{active:{}},[[{x:{}},null,null],[null,null,null],[null,null,null]])`,\n  `await play(program,gamePublicKey,playerOne,{column:0,row:0},2,{active:{}},[[{x:{}},null,null],[null,null,null],[null,null,null]])`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 59\n\n### --description--\n\nTo test whether the `play` instruction handle correctly throws an error when a player tries to play out of turn, wrap a `play` call in a `try...catch` block.\n\nThe `try` should throw an error if the `play` call does not throw an error.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have `await play(program, gamePublicKey, playerOne, {row:1,column:0}, 2, {active:{}}, [[{x:{}},null,null],[null,null,null],[null,null,null]]);` in the `try` block.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatement = blockStatement?.body?.find(v => {\n  return v?.block?.type === 'TryStatement';\n});\nassert.exists(tryStatement, 'A try statement should exist');\nconst actualCodeString = babelisedCode.generateCode(tryStatement, {\n  compact: true\n});\nconst expectedCodeString = `try{await play(program,gamePublicKey,playerOne,{row:1,column:0},2,{active:{}},[[{x:{}},null,null],[null,null,null],[null,null,null]])}catch(e){}`;\nassert.deepInclude(actualCodeString, expectedCodeString);\n```\n\n`tests/tic-tac-toe.ts` should have a `try` block that throws if `play` does not throw.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatement = blockStatement?.body?.find(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryBlock = tryStatement?.block;\nconst actualCodeString = babelisedCode.generateCode(tryBlock, {\n  compact: true\n});\n\nconst chai = await import('chai');\nconst { expect } = chai;\nconst play = () => {};\nlet program, gamePublicKey, playerOne, playerTwo;\ntry {\n  await eval(`(async () => {\n    ${actualCodeString}\n  })()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'Try block should throw if `play` does not throw'\n  );\n}\n```\n\n## 60\n\n### --description--\n\nWithin the `catch` block, assert the caught error is an instance of `AnchorError`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have a `catch` block that asserts `e instanceof AnchorError`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatement = blockStatement?.body?.find(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst catchBlock = tryStatement?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'NotPlayersTurn',\n        number: 6003\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw with an `AnchorError`');\n}\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${'Not an AnchorError'};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'Catch block should throw without an `AnchorError`'\n  );\n}\n```\n\n`AnchorError` should be imported from `@coral-xyz/anchor`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source?.value === '@coral-xyz/anchor';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@coral-xyz/anchor` should exist'\n);\n\nconst specifierNames = importDeclaration.specifiers?.map(s => {\n  return s?.local?.name;\n});\nassert.include(\n  specifierNames,\n  'AnchorError',\n  'The `Keypair` class should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 61\n\n### --description--\n\nAnchor deserializes any Rust enum annotated with `error_code` into a JavaScript object consisting of a numeric code, and a string name matching the pascalcase enum variant.\n\nWithin the `catch` block, assert the caught error has an `error.errorCode.code` property equal to `\"NotPlayersTurn\"`, and an `error.errorCode.number` property equal to `6003`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have a `catch` block that asserts `e.error.errorCode.code === \"NotPlayersTurn\"`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatement = blockStatement?.body?.find(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst catchBlock = tryStatement?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'NotPlayersTurn',\n        number: 6003\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  __error.error.errorCode.code = 'fcc';\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\n`tests/tic-tac-toe.ts` should have a `catch` block that asserts `e.error.errorCode.number === 6003`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatement = blockStatement?.body?.find(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst catchBlock = tryStatement?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'NotPlayersTurn',\n        number: 6003\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  __error.error.errorCode.number = 6000;\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 62\n\n### --description--\n\nIf you have multiple programs, or a function involves multiple program calls, the Anchor error also provides a `program` property, which is the program that threw the error.\n\nWithin the `catch` block, assert the program that threw the error is equal to `program.programId`.\n\n### --tests--\n\n`tests/tic-tac-toe.ts` should have a `catch` block that asserts `e.program === program.programId`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatement = blockStatement?.body?.find(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst catchBlock = tryStatement?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'NotPlayersTurn',\n        number: 6003\n      },\n      comparedValues: []\n    };\n    this.program = {\n      programId: 1\n    };\n  }\n}\nlet program = { programId: 1 },\n  playerOne,\n  playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  program.programId = 2;\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 63\n\n### --description--\n\nWithin the `\"handles invalid plays\"` callback, add another `try...catch` block testing for the case where a player tries to play in a tile that is out of bounds.\n\n### --tests--\n\nThe `\"handles invalid plays\"` callback should have a second `try...catch` block.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement2 = tryStatements?.[1];\nassert.exists(tryStatement2);\n```\n\nThe `try` block should call `play` with an invalid tile.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement2 = tryStatements?.[1];\nassert.exists(tryStatement2, \"A second `try...catch` block should exist\");\nconst tryBlock = tryStatement2?.block;\nconst actualCodeString = babelisedCode.generateCode(tryBlock, {\n  compact: true\n});\n\nconst chai = await import('chai');\nconst { expect } = chai;\nconst play = (p,g,pl,t) => {\n  const {row, column} = t;\n  assert.exists(row, \"`row` should exist);\n  assert.exists(column, \"`column` should exist\");\n  const isRowOut = row >= 3 || row < 0;\n  const isColumnOut = column >= 3 || column < 0;\n  assert(isRowOut || isColumnOut, \"`row` and/or `column` should be out of bounds\");\n};\nlet program, gamePublicKey, playerOne, playerTwo;\nawait eval(`(async () => {\n  ${actualCodeString}\n})()`);\n```\n\nThe `try` block should throw if the `play` call does not throw.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement2 = tryStatements?.[1];\nassert.exists(tryStatement2, 'A second `try...catch` block should exist');\nconst tryBlock = tryStatement2?.block;\nconst actualCodeString = babelisedCode.generateCode(tryBlock, {\n  compact: true\n});\n\nconst chai = await import('chai');\nconst { expect } = chai;\nconst play = () => {};\nlet program, gamePublicKey, playerOne, playerTwo;\ntry {\n  await eval(`(async () => {\n    ${actualCodeString}\n  })()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'The `try` block should throw, if `play` does not'\n  );\n}\n```\n\nThe `catch` block should assert the error is an instance of `AnchorError`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement2 = tryStatements?.[1];\nassert.exists(tryStatement2, 'A second `try...catch` block should exist');\nconst catchBlock = tryStatement2?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement2?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'TileOutOfBounds',\n        number: 6000\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw with an `AnchorError`');\n}\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${'Not an AnchorError'};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'Catch block should throw without an `AnchorError`'\n  );\n}\n```\n\nThe `catch` block should assert the error has an `error.errorCode.number` property equal to `6000`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement2 = tryStatements?.[1];\nassert.exists(tryStatement2, 'A second `try...catch` block should exist');\nconst catchBlock = tryStatement2?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement2?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'TileOutOfBounds',\n        number: 6000\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  __error.error.errorCode.number = 6003;\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\nThe `catch` block should assert the error has an `error.errorCode.code` property equal to `\"TileOutOfBounds\"`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement2 = tryStatements?.[1];\nassert.exists(tryStatement2, 'A second `try...catch` block should exist');\nconst catchBlock = tryStatement2?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement2?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'TileOutOfBounds',\n        number: 6000\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  __error.error.errorCode.code = 'fcc';\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 64\n\n### --description--\n\nWithin the `\"handles invalid plays\"` callback, add another `try...catch` block testing for the case where a player tries to play in a tile that is already occupied.\n\n### --tests--\n\nThe `\"handles invalid plays\"` callback should have a third `try...catch` block.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement3 = tryStatements?.[2];\nassert.exists(tryStatement3);\n```\n\nThe `try` block should call `play` with a tile position that is already occupied.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement3 = tryStatements?.[2];\nassert.exists(tryStatement3);\nassert.exists(tryStatement3, \"A third `try...catch` block should exist\");\nconst tryBlock = tryStatement3?.block;\nconst actualCodeString = babelisedCode.generateCode(tryBlock, {\n  compact: true\n});\n\nconst chai = await import('chai');\nconst { expect } = chai;\nconst play = (p,g,pl,t) => {\n  const {row, column} = t;\n  assert.exists(row, \"`row` should exist);\n  assert.exists(column, \"`column` should exist\");\n  const isRowOut = row === 0 || row  === 1;\n  const isColumnOut = column === 0;\n  assert(isRowOut && isColumnOut, \"`row` and `column` should already be occupied\");\n};\nlet program, gamePublicKey, playerOne, playerTwo;\nawait eval(`(async () => {\n  ${actualCodeString}\n})()`);\n```\n\nThe `try` block should throw if the `play` call does not throw.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement3 = tryStatements?.[2];\nassert.exists(tryStatement3);\nassert.exists(tryStatement3, 'A third `try...catch` block should exist');\nconst tryBlock = tryStatement3?.block;\nconst actualCodeString = babelisedCode.generateCode(tryBlock, {\n  compact: true\n});\n\nconst chai = await import('chai');\nconst { expect } = chai;\nconst play = () => {};\nlet program, gamePublicKey, playerOne, playerTwo;\ntry {\n  await eval(`(async () => {\n    ${actualCodeString}\n  })()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'The `try` block should throw, if `play` does not'\n  );\n}\n```\n\nThe `catch` block should assert the error is an instance of `AnchorError`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement3 = tryStatements?.[2];\nconst catchBlock = tryStatement3?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement3?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'TileAlreadySet',\n        number: 6001\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw with an `AnchorError`');\n}\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${'Not an AnchorError'};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'Catch block should throw without an `AnchorError`'\n  );\n}\n```\n\nThe `catch` block should assert the error has an `error.errorCode.number` property equal to `6001`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement3 = tryStatements?.[2];\nassert.exists(tryStatement3, 'A third `try...catch` block should exist');\nconst catchBlock = tryStatement3?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement3?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'TileAlreadySet',\n        number: 6001\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  __error.error.errorCode.number = 6003;\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\nThe `catch` block should assert the error has an `error.errorCode.code` property equal to `\"TileAlreadySet\"`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement3 = tryStatements?.[2];\nassert.exists(tryStatement3, 'A third `try...catch` block should exist');\nconst catchBlock = tryStatement3?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement3?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'TileAlreadySet',\n        number: 6001\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  __error.error.errorCode.code = 'fcc';\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 65\n\n### --description--\n\nWithin the `\"handles invalid plays\"` callback, call the `play` function as many times as necessary to have a player win the game.\n\n### --tests--\n\nAll `play` calls outwith `try...catch` blocks should result in a win.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst plays = blockStatement?.body?.filter(v => {\n  return v.expression?.argument?.callee?.name === 'play';\n});\nconst assertionCodeString = babelisedCode.generateCode({\n  type: 'BlockStatement',\n  body: plays,\n  directives: []\n});\n\nlet program,\n  gamePublicKey,\n  playerOne = 1,\n  playerTwo = 4;\nconst board = [\n  [null, null, null],\n  [null, null, null],\n  [null, null, null]\n];\nconst play = (p, g, pl, tile) => {\n  board[tile.row][tile.column] = pl;\n};\nawait eval(`(async () => {${assertionCodeString}})()`);\n\nfunction checkWin(board) {\n  // All rows\n  board.forEach(r => {\n    const rowSum = r.reduce((acc, curr) => curr && acc + curr, 0);\n    if (rowSum === playerOne * 3 || rowSum === playerTwo * 3) {\n      return true;\n    }\n  });\n  // All columns\n  for (let i = 0; i < board.length; i++) {\n    const columnSum = board[0][i] + board[1][i] + board[2][i];\n    if (columnSum === playerOne * 3 || columnSum === playerTwo * 3) {\n      return true;\n    }\n  }\n\n  for (let i = 0; i < board.length; i++) {\n    if (board[i][0] === board[i][1] && board[i][1] === board[i][2]) {\n      return true;\n    }\n    if (board[0][i] === board[1][i] && board[1][i] === board[2][i]) {\n      return true;\n    }\n    if (board[0][0] === board[1][1] && board[1][1] === board[2][2]) {\n      return true;\n    }\n    if (board[0][2] === board[1][1] && board[1][1] === board[2][0]) {\n      return true;\n    }\n  }\n  return false;\n}\n\nassert.isTrue(checkWin(board), `Found board of: ${JSON.stringify(board)}`);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 66\n\n### --description--\n\nWithin the `\"handles invalid plays\"` callback, add another `try...catch` block testing for the case where a player tries to play after the game is over.\n\n### --tests--\n\nThe `\"handles invalid plays\"` callback should have a fourth `try...catch` block.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement4 = tryStatements?.[3];\nassert.exists(tryStatement4);\n```\n\nThe `try` block should call `play` after the game is over.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement4 = tryStatements?.[3];\nassert.exists(tryStatement4);\nassert.exists(tryStatement4, \"A fourth `try...catch` block should exist\");\nconst tryBlock = tryStatement4?.block;\nconst actualCodeString = babelisedCode.generateCode(tryBlock, {\n  compact: true\n});\n\nconst chai = await import('chai');\nconst { expect } = chai;\nconst play = (p,g,pl,t) => {\n  const {row, column} = t;\n  assert.exists(row, \"`row` should exist);\n  assert.exists(column, \"`column` should exist\");\n};\nlet program, gamePublicKey, playerOne, playerTwo;\nawait eval(`(async () => {\n  ${actualCodeString}\n})()`);\n```\n\nThe `try` block should throw if the `play` call does not throw.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement4 = tryStatements?.[3];\nassert.exists(tryStatement4);\nassert.exists(tryStatement4, 'A fourth `try...catch` block should exist');\nconst tryBlock = tryStatement4?.block;\nconst actualCodeString = babelisedCode.generateCode(tryBlock, {\n  compact: true\n});\n\nconst chai = await import('chai');\nconst { expect } = chai;\nconst play = () => {};\nlet program, gamePublicKey, playerOne, playerTwo;\ntry {\n  await eval(`(async () => {\n    ${actualCodeString}\n  })()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'The `try` block should throw, if `play` does not'\n  );\n}\n```\n\nThe `catch` block should assert the error is an instance of `AnchorError`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement4 = tryStatements?.[3];\nconst catchBlock = tryStatement4?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement4?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'TileAlreadySet',\n        number: 6001\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw with an `AnchorError`');\n}\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${'Not an AnchorError'};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(\n    e.message,\n    'fcc',\n    'Catch block should throw without an `AnchorError`'\n  );\n}\n```\n\nThe `catch` block should assert the error has an `error.errorCode.number` property equal to `6002`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement4 = tryStatements?.[3];\nassert.exists(tryStatement4, 'A fourth `try...catch` block should exist');\nconst catchBlock = tryStatement4?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement4?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'GameAlreadyOver',\n        number: 6002\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  __error.error.errorCode.number = 6003;\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\nThe `catch` block should assert the error has an `error.errorCode.code` property equal to `\"GameAlreadyOver\"`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.name === 'it' &&\n    c.arguments?.[0]?.value === 'handles invalid plays'\n  );\n});\nconst blockStatement = callExpression?.arguments?.[1]?.body;\nconst tryStatements = blockStatement?.body?.filter(v => {\n  return v?.block?.type === 'TryStatement';\n});\nconst tryStatement4 = tryStatements?.[3];\nassert.exists(tryStatement4, 'A fourth `try...catch` block should exist');\nconst catchBlock = tryStatement4?.handler?.body;\nconst actualCodeString = babelisedCode.generateCode(catchBlock, {\n  compact: true\n});\nconst errorParameterName = tryStatement4?.handler?.param?.name;\n\nconst chai = await import('chai');\nconst { expect } = chai;\nclass AnchorError {\n  constructor() {\n    this.error = {\n      errorCode: {\n        code: 'GameAlreadyOver',\n        number: 6002\n      },\n      comparedValues: []\n    };\n  }\n}\nlet program, playerOne, playerTwo;\nlet __error = new AnchorError();\ntry {\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n} catch (e) {\n  assert.fail(e, 'Catch block should not throw');\n}\ntry {\n  __error.error.errorCode.code = 'fcc';\n  await eval(`(async () => {\n    let ${errorParameterName} = ${__error};\n  ${actualCodeString}\n})()`);\n  assert.fail('fcc');\n} catch (e) {\n  assert.notEqual(e.message, 'fcc', 'Catch block should throw');\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/tests/tic-tac-toe.ts`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 67\n\n### --description--\n\n**Summary**\n\n- The Anchor `workspace` contains all the programs in your project\n- Each program has a `methods` property containing all the program's instructions\n  - The `accounts` method is used to pass in all the required public keys for the chained instruction\n  - The `signers` method is used to pass in all the keypairs for the chained instruction\n  - The `rpc` method sends the transaction to the Solana cluster\n- Each program has an `account` property containing all the program's accounts\n  - An account's data can be fetched with: `program.account.<ACCOUNT_NAME>.fetch(<ACCOUNT_PUBKEY>)`\n- Program Derived Addresses are used to programmatically generate a public key for an account\n  - The `PublicKey.findProgramAddressSync` method can be used to derive a PDA\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-build-a-client-side-app-part-1.md",
    "content": "# Solana - Learn How to Build a Client-Side App: Part 1\n\n## 1\n\n### --description--\n\nPreviously, you built, tested, and deployed a Tic-Tac-Toe program. In this project, you will learn how to write a client-side application that interacts with your program API.\n\nOpen a new terminal, and cd into the `learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/` directory.\n\n### --tests--\n\nYou should be in the `learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 2\n\n### --description--\n\nAdd the correct program public key to the `programs/tic-tac-toe/src/lib.rs` and `Anchor.toml` files.\n\n### --tests--\n\nThe `lib.rs` file should contain the program id within the `declare_id!()` call.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/programs/tic-tac-toe/src/lib.rs`\n);\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/tic-tac-toe`\n);\nconst expectedProgramId = stdout.match(/[^\\s]{44}/)?.[0];\nconst actualProgramId = librs.match(/declare_id!\\(\"([^\\)]+)\"\\)/)?.[1];\nassert.equal(actualProgramId, expectedProgramId);\n```\n\nThe `Anchor.toml` file should contain the program id as the value for the `tic_tac_toe` key.\n\n```js\nconst toml = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/Anchor.toml`\n);\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/tic-tac-toe`\n);\nconst expectedProgramId = stdout.match(/[^\\s]{44}/)?.[0];\nconst actualProgramId = toml.match(/tic_tac_toe = \"([^\\\"]+)\"/)?.[1];\nassert.equal(actualProgramId, expectedProgramId);\n```\n\n## 3\n\n### --description--\n\nBuild the program.\n\n### --tests--\n\nThe program should successfully build.\n\n```js\nconst { access, constants } = await import('fs/promises');\ntry {\n  await access(\n    `${project.dashedName}/tic-tac-toe/target/deploy/tic_tac_toe.so`,\n    constants.F_OK\n  );\n} catch (e) {\n  assert.fail(\n    `Try running \\`anchor build\\` in the \\`tic-tac-toe\\` directory:\\n\\n${JSON.stringify(\n      e,\n      null,\n      2\n    )}`\n  );\n}\n```\n\n## 4\n\n### --description--\n\nYou have been started out with boilerplate client-side code in the `app/` directory.\n\nIn the terminal, change into the `app/` directory.\n\n### --tests--\n\nYour current working directory should be `learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/tic-tac-toe/app/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 5\n\n### --description--\n\nWithin `app/`, create a `web3.js` file for all of your Solana and Anchor code.\n\n### --tests--\n\nYou should have a `learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/web3.js` file.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(join(project.dashedName, 'tic-tac-toe/app/web3.js'));\n```\n\n## 6\n\n### --description--\n\nWithin `web3.js`, export a named variable `PROGRAM_ID`, and set it to the public key of your program.\n\n### --tests--\n\nYou should have `export const PROGRAM_ID = new PublicKey(...)`.\n\n```js\nconst codeString = await __helpers.getFile(\n  `${project.dashedName}/tic-tac-toe/app/web3.js`\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst actualCodeString = babelisedCode.generateCode(babelisedCode.parsedCode, {\n  compact: true\n});\n\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/tic-tac-toe`\n);\nconst expectedProgramId = stdout.match(/[^\\s]{44}/)?.[0];\nconst expectedCodeStrings = [\n  `export const PROGRAM_ID=new PublicKey('${expectedProgramId}');`,\n  `export const PROGRAM_ID=new PublicKey(\"${expectedProgramId}\");`\n];\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 7\n\n### --description--\n\nWithin `app/` use `yarn` to install `@solana/web3.js@1.78`.\n\n### --tests--\n\nYou should install version `1.78` of the `@solana/web3.js` package.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    join(project.dashedName, 'tic-tac-toe/app/package.json')\n  )\n);\nassert.property(\n  packageJson.dependencies,\n  '@solana/web3.js',\n  'The `package.json` file should have a `@solana/web3.js` dependency.'\n);\nassert.equal(\n  packageJson.dependencies['@solana/web3.js'],\n  '1.78',\n  'Try running `yarn add @solana/web3.js@1.78` in the terminal.'\n);\n```\n\n## 8\n\n### --description--\n\nWithin `web3.js`, import the `PublicKey` class.\n\n### --tests--\n\nYou should have `import { PublicKey } from \"@solana/web3.js\"`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `@solana/web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'PublicKey',\n  '`PublicKey` should be imported from `@solana/web3.js`'\n);\n```\n\n## 9\n\n### --description--\n\nGenerally, a browser-based client app follows the following workflow to interact with Solana programs:\n\n1. Client connects to user wallet\n2. Client builds a transaction\n3. User signs the transaction\n4. Client sends transaction to network\n\nWithin `web3.js`, export a named function `connectWallet`.\n\n### --tests--\n\nYou should have `export function connectWallet() {}`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst exportDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    return e.declaration?.id?.name === 'connectWallet';\n  });\nassert.exists(exportDeclaration, 'You should export `connectWallet`');\n```\n\n## 10\n\n### --description--\n\nFor your Tic-Tac-Toe program, the first transaction to build is the `setup_game` instruction.\n\nWithin, `web3.js`, export a named, async function `startGame`.\n\n### --tests--\n\nYou should have `export async function startGame() {}`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst exportDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    return e.declaration?.id?.name === 'startGame';\n  });\nassert.exists(exportDeclaration, 'You should export `startGame`');\nassert.isTrue(exportDeclaration.declaration.async);\n```\n\n## 11\n\n### --description--\n\nAnother transaction to build is the `play` instruction.\n\nWithin `web3.js`, export a named, async function `handlePlay` that expects an `id` argument.\n\n### --tests--\n\nYou should have `export async function handlePlay(id) {}`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst exportDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    return e.declaration?.id?.name === 'handlePlay';\n  });\nassert.exists(exportDeclaration, 'You should export `handlePlay`');\n```\n\n## 12\n\n### --description--\n\nIn order to send transactions to the network, the client has to connect to the network.\n\nWithin `web3.js`, declare a variable named `connection`, and set it to connect with the default port of a local validator.\n\n### --tests--\n\nYou should have `const connection = new Connection(\"http://localhost:8899\")`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'You should declare a variable named `connection`'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee.name,\n  'Connection',\n  'You should initialise `connection` with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments[0].value,\n  'http://localhost:8899',\n  \"You should create a new connection with `new Connection('http://localhost:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `@solana/web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'Connection',\n  '`Connection` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 13\n\n### --description--\n\nEach program game state account requires a game id. This id is used to find the program address on the <dfn title=\"\">ed25519 curve</dfn>.\n\nWithin `web3.js`, export a named function `deriveGamePublicKey` that expects two arguments: `playerOnePublicKey` and `gameId`.\n\n### --tests--\n\nYou should have `export function deriveGamePublicKey(playerOnePublicKey, gameId) {}`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst exportDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    return e.declaration?.id?.name === 'deriveGamePublicKey';\n  });\nassert.exists(exportDeclaration, 'You should export `deriveGamePublicKey`');\n```\n\n## 14\n\n### --description--\n\nTwo players are required.\n\nWithin `tic-tac-toe/`, create two keypairs: `player-one.json` and `player-two.json`\n\n### --tests--\n\nYou should have a `learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/player-one.json` file.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(\n  join(project.dashedName, 'tic-tac-toe/player-one.json'),\n  constants.F_OK\n);\n```\n\nThe `player-one.json` file should contain a valid keypair.\n\n```js\nconst keyjson = JSON.parse(\n  await __helpers.getFile(\n    join(project.dashedName, 'tic-tac-toe/player-one.json')\n  )\n);\ntry {\n  const { Keypair } = await import('@solana/web3.js');\n  const keypair = Keypair.fromSecretKey(new Uint8Array(keyjson));\n} catch (e) {\n  assert.fail(\n    `Try running \\`solana-keygen new --outfile player-one.json\\` in the \\`tic-tac-toe\\` directory:\\n\\n${JSON.stringify(\n      e,\n      null,\n      2\n    )}`\n  );\n}\n```\n\nYou should have a `learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/player-two.json` file.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(\n  join(project.dashedName, 'tic-tac-toe/player-two.json'),\n  constants.F_OK\n);\n```\n\nThe `player-two.json` file should contain a valid keypair.\n\n```js\nconst keyjson = JSON.parse(\n  await __helpers.getFile(\n    join(project.dashedName, 'tic-tac-toe/player-two.json')\n  )\n);\ntry {\n  const { Keypair } = await import('@solana/web3.js');\n  const keypair = Keypair.fromSecretKey(new Uint8Array(keyjson));\n} catch (e) {\n  assert.fail(\n    `Try running \\`solana-keygen new --outfile player-two.json\\` in the \\`tic-tac-toe\\` directory:\\n\\n${JSON.stringify(\n      e,\n      null,\n      2\n    )}`\n  );\n}\n```\n\n## 15\n\n### --description--\n\nWithin `app/`, run `yarn dev` in your terminal to serve your app. Open your browser to the localhost shown in the output.\n\nPay attention to the required inputs for the game.\n\n### --tests--\n\nYou should have your app served at `http://localhost:5173`.\n\n```js\nconst response = await fetch('http://localhost:5173');\nassert.equal(response.status, 200, 'The server should be running.');\n```\n\n## 16\n\n### --description--\n\nTypically, connecting to a wallet involves using browser API to connect to a wallet browser extension. _You will do this in the next project._\n\nFor this project, you will directly input the keypair array. For convienience, the UI includes the keypairs you created in the previous step for easy copy-pasting, which is then stored in the browser's session storage.\n\nWithin the `connectWallet` function, create a keypair from the session storage:\n\n```js\nconst keypairStr = sessionStorage.getItem('keypair');\nconst keypairArr = JSON.parse(keypairStr);\nconst uint8Arr = new Uint8Array(keypairArr);\nconst keypair = Keypair.fromSecretKey(uint8Arr);\n```\n\n### --tests--\n\nYou should have the above code within the `connectWallet` function.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'connectWallet');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `connectWallet`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\n\nconst expectedCodeStrings = [\n  `const keypairStr=sessionStorage.getItem('keypair');const keypairArr=JSON.parse(keypairStr);const uint8Arr=new Uint8Array(keypairArr);const keypair=Keypair.fromSecretKey(uint8Arr);`,\n  `const keypairStr=sessionStorage.getItem(\"keypair\");const keypairArr=JSON.parse(keypairStr);const uint8Arr=new Uint8Array(keypairArr);const keypair=Keypair.fromSecretKey(uint8Arr);`\n];\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\nYou should import `Keypair` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `@solana/web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'Keypair',\n  '`Keypair` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 17\n\n### --description--\n\nTypically, wallet vendors provide interfaces and plugins to interact with their wallet. For this project, a `Wallet` class is provided to simulate the wallet interface.\n\nWithin the `connectWallet` function, create a new `Wallet` instance assigned to a `wallet` variable.\n\n### --tests--\n\nYou should have `const wallet = new Wallet(keypair);`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'connectWallet');\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedString = `const wallet=new Wallet(keypair);`;\nassert.include(actualCodeString, expectedString);\n```\n\nYou should import `Wallet` from `./wallet.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `./wallet.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'Wallet',\n  '`Wallet` should be imported from `./wallet.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 18\n\n### --description--\n\nAnchor expects a `Provider` instance to be passed to the `Program` constructor. The `Provider` instance is used to sign transactions based on the provided wallet.\n\nWithin the `connectWallet` function, declare a `provider` variable set to:\n\n```js\nconst provider = new AnchorProvider(connection, wallet, {});\n```\n\n**Note:** The empty `{}` options property prevents the default `AnchorProvider` options from being used which only work on Nodejs clients.\n\n### --tests--\n\nYou should have the above code within the `connectWallet` function.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'connectWallet');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `connectWallet`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const provider=new AnchorProvider(connection,wallet,{});`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `AnchorProvider` from `@coral-xyz/anchor`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@coral-xyz/anchor';\n});\nassert.exists(importDeclaration, 'You should import from `@coral-xyz/anchor`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'AnchorProvider',\n  '`AnchorProvider` should be imported from `@coral-xyz/anchor`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 19\n\n### --description--\n\nInstall the `@coral-xyz/anchor@0.28` package.\n\n### --tests--\n\nYou should install version `0.28` of the `@coral-xyz/anchor` package.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    join(project.dashedName, 'tic-tac-toe/app/package.json')\n  )\n);\nassert.property(\n  packageJson.dependencies,\n  '@coral-xyz/anchor',\n  'The `package.json` file should have a `@coral-xyz/anchor` dependency.'\n);\nassert.equal(\n  packageJson.dependencies['@coral-xyz/anchor'],\n  '0.28',\n  'Try running `yarn add @coral-xyz/anchor@0.28` in the terminal.'\n);\n```\n\n## 20\n\n### --description--\n\nTo tell Anchor which provider to use for all transactions, use the `setProvider` function from `@coral-xyz/anchor` and pass it the `provider` variable.\n\n### --tests--\n\nYou should have `setProvider(provider)` within the `connectWallet` function.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'connectWallet');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `connectWallet`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `setProvider(provider);`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `setProvider` from `@coral-xyz/anchor`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@coral-xyz/anchor';\n});\nassert.exists(importDeclaration, 'You should import from `@coral-xyz/anchor`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'setProvider',\n  '`setProvider` should be imported from `@coral-xyz/anchor`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 21\n\n### --description--\n\nWithin the `connectWallet` function, declare a `program` variable set to:\n\n```js\nnew Program(IDL, PROGRAM_ID, provider);\n```\n\n### --tests--\n\nYou should have the above code within the `connectWallet` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'connectWallet');\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedString = `const program=new Program(IDL,PROGRAM_ID,provider);`;\nassert.include(actualCodeString, expectedString);\n```\n\n## 22\n\n### --description--\n\nImport the `IDL` generated by Anchor.\n\n### --tests--\n\nYou should have `import { IDL } from \"../target/types/tic_tac_toe\";`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '../target/types/tic_tac_toe';\n});\nassert.exists(importDeclaration, 'You should import from `../target/types`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'IDL', '`IDL` should be imported');\n```\n\n## 23\n\n### --description--\n\nTo make the program available globally, attach it to the `window`.\n\n### --tests--\n\nYou should have `window.program = program` in `connectWallet`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst actualCodeString = babelisedCode.generateCode(babelisedCode.parsedCode, {\n  compact: true\n});\nconst expectedCodeString = `window.program=program;`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 24\n\n### --description--\n\nNow, within the `app/index.js` file, call the `connectWallet` function in the `connectWalletBtnEl` event listener callback at the indicated comment.\n\n### --tests--\n\nYou should add `connectWallet()` below the `// TODO: Connect to wallet` comment.\n\n```js\nconst callExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => c.callee.object.name === 'connectWalletBtnEl');\nconst tryStatementBlock = callExpression.arguments[1]?.body?.body?.find(\n  s => s.type === 'TryStatement'\n)?.block;\nconst actualCodeString = babelisedCode.generateCode(tryStatementBlock, {\n  compact: true\n});\nconst expectedCodeString = `connectWallet()`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `connectWallet` from `./web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `./web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'connectWallet',\n  '`connectWallet` should be imported'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/index.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 25\n\n### --description--\n\nWithin the `startGame` function in `web3.js`, declare a `gameId` variable set to the `\"gameId\"` session storage item.\n\n### --tests--\n\nYou should have `const gameId = sessionStorage.getItem(\"gameId\");` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `const gameId=sessionStorage.getItem(\"gameId\")`,\n  `const gameId=sessionStorage.getItem('gameId')`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 26\n\n### --description--\n\nWithin the `startGame` function, declare a `playerOnePublicKey` variable set to the `\"playerOnePublicKey\"` session storage item.\n\n### --tests--\n\nYou should have `const playerOnePublicKey = sessionStorage.getItem(\"playerOnePublicKey\");` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `const playerOnePublicKey=sessionStorage.getItem(\"playerOnePublicKey\")`,\n  `const playerOnePublicKey=sessionStorage.getItem('playerOnePublicKey')`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 27\n\n### --description--\n\nWithin the `startGame` function, declare a `playerTwoPublicKey` variable set to the `\"playerTwoPublicKey\"` session storage item.\n\n### --tests--\n\nYou should have `const playerTwoPublicKey = sessionStorage.getItem(\"playerTwoPublicKey\");` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `const playerTwoPublicKey=sessionStorage.getItem(\"playerTwoPublicKey\")`,\n  `const playerTwoPublicKey=sessionStorage.getItem('playerTwoPublicKey')`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 28\n\n### --description--\n\nWithin the `startGame` function, declare a `gamePublicKey` variable set to the result of calling the `deriveGamePublicKey` function with the `playerOnePublicKey`, `gameId`, and `PROGRAM_ID` variables.\n\n### --tests--\n\nYou should have `const gamePublicKey = deriveGamePublicKey(playerOnePublicKey, gameId, PROGRAM_ID);` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `const gamePublicKey=deriveGamePublicKey(playerOnePublicKey,gameId,PROGRAM_ID)`,\n  `const gamePublicKey=deriveGamePublicKey(playerOnePublicKey,gameId,PROGRAM_ID)`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 29\n\n### --description--\n\nWithin the `startGame` function, set the `\"gamePublicKey\"` session storage item to the `gamePublicKey` variable value.\n\n### --tests--\n\nYou should have `sessionStorage.setItem(\"gamePublicKey\", gamePublicKey.toString());` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `sessionStorage.setItem(\"gamePublicKey\",gamePublicKey.toString())`,\n  `sessionStorage.setItem('gamePublicKey',gamePublicKey.toString())`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 30\n\n### --description--\n\nWithin the `startGame` function, declare a `keypairStr` variable set to the `\"keypair\"` session storage item.\n\n### --tests--\n\nYou should have `const keypairStr = sessionStorage.getItem(\"keypair\");` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `const keypairStr=sessionStorage.getItem(\"keypair\")`,\n  `const keypairStr=sessionStorage.getItem('keypair')`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 31\n\n### --description--\n\nWithin the `startGame` function, declare a `keypairArr` variable set to the result of calling `JSON.parse` with the `keypairStr` variable.\n\n### --tests--\n\nYou should have `const keypairArr = JSON.parse(keypairStr);` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `const keypairArr=JSON.parse(keypairStr)`,\n  `const keypairArr=JSON.parse(keypairStr)`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 32\n\n### --description--\n\nWithin the `startGame` function, declare a `uint8Arr` variable set to a new `Uint8Array` with the `keypairArr` variable.\n\n### --tests--\n\nYou should have `const uint8Arr = new Uint8Array(keypairArr);` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const uint8Arr=new Uint8Array(keypairArr)`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 33\n\n### --description--\n\nWithin the `startGame` function, declare a `keypair` variable set to the result of calling `Keypair.fromSecretKey` with the `uint8Arr` variable.\n\n### --tests--\n\nYou should have `const keypair = Keypair.fromSecretKey(uint8Arr);` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const keypair=Keypair.fromSecretKey(uint8Arr)`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 34\n\n### --description--\n\nWithin the `startGame` function, call the `setupGame` instruction attaching the necessary accounts and signers.\n\n### --tests--\n\nYou should have `await program.methods.setupGame(playerTwoPublicKey,gameId).accounts({player:keypair.publicKey,game:gamePublicKey}).signers([keypair]).rpc()` within the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\n\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `await program.methods.setupGame(playerTwoPublicKey,gameId).accounts({player:keypair.publicKey,game:gamePublicKey}).signers([keypair]).rpc()`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 35\n\n### --description--\n\nDeriving the public key requires converting the inputs to buffers. Also, the `@coral-xyz/anchor` internals make use of buffers for the transactions.\n\nThe browser does not have a Buffer class. Instead, you will need to install the `buffer` package, and attach it to the `window`.\n\nInstall the `buffer` package in the `app/` directory.\n\n### --tests--\n\nYou should install the `buffer` package.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    join(project.dashedName, 'tic-tac-toe/app/package.json')\n  )\n);\nassert.property(\n  packageJson.dependencies,\n  'buffer',\n  'The `package.json` file should have a `buffer` dependency.'\n);\n```\n\n## 36\n\n### --description--\n\nWithin the `web3.js` file, import the `Buffer` class named export from the `buffer` package.\n\n### --tests--\n\nYou should have `import { Buffer } from \"buffer\";` within the `web3.js` file.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === 'buffer';\n});\nassert.exists(importDeclaration, 'You should import from `buffer`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'Buffer', '`Buffer` should be imported');\n```\n\n## 37\n\n### --description--\n\nWithin the `web3.js` file, attach the `Buffer` class to the `window` using the same name.\n\n### --tests--\n\nYou should have `window.Buffer = Buffer;` within the `web3.js` file.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nassert.match(codeString, /window\\.Buffer\\s*=\\s*Buffer/);\n```\n\n## 38\n\n### --description--\n\nWithin the `deriveGamePublicKey` function, use the `PublicKey.findProgramAddressSync` function to derive the game public key from the `playerOnePublicKey` and `gameId` parameters. Return the public key.\n\n_Remember all the seeds defined for the `tic-tac-toe` program_\n\n### --tests--\n\nYou should have `return PublicKey.findProgramAddressSync([Buffer.from(\"game\"),playerOnePublicKey.toBuffer(),Buffer.from(gameId)], PROGRAM_ID)[0];` within the `deriveGamePublicKey` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'deriveGamePublicKey');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `deriveGamePublicKey`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `return PublicKey.findProgramAddressSync([Buffer.from(\"game\"),playerOnePublicKey.toBuffer(),Buffer.from(gameId)],PROGRAM_ID)[0]`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 39\n\n### --description--\n\nWithin `web3.js`, declare a an async `getGameAccount` function.\n\n### --tests--\n\nYou should have `async function getGameAccount() {}` within the `web3.js` file.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nassert.match(\n  codeString,\n  /async\\s+function\\s+getGameAccount\\s*\\(\\s*\\)\\s*\\{\\s*\\}/\n);\n```\n\n## 40\n\n### --description--\n\nWithin the `getGameAccount` function, declare a `gamePublicKey` variable set to the `\"gamePublicKey\"` session storage item passed to the `PublicKey` constructor.\n\n### --tests--\n\nYou should have `const gamePublicKey = new PublicKey(sessionStorage.getItem(\"gamePublicKey\"));` within the `getGameAccount` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'getGameAccount');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `getGameAccount`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gamePublicKey=new PublicKey(sessionStorage.getItem(\"gamePublicKey\"))`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 41\n\n### --description--\n\nWithin the `getGameAccount` function, declare a `gameData` variable set to the result of awaiting a fetch to the `program` variable's `game` account.\n\n### --tests--\n\nYou should have `const gameData = await program.account.game.fetch(gamePublicKey);` within the `getGameAccount` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'getGameAccount');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `getGameAccount`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gameData=await program.account.game.fetch(gamePublicKey)`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 42\n\n### --description--\n\n<!-- TODO: at end, add features of `turnEl.textContent = ... and playerTurnEl.textContent = ...` -->\n\nWithin the `getGameAccount` function, return the `gameData` variable.\n\n### --tests--\n\nYou should have `return gameData;` within the `getGameAccount` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'getGameAccount');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `getGameAccount`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `return gameData`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 43\n\n### --description--\n\nSeeing as the game is played by multiple players, the client needs to have the latest game state.\n\nWithin the `web3.js` file, declare and export an async `updateBoard` function.\n\n### --tests--\n\nYou should have `export async function updateBoard() {}` within the `web3.js` file.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nassert.match(\n  codeString,\n  /export\\s+async\\s+function\\s+updateBoard\\s*\\(\\s*\\)\\s*\\{\\s*\\}/\n);\n```\n\n## 44\n\n### --description--\n\nWithin the `updateBoard` function, declare a `gameData` variable set to the result of calling the `getGameAccount` function.\n\n### --tests--\n\nYou should have `const gameData = await getGameAccount();` within the `updateBoard` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'updateBoard');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `updateBoard`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gameData=await getGameAccount()`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 45\n\n### --description--\n\nWithin the `updateBoard` function, declare a `board` variable set to the `gameData.board` property.\n\n### --tests--\n\nYou should have `const board = gameData.board;` within the `updateBoard` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'updateBoard');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `updateBoard`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const board=gameData.board`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 46\n\n### --description--\n\nA utility function has been provided that takes the `board` array, and sets the HTML elements to the correct values.\n\nWithin the `web3.js` file, import the `setTiles` function from `./utils.js`, and call it within the `updateBoard` function with the `board` variable.\n\n### --tests--\n\nYou should have `setTiles(board);` within the `updateBoard` function.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'updateBoard');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `updateBoard`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `setTiles(board)`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `setTiles` from `./utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'You should import from `./utils.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'setTiles', '`setTiles` should be imported');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 47\n\n### --description--\n\nAt the end of the `startGame` function, call the `updateBoard` function to ensure after the play, the board is updated.\n\n### --tests--\n\nYou should have `await updateBoard();` at the end of the `startGame` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'startGame');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `startGame`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `await updateBoard()`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 48\n\n### --description--\n\nWithin the `index.js` file, in the `startGameBtnEl` event listener callback, call the `startGame` function at the indicated comment.\n\n### --tests--\n\nYou should have `await startGame();` below `// TODO: Create a new game`.\n\n```js\nconst callExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => c.callee?.object?.name === 'startGameBtnEl');\nconst tryStatementBlock = callExpression.arguments[1]?.body?.body?.find(\n  s => s.type === 'TryStatement'\n)?.block;\nconst actualCodeString = babelisedCode.generateCode(tryStatementBlock, {\n  compact: true\n});\nconst expectedCodeString = `await startGame()`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `startGame` from `./web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `./web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'startGame', '`startGame` should be imported');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/index.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 49\n\n### --description--\n\nWithin the `index.js` file, in the `joinGameBtnEl` event listener callback, call the `updateBoard` function at the indicated comment.\n\n### --tests--\n\nYou should have `await updateBoard();` below `// TODO: Join an existing game`.\n\n```js\nconst callExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => c.callee.object?.name === 'joinGameBtnEl');\nconst tryStatementBlock = callExpression.arguments[1]?.body?.body?.find(\n  s => s.type === 'TryStatement'\n)?.block;\nconst actualCodeString = babelisedCode.generateCode(tryStatementBlock, {\n  compact: true\n});\nconst expectedCodeString = `updateBoard()`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `updateBoard` from `./web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `./web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'updateBoard',\n  '`updateBoard` should be imported'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/index.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 50\n\n### --description--\n\nWithin the `index.js` file, in the `DOMContentLoaded` event listener callback, call the `updateBoard` function at the indicated comment only if the `\"gamePublicKey\"` session storage item and `program` exist.\n\n### --tests--\n\nYou should have `if (program && sessionStorage.getItem(\"gamePublicKey\")) {await updateBoard();}` below `// TODO: If program and gamePublicKey exist, update board`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/index.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst callExpression = babelisedCode\n  .getType('CallExpression')\n  .find(\n    c =>\n      c.callee.object?.name === 'document' &&\n      c.callee.property?.name === 'addEventListener'\n  );\nconst newBabelisedCode = new __helpers.Babeliser(\n  babelisedCode.generateCode(callExpression)\n);\nconst tryStatement = newBabelisedCode.getType('TryStatement')?.[0];\nconst tryStatementBlock = tryStatement.block;\nconst actualCodeString = babelisedCode.generateCode(tryStatementBlock, {\n  compact: true\n});\nconst expectedCodeStrings = [\n  `if(program&&sessionStorage.getItem(\"gamePublicKey\")){await updateBoard()`,\n  `if(sessionStorage.getItem(\"gamePublicKey\")&&program){await updateBoard()`\n];\n\nconst promises = expectedCodeStrings.map((expectedCodeString, index) => {\n  return new Promise((resolve, reject) => {\n    try {\n      assert.include(actualCodeString, expectedCodeString);\n      resolve(index + 1);\n    } catch (e) {\n      reject(e);\n    }\n  });\n});\n\nawait Promise.any(promises);\n```\n\n## 51\n\n### --description--\n\nWithin `web3.js` initialize `window.program` to `null`.\n\n### --tests--\n\nYou should have `window.program = null;` within the `web3.js` file.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nassert.match(codeString, /window\\.program\\s*=\\s*null/);\n```\n\n## 52\n\n### --description--\n\nWithin the `handlePlay` function in `web3.js`, use the utility function `idToTile` to convert the `id` parameter to a `tile` variable.\n\n### --tests--\n\nYou should have `const tile = idToTile(id);` within the `handlePlay` function.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'handlePlay');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `handlePlay`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const tile=idToTile(id)`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `idToTile` from `./utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'You should import from `./utils.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'idToTile', '`idToTile` should be imported');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 53\n\n### --description--\n\nWithin the `handlePlay` function, declare a `keypair` variable set to an instance of the correct `Keypair` value.\n\n### --tests--\n\nYou should have `const keypair = Keypair.fromSecretKey(new Uint8Array(JSON.parse(sessionStorage.getItem(\"keypair\"))));` within the `handlePlay` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'handlePlay');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `handlePlay`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const keypair=Keypair.fromSecretKey(new Uint8Array(JSON.parse(sessionStorage.getItem(\"keypair\"))))`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 54\n\n### --description--\n\nWithin the `handlePlay` function, declare a `gamePublicKey` variable set to the `\"gamePublicKey\"` session storage item passed to the `PublicKey` constructor.\n\n### --tests--\n\nYou should have `const gamePublicKey = new PublicKey(sessionStorage.getItem(\"gamePublicKey\"));` within the `handlePlay` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'handlePlay');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `handlePlay`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `const gamePublicKey=new PublicKey(sessionStorage.getItem(\"gamePublicKey\"))`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 55\n\n### --description--\n\nWithin the `handlePlay` function, call the `play` instruction attaching the necessary accounts and signers.\n\n### --tests--\n\nYou should have `await program.methods.play(tile).accounts({ player: keypair.publicKey, game: gamePublicKey }).signers([keypair]).rpc();` within the `handlePlay` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'handlePlay');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `handlePlay`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `await program.methods.play(tile).accounts({player:keypair.publicKey,game:gamePublicKey}).signers([keypair]).rpc()`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 56\n\n### --description--\n\nWithin the `handlePlay` function, call the `updateBoard` function.\n\n### --tests--\n\nYou should have `await updateBoard();` within the `handlePlay` function.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'handlePlay');\nassert.exists(\n  functionDeclaration,\n  'You should declare a function named `handlePlay`'\n);\nconst actualCodeString = babelisedCode.generateCode(functionDeclaration, {\n  compact: true\n});\nconst expectedCodeString = `await updateBoard()`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 57\n\n### --description--\n\nWithin the `index.js` file, in the `tdEl` event listener callback, call the `handlePlay` function at the indicated comment. Remember to pass in the `id` of the `tdEl` element.\n\n### --tests--\n\nYou should have `await handlePlay(event.target.id);` below `// TODO: Play tile`.\n\n```js\nconst callExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => c.callee.object?.name === 'tdEl');\nconst tryStatementBlock = callExpression.arguments[1]?.body?.body?.find(\n  s => s.type === 'TryStatement'\n)?.block;\nconst actualCodeString = babelisedCode.generateCode(tryStatementBlock, {\n  compact: true\n});\nconst expectedCodeString = `handlePlay(event.target.id)`;\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `handlePlay` from `./web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `./web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'handlePlay',\n  '`handlePlay` should be imported'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'tic-tac-toe/app/index.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 58\n\n### --description--\n\nStart the Solana local validator, and deploy the tic-tac-toe program.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe tic-tac-toe program should be deployed.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '\n  {\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"getProgramAccounts\",\n    \"params\": [\n      \"BPFLoader2111111111111111111111111111111111\", {\n        \"encoding\": \"base64\",\n        \"dataSlice\": {\n          \"length\": 0,\n          \"offset\": 0\n        }\n      }\n    ]\n}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\nconst { stdout: keys } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/tic-tac-toe`\n);\nconst expectedProgramId = keys.match(/[^\\s]{44}/)?.[0];\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.exists(jsonOut.result.find(r => r.pubkey === expectedProgramId));\n} catch (e) {\n  assert.fail(\n    e,\n    `Try running \\`solana-test-validator --bpf-program ${expectedProgramId} ./target/deploy/tic_tac_toe.so --reset\\``\n  );\n}\n```\n\n## 59\n\n### --description--\n\nStart the Solana local validator, and initialize your two player accounts with funds.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe `player-one.json` account should have at least 1 SOL.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ${project.dashedName}/tic-tac-toe/player-one.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAtLeast(\n  parseInt(balance),\n  1,\n  'Try running `solana airdrop 1 ./player-one.json` within `tic-tac-toe/`'\n);\n```\n\nThe `player-two.json` account should have at least 1 SOL.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ${project.dashedName}/tic-tac-toe/player-two.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAtLeast(\n  parseInt(balance),\n  1,\n  'Try running `solana airdrop 1 ./player-one.json` within `tic-tac-toe/`'\n);\n```\n\n## 60\n\n### --description--\n\nThen, opening your browser to the port your app is served on, you should now be able to play a game.\n\n**NOTE:** Open one tab for each player, add the necessary keypairs, choose a game id, start a game with player one, join the game with player two, then start playing.\n\n### --tests--\n\nThe `player-one.json` account should make at least one transaction.\n\n```js\nconst { Connection, PublicKey } = await import('@solana/web3.js');\nconst connection = new Connection('http://localhost:8899', 'confirmed');\n\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana address -k ${project.dashedName}/tic-tac-toe/player-one.json`\n);\nconst pubkey = new PublicKey(stdout.trim());\nconst transactions = await connection.getConfirmedSignaturesForAddress2(pubkey);\n// 2 is used because the first transaction is likely an airdrop\nassert.isAtLeast(transactions, 2, 'Try playing a game with player one');\n```\n\nThe `player-two.json` account should make at least one transaction.\n\n```js\nconst { Connection, PublicKey } = await import('@solana/web3.js');\nconst connection = new Connection('http://localhost:8899', 'confirmed');\n\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana address -k ${project.dashedName}/tic-tac-toe/player-two.json`\n);\nconst pubkey = new PublicKey(stdout.trim());\nconst transactions = await connection.getConfirmedSignaturesForAddress2(pubkey);\n// 2 is used because the first transaction is likely an airdrop\nassert.isAtLeast(transactions, 2, 'Try playing a game with player one');\n```\n\n## 61\n\n### --description--\n\nWithin `web3.js`, you create a new `Connection` with the default level of commitment, _confirmed_:\n\n```js\nnew Connetion('http://localhost:8899', 'confirmed');\n```\n\n_Commitment_ refers to the level of confidence you have that a transaction will be included in a block. The higher the level of commitment, the longer it takes for a transaction to be confirmed.\n\nPlay different games changing the level of commitment to see what affect that has on the speed the board updates propagate.\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 62\n\n### --description--\n\nCongratulations on finishing this project! Feel free to play with your code.\n\n**Summary**\n\nInteracting with your Anchor program in the browser involves:\n\n- Building the program IDL\n- Attaching `Buffer` to the `window`\n- Connecting to a user's wallet\n- Creating and settings the provider\n- Defining your Anchor `Program`\n- For apps involving shared state between multiple users, it is important to understand the required commitment level for a transaction\n\nMinimal code example:\n\n```javascript\nimport { AnchorProvider, Program, setProvider } from '@coral-xyz/anchor';\nimport { Connection, PublicKey } from '@solana/web3.js';\nimport { IDL } from '../path/to/program/idl';\nimport { Buffer } from 'buffer';\n\nwindow.Buffer = Buffer;\n\nconst connection = new Connection('network', 'commitment');\nconst PROGRAM_ID = new PublicKey('program_publickey');\n\n// Wallet is an interface defined by Anchor, but specific to different vendors\nconst wallet = new Wallet();\nconst provider = new AnchorProvider(connection, wallet, {});\nsetProvider(provider);\nconst program = new Program(IDL, PROGRAM_ID, provider);\n```\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-build-a-client-side-app-part-2.md",
    "content": "# 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 to use the Phantom wallet browser extension to connect to your local validator, connect your wallet to a dApp, and sign transactions.\n\nChange into the `learn-how-to-build-a-client-side-app-part-2/` directory in a new terminal.\n\n### --tests--\n\nYou should be in the `learn-how-to-build-a-client-side-app-part-2/` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 2\n\n### --description--\n\nYou have been started out with the same Tic-Tac-Toe Anchor program as the last project.\n\nPreviously, you manually copy-pasted the player keypairs into the client app. This is both insecure and a poor user experience.\n\nInstead, install the `@solana/wallet-adapter-phantom` package in `app/` to handle connecting to the Phantom Wallet - a multi-chain wallet.\n\n### --tests--\n\nYou should have `@solana/wallet-adapter-phantom` in `app/package.json`.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(join(project.dashedName, 'app/package.json'))\n);\nassert.property(\n  packageJson.dependencies,\n  '@solana/wallet-adapter-phantom',\n  'The `package.json` file should have a `@solana/wallet-adapter-phantom` dependency.'\n);\n```\n\n## 3\n\n### --description--\n\nWithin `web3.js`, replace the `Wallet` import with `PhantomWalletAdapter` from `@solana/wallet-adapter-phantom`.\n\n### --tests--\n\nYou should not import `Wallet` from `./wallet.js`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './wallet.js';\n});\nassert.notExists(importDeclaration, 'You should not import from `./wallet.js`');\n```\n\nYou should import `PhantomWalletAdapter` from `@solana/wallet-adapter-phantom`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/wallet-adapter-phantom';\n});\nassert.exists(\n  importDeclaration,\n  'You should import from `@solana/wallet-adapter-phantom`'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'PhantomWalletAdapter',\n  '`PhantomWalletAdapter` should be imported from `@solana/wallet-adapter-phantom`'\n);\n```\n\n## 4\n\n### --description--\n\nWithin the `connectWallet` function, delete all the keypair logic, and assign `wallet` a new instance of `PhantomWalletAdapter`.\n\n### --tests--\n\nYou should remove the `keypairArr` declaration from `connectWallet`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'keypairArr');\nassert.notExists(\n  variableDeclaration,\n  'You should remove the `keypairArr` declaration from `connectWallet`'\n);\n```\n\nYou should remove the `uint` declaration from `connectWallet`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'uint');\nassert.notExists(\n  variableDeclaration,\n  'You should remove the `uint` declaration from `connectWallet`'\n);\n```\n\nYou should remove the `keypair` declaration from `connectWallet`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'keypair');\nassert.notExists(\n  variableDeclaration,\n  'You should remove the `keypair` declaration from `connectWallet`'\n);\n```\n\nYou should have `const wallet = new PhantomWalletAdapter()` within `connectWallet`.\n\n```js\nconst expectedCodeString = `const wallet=new PhantomWalletAdapter()`;\nconst actualCodeString = babelisedCode.generate(babelisedCode.parsedCode, {\n  compact: true\n});\nassert.include(actualCodeString, expectedCodeString);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst babelisedConnectWallet = new __helpers.Babeliser(\n  babelisedCode.generate(\n    babelisedCode\n      .getFunctionDeclarations()\n      .find(f => f.id.name === 'connectWallet')\n  )\n);\nglobal.babelisedCode = babelisedConnectWallet;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 5\n\n### --description--\n\nSimilarly, remove all the keypair logic within the `startGame` function.\n\n### --tests--\n\nYou should remove the `keypairStr` declaration from `startGame`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'keypairStr');\nassert.notExists(\n  variableDeclaration,\n  'A `keypairStr` declaration should not exist in `startGame`'\n);\n```\n\nYou should remove the `keypairArr` declaration from `startGame`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'keypairArr');\nassert.notExists(\n  variableDeclaration,\n  'A `keypairArr` declaration should not exist in `startGame`'\n);\n```\n\nYou should remove the `uint8Array` declaration from `startGame`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'uint8Array');\nassert.notExists(\n  variableDeclaration,\n  'A `uint8Array` declaration should not exist in `startGame`'\n);\n```\n\nYou should remove the `keypair` declaration from `startGame`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'keypair');\nassert.notExists(\n  variableDeclaration,\n  'A `keypair` declaration should not exist in `startGame`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst babelisedConnectWallet = new __helpers.Babeliser(\n  babelisedCode.generate(\n    babelisedCode\n      .getFunctionDeclarations()\n      .find(f => f.id.name === 'connectWallet')\n  )\n);\nglobal.babelisedCode = babelisedConnectWallet;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 6\n\n### --description--\n\nThe Phantom Browser Extension injects a global `window.phantom` object into the browser. This object contains the chains and public keys of the currently connected wallet.\n\nWithin `startGame`, set the `playerOne` account public key to `window.phantom.solana.publicKey`.\n\n### --tests--\n\nYou should have `.accounts({playerOne:window.phantom.solana.publicKey})` within `startGame`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst babelisedStartGame = new __helpers.Babeliser(\n  babelisedCode.generate(\n    babelisedCode.getFunctionDeclarations().find(f => f.id.name === 'startGame')\n  )\n);\nconst expectedCodeString = `.accounts({playerOne:window.phantom.solana.publicKey})`;\nconst actualCodeString = babelisedStartGame.generate(\n  babelisedStartGame.parsedCode,\n  { compact: true }\n);\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 7\n\n### --description--\n\nWithin `startGame`, seeing as the wallet is handling the signing, remove the `keypair` as a `signer` to the rpc call.\n\n### --tests--\n\nYou should remove `.signers([keypair])` from `startGame`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst babelisedStartGame = new __helpers.Babeliser(\n  babelisedCode.generate(\n    babelisedCode.getFunctionDeclarations().find(f => f.id.name === 'startGame')\n  )\n);\nconst expectedCodeString = `.signers([keypair])`;\nconst actualCodeString = babelisedStartGame.generate(\n  babelisedStartGame.parsedCode,\n  { compact: true }\n);\nassert.notInclude(actualCodeString, expectedCodeString);\n```\n\n## 8\n\n### --description--\n\nWithin `startGame`, remove the keypair logic from the `handlePlay` function.\n\n### --tests--\n\nYou should remove the `keypairStr` declaration from `handlePlay`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'keypairStr');\nassert.notExists(\n  variableDeclaration,\n  'A `keypairStr` declaration should not exist in `handlePlay`'\n);\n```\n\nYou should remove the `keypairArr` declaration from `handlePlay`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'keypairArr');\nassert.notExists(\n  variableDeclaration,\n  'A `keypairArr` declaration should not exist in `handlePlay`'\n);\n```\n\nYou should remove the `uint8Array` declaration from `handlePlay`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'uint8Array');\nassert.notExists(\n  variableDeclaration,\n  'A `uint8Array` declaration should not exist in `handlePlay`'\n);\n```\n\nYou should remove the `keypair` declaration from `handlePlay`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.id.name === 'keypair');\nassert.notExists(\n  variableDeclaration,\n  'A `keypair` declaration should not exist in `handlePlay`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst babelisedConnectWallet = new __helpers.Babeliser(\n  babelisedCode.generate(\n    babelisedCode\n      .getFunctionDeclarations()\n      .find(f => f.id.name === 'connectWallet')\n  )\n);\nglobal.babelisedCode = babelisedConnectWallet;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 9\n\n### --description--\n\nWithin `handlePlay`, remove the `keypair` as a `signer` to the rpc call.\n\n### --tests--\n\nYou should remove `.signers([keypair])` from `handlePlay`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst babelisedHandlePlay = new __helpers.Babeliser(\n  babelisedCode.generate(\n    babelisedCode\n      .getFunctionDeclarations()\n      .find(f => f.id.name === 'handlePlay')\n  )\n);\nconst expectedCodeString = `.signers([keypair])`;\nconst actualCodeString = babelisedHandlePlay.generate(\n  babelisedHandlePlay.parsedCode,\n  { compact: true }\n);\nassert.notInclude(actualCodeString, expectedCodeString);\n```\n\n## 10\n\n### --description--\n\nWithin `handlePlay`, set the `player` account public key to the public key of the wallet.\n\n### --tests--\n\nYou should have `.accounts({player:window.phantom.solana.publicKey})` within `handlePlay`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'app/web3.js')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst babelisedHandlePlay = new __helpers.Babeliser(\n  babelisedCode.generate(\n    babelisedCode\n      .getFunctionDeclarations()\n      .find(f => f.id.name === 'handlePlay')\n  )\n);\nconst expectedCodeString = `.accounts({player:window.phantom.solana.publicKey})`;\nconst actualCodeString = babelisedHandlePlay.generate(\n  babelisedHandlePlay.parsedCode,\n  { compact: true }\n);\nassert.include(actualCodeString, expectedCodeString);\n```\n\n## 11\n\n### --description--\n\nThe proceeding lessons do not have any tests. Follow the instructions, and once you are confident you have completed the task, type `done` in the terminal.\n\nType `done` in the terminal to move on to the next lesson.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 12\n\n### --description--\n\nWithin a browser, navigate to https://phantom.app/ to install the Phantom browser extension.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 13\n\n### --description--\n\nClick the \"Download\" button, and follow your browser's instructions to add the extension.\n\n![Phantom browser extension download page](../../images/phantom/image.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 14\n\n### --description--\n\nAfter installing the extension, click the Phantom icon in your browser's toolbar to set up your wallet.\n\nCreate a password, and click \"Continue\".\n\n![Phantom browser extension password creation](../../images/phantom/image-2.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 15\n\n### --description--\n\nTake note of your secret recovery phrase. This is the only way to recover your wallet if you forget your password, and can be used to import your wallet into other browsers/platforms. Then, click \"Continue\".\n\n![Phantom browser extension secret recovery phrase](../../images/phantom/image-3.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 16\n\n### --description--\n\nFinish reading the setup information, and click the Phantom icon in your browser's toolbar to open the extension.\n\n![Phantom browser extension setup information](../../images/phantom/image-4.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 17\n\n### --description--\n\nYou should a UI similar to:\n\n![Phantom browser extension landing page](../../images/phantom/image-5.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\nOpen the menubar by clicking the three dots in the top left corner.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 18\n\n### --description--\n\nOpen the settings page:\n\n![Phantom browser extension settings button](../../images/phantom/image-6.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 19\n\n### --description--\n\nClick the \"Developer Settings\" button.\n\n![Phantom browser extension developer settings button](../../images/phantom/image-7.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 20\n\n### --description--\n\nEnable the \"Testnet Mode\" in order to connect to your local validator.\n\n![Phantom browser extension testnet mode](../../images/phantom/image-8.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 21\n\n### --description--\n\nClick the Solana network button.\n\n![Phantom browser extension solana network button](../../images/phantom/image-9.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 22\n\n### --description--\n\nSelect the \"Solana Localnet\" option.\n\n![Phantom browser extension solana localnet option](../../images/phantom/image-10.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 23\n\n### --description--\n\nFrom the menubar, click the account to edit it.\n\n![Phantom browser extension account button](../../images/phantom/image-12.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 24\n\n### --description--\n\nChange the account name to `Player 1` to help you keep track of it.\n\n![Phantom browser extension account name](../../images/phantom/image-12.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 25\n\n### --description--\n\nWithin your terminal, start a local validator, being sure to deploy the `tic_tac_toe.so` program at the same time.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe mess program should be deployed.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '\n  {\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"getProgramAccounts\",\n    \"params\": [\n      \"BPFLoader2111111111111111111111111111111111\", {\n        \"encoding\": \"base64\",\n        \"dataSlice\": {\n          \"length\": 0,\n          \"offset\": 0\n        }\n      }\n    ]\n}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\nconst programId = '5xGwZASoE5ZgxKgaisJNaGTGzMKzjyyBGv9FCUtu2m1c';\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.exists(jsonOut.result.find(r => r.pubkey === programId));\n} catch (e) {\n  assert.fail(\n    e,\n    `Try running \\`solana-test-validator --bpf-program ${programId} tic_tac_toe.so --reset\\``\n  );\n}\n```\n\nWithin the Phantom browser extension, click on your account name to get your Solana public key:\n\n![Phantom browser extension account public key](../../images/phantom/image-13.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 26\n\n### --description--\n\nWithin your terminal, airdrop to your account.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 27\n\n### --description--\n\nWithin the `app/` directory, start the client app server with `yarn dev`.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 28\n\n### --description--\n\nWithin your browser, navigate to http://localhost:5173/. You should see the client app.\n\nConnect your wallet to the app by clicking the \"Connect Wallet\" button.\n\n![Phantom browser extension connect wallet button](../../images/phantom/image-14.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 29\n\n### --description--\n\nWithin the Phantom browser extension, create a second Solana account:\n\n![Phantom browser extension create account button](../../images/phantom/image-15.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n![Phantom browser extension create account button](../../images/phantom/image-16.png)\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 30\n\n### --description--\n\nRename the second account to `Player 2`:\n\n![Phantom browser extension rename account button](../../images/phantom/image-17.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 31\n\n### --description--\n\nWithin your terminal, airdrop to the second account.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 32\n\n### --description--\n\nWithin the Phantom browser extension, ensure you are on your `Player 1` account.\n\nWithin the client app, add the two public keys and any game id.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 33\n\n### --description--\n\nWithin the client app, click the start game button.\n\nThe Phantom browser extension should prompt you to approve the transaction. Approve it.\n\n![Phantom browser extension approve transaction](../../images/phantom/image-19.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 34\n\n### --description--\n\nOnce the game is started, you can play the game by opening a second browser window, and connecting to the second account.\n\n![Phantom browser extension connect wallet button](../../images/phantom/image-18.png)\n\n![Phantom browser extension approve transaction](../../images/phantom/image-19.png)\n\n<style>\n  img {\n    width: 100%;\n  }\n</style>\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 35\n\n### --description--\n\nCongratulations on finishing this project! Feel free to play with your code.\n\n**Summary**:\n\n1. Install the wallet adapter/s: `@solana/wallet-adapter-<WALLET>`\n2. Navigate to https://phantom.app/\n3. Install the Phantom browser extension\n4. Start a local validator:\n\n```bash\nsolana-test-validator --bpf-program <PROGRAM_ID> ./tic_tac_toe.so --reset\n```\n\n5. Airdrop to your wallet account\n6. Connect your wallet to your app\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-build-for-mainnet.md",
    "content": "# Solana - Learn How to Build for Mainnet\n\n## 1\n\n### --description--\n\nYou have been started off with an Anchor full-stack boilerplate.\n\nIn this project, you will develop and prepare a program for deployment on the Solana blockchain.\n\nStart by changing into the `learn-how-to-build-for-mainnet/todo/` directory.\n\n### --tests--\n\nYou should be in the `learn-how-to-build-for-mainnet/todo/` directory.\n\n```js\nconst cwd = await __helpers.getLastCWD();\nconst dirRegex = new RegExp(`${project.dashedName}/todo/?$`);\nassert.match(cwd, dirRegex);\n```\n\n## 2\n\n### --description--\n\nWithin `programs/todo/src/lib.rs`, start by renaming the `initialize` instruction handle to `save_tasks`.\n\n### --tests--\n\nThe `initialize` instruction handle should be renamed to `save_tasks`.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nassert.match(librs, /pub fn save_tasks/);\nassert.notMatch(librs, /pub fn initialize/);\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct Initialize {}\n```\n\n## 3\n\n### --description--\n\nRename the `Initialize` context to `SaveTasks`.\n\n### --tests--\n\nThe `Initialize` struct should be renamed to `SaveTasks`.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nassert.match(librs, /pub struct SaveTasks/);\nassert.notMatch(librs, /pub struct Initialize/);\n```\n\nThe `Initialize` context should be renamed to `SaveTasks`.\n\n```js\nconst librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nassert.match(librs, /Context<SaveTasks>/);\nassert.notMatch(librs, /Context<Initialize>/);\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<Initialize>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct Initialize {}\n```\n\n## 4\n\n### --description--\n\nA \"task\" will be saved into a new PDA.\n\nWithin the `SaveTasks` struct, add a public `tasks` account with a type of `Account<'info, TasksAccount>`.\n\n### --tests--\n\nThe `SaveTasks` struct should have a `pub tasks` field.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /pub\\s+tasks/);\n```\n\nThe `tasks` field should be annotated with `#[account()]`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /#\\[\\s*account\\(/);\n```\n\nThe `tasks` field should have a type of `Account<'info, TasksAccount>`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(\n  saveTasks,\n  /pub\\s+tasks\\s*:\\s*Account\\s*<\\s*'info\\s*,\\s*TasksAccount\\s*>/\n);\n```\n\nThe `SaveTasks` struct should be generic over a lifetime of `'info`.\n\n```js\nassert.match(__librs, /struct\\s+SaveTasks\\s*<\\s*'info\\s*>/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks {}\n```\n\n## 5\n\n### --description--\n\nThe `save_task` instruction handle will be the only instruction handle in the program. So, the data account should be initialized only _if needed_.\n\nAnchor provides an `init_if_needed` argument for this purpose. Pass it as an argument to the `account` attribute macro, and add `init-if-needed` as a feature to the `anchor-lang` dependency in the `Cargo.toml` file.\n\n### --tests--\n\nThe `tasks` field should be annotated with `#[account(init_if_needed)]`.\n\n```js\nconst saveTasks = __librs.match(\n  /struct\\s+SaveTasks\\s*<\\s*'info\\s*>\\s*{([^}]*)}/s\n)?.[1];\nassert.match(saveTasks, /#\\[\\s*account\\s*\\(\\s*init_if_needed\\s*\\)\\s*\\]/);\n```\n\nThe `anchor-lang` dependency should have a `features` array with a value of `[\"init-if-needed\"]`.\n\n```js\nconst cargoToml = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/Cargo.toml`\n);\nassert.match(cargoToml, /features\\s*=\\s*\\[\\s*\"init-if-needed\"\\s*\\]/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account()]\n    pub tasks: Account<'info, TasksAccount>\n}\n```\n\n## 6\n\n### --description--\n\nDefine a public `TasksAccount` struct with a `tasks` field of type `Vec<Task>`. Remember to annotate it as an account.\n\n### --tests--\n\nThe `TasksAccount` struct should be annotated with `#[account]`.\n\n```js\nassert.match(__librs, /#\\[\\s*account\\s*\\]\\s*pub\\s+struct\\s+TasksAccount/);\n```\n\nThe `TasksAccount` struct should have a public `tasks` field.\n\n```js\nconst tasksAccount = __librs.match(\n  /pub\\s+struct\\s+TasksAccount[^{]*{([^}]*)}/s\n)?.[1];\nassert.match(tasksAccount, /pub\\s+tasks/);\n```\n\nThe `tasks` field should have a type of `Vec<Task>`.\n\n```js\nconst tasksAccount = __librs.match(\n  /pub\\s+struct\\s+TasksAccount[^{]*{([^}]*)}/s\n)?.[1];\nassert.match(tasksAccount, /pub\\s+tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>\n}\n```\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/Cargo.toml\"--\n\n```toml\n[package]\nname = \"todo\"\nversion = \"0.1.0\"\ndescription = \"A todo list program\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\nname = \"todo\"\n\n[features]\nno-entrypoint = []\nno-idl = []\nno-log-ix-name = []\ncpi = [\"no-entrypoint\"]\ndefault = []\n\n[dependencies]\nanchor-lang = { version = \"0.28.0\", features = [\"init-if-needed\"] }\n```\n\n## 7\n\n### --description--\n\nDefine a public `Task` struct with an `id` file of type `u32`, a `name` field of type `String`, and a `completed` field of type `bool`.\n\n### --tests--\n\nThe `Task` struct should have a public `id` field.\n\n```js\nconst task = __librs.match(/pub\\s+struct\\s+Task\\s*{([^}]*)}/s)?.[1];\nassert.match(task, /pub\\s+id/);\n```\n\nThe `id` field should have a type of `u32`.\n\n```js\nconst task = __librs.match(/pub\\s+struct\\s+Task\\s*{([^}]*)}/s)?.[1];\nassert.match(task, /pub\\s+id\\s*:\\s*u32/);\n```\n\nThe `Task` struct should have a public `name` field.\n\n```js\nconst task = __librs.match(/pub\\s+struct\\s+Task\\s*{([^}]*)}/s)?.[1];\nassert.match(task, /pub\\s+name/);\n```\n\nThe `name` field should have a type of `String`.\n\n```js\nconst task = __librs.match(/pub\\s+struct\\s+Task\\s*{([^}]*)}/s)?.[1];\nassert.match(task, /pub\\s+name\\s*:\\s*String/);\n```\n\nThe `Task` struct should have a public `completed` field.\n\n```js\nconst task = __librs.match(/pub\\s+struct\\s+Task\\s*{([^}]*)}/s)?.[1];\nassert.match(task, /pub\\s+completed/);\n```\n\nThe `completed` field should have a type of `bool`.\n\n```js\nconst task = __librs.match(/pub\\s+struct\\s+Task\\s*{([^}]*)}/s)?.[1];\nassert.match(task, /pub\\s+completed\\s*:\\s*bool/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>\n}\n```\n\n## 8\n\n### --description--\n\nDerive the necessary traits for the `Task` struct.\n\n### --tests--\n\nThe `Task` struct should be annotated with `#[derive(AnchorSerialize, AnchorDeserialize, Clone)]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*derive\\s*\\(\\s*AnchorSerialize\\s*,\\s*AnchorDeserialize\\s*,\\s*Clone\\s*\\)\\s*\\]\\s*pub\\s+struct\\s+Task/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n```\n\n## 9\n\n### --description--\n\nThe `save_tasks` instruction handle will take a `replacing_tasks` argument of type `Vec<Task>`.\n\nAdd the expected argument.\n\n### --tests--\n\nThe `save_tasks` instruction handle should have a `replacing_tasks` argument.\n\n```js\nassert.match(\n  __librs,\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks/s\n);\n```\n\nThe `replacing_tasks` argument should have a type of `Vec<Task>`.\n\n```js\nassert.match(\n  __librs,\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)/s\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n```\n\n## 10\n\n### --description--\n\nCreate a public `ErrorCode` enum with the following variants: `TaskNameTooLong`, `TaskNameTooShort`, and `TaskIdNotUnique`\n\n### --tests--\n\nThe `ErrorCode` enum should have a `TaskNameTooLong` variant.\n\n```js\nconst errorCode = __librs.match(/pub\\s+enum\\s+ErrorCode\\s*{([^}]*)}/s)?.[1];\nassert.match(errorCode, /TaskNameTooLong/);\n```\n\nThe `ErrorCode` enum should have a `TaskNameTooShort` variant.\n\n```js\nconst errorCode = __librs.match(/pub\\s+enum\\s+ErrorCode\\s*{([^}]*)}/s)?.[1];\nassert.match(errorCode, /TaskNameTooShort/);\n```\n\nThe `ErrorCode` enum should have a `TaskIdNotUnique` variant.\n\n```js\nconst errorCode = __librs.match(/pub\\s+enum\\s+ErrorCode\\s*{([^}]*)}/s)?.[1];\nassert.match(errorCode, /TaskIdNotUnique/);\n```\n\nThe `ErrorCode` enum should be annotated with `#[error_code]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[\\s*error_code\\s*\\]\\s*pub\\s+enum\\s+ErrorCode\\s*{([^}]*)}/s\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n```\n\n## 11\n\n### --description--\n\nWithin the `save_tasks` instruction handle, return an error if any of the task names are greater than 32 characters.\n\n### --tests--\n\nThe `save_tasks` instruction handle should return `Err(ErrorCode::TaskNameTooLong.into())` if any of the task names are greater than 32 characters.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /Err\\s*\\(\\s*ErrorCode::TaskNameTooLong\\s*\\.\\s*into\\s*\\(\\s*\\)/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 12\n\n### --description--\n\nWithin the `save_tasks` instruction handle, return an error if any of the task names are less than 1 character.\n\n### --tests--\n\nThe `save_tasks` instruction handle should return `Err(ErrorCode::TaskNameTooShort.into())` if any of the task names are less than 1 character.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /Err\\s*\\(\\s*ErrorCode::TaskNameTooShort\\s*\\.\\s*into\\s*\\(\\s*\\)/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 13\n\n### --description--\n\nWithin the `save_tasks` instruction handle, return an error if any of the task ids are not unique.\n\n### --tests--\n\nThe `save_tasks` instruction handle should return `Err(ErrorCode::TaskIdNotUnique.into())` if any of the task ids are not unique.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /Err\\s*\\(\\s*ErrorCode::TaskIdNotUnique\\s*\\.\\s*into\\s*\\(\\s*\\)/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 14\n\n### --description--\n\nWhen initializing the `TasksAccount` account, set the `space` argument to:\n\n```text\n<DISCRIMINANT_SIZE> + replacing_tasks.len() * <TASK_SIZE>\n```\n\n### --tests--\n\nThe `SaveTasks` struct `tasks` field should be annotated with `#[account(space = ...)]`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /\\s*space\\s*=/);\n```\n\nThe `space` argument should be set to `8 + replacing_tasks.len() * (4 + (4 + 32) + 1)`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(\n  saveTasks,\n  /\\s*space\\s*=\\s*8\\s*\\+\\s*replacing_tasks\\.len\\s*\\(\\s*\\)\\s*\\*\\s*\\(\\s*4\\s*\\+\\s*\\(\\s*4\\s*\\+\\s*32\\s*\\)\\s*\\+\\s*1\\s*\\)/\n);\n```\n\nThe `SaveTasks` struct should be annotated with `#[instruction(replacing_tasks: Vec<Task>)]`.\n\n```js\nassert.match(\n  __librs,\n  /#\\[instruction\\s*\\(\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*\\]/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed)]\n    pub tasks: Account<'info, TasksAccount>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 15\n\n### --description--\n\nSet the payer of the `TasksAccount` account to `user`, and declare a public `user` field of type `Signer<'info>` within the `SaveTasks` struct.\n\n### --tests--\n\nThe `SaveTasks` struct should have a `pub user` field.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /pub\\s+user/);\n```\n\nThe `user` field should have a type of `Signer<'info>`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /pub\\s+user\\s*:\\s*Signer\\s*<\\s*'\\s*info\\s*>\\s*,/);\n```\n\nThe `user` field should be annotated with `#[account(mut)]`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /#[^#]*account\\s*\\(\\s*mut\\s*\\)/);\n```\n\nThe `tasks` field should be annotated with `#[account(payer = user)]`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /payer\\s*=\\s*user/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1))]\n    pub tasks: Account<'info, TasksAccount>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 16\n\n### --description--\n\nSeeing as each ToDo will be related to a specific user, set the seeds of the `TasksAccount` account to the user public key, and tell Anchor to generate the bump seed.\n\n### --tests--\n\nThe `tasks` field should be annotated with `#[account(seeds = [user.key().as_ref()])]`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(\n  saveTasks,\n  /seeds\\s*=\\s*\\[\\s*user\\.key\\s*\\(\\s*\\)\\s*\\.\\s*as_ref\\s*\\(\\s*\\)\\s*\\]/\n);\n```\n\nThe `tasks` field should be annotated with `#[account(bump)]`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /bump/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 17\n\n### --description--\n\nInclude the necessary program to initialize a data account owned by your program.\n\n### --tests--\n\nThe `SaveTasks` struct should have a `pub system_program` field.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(saveTasks, /pub\\s+system_program/);\n```\n\nThe `system_program` field should have a type of `Program<'info, System>`.\n\n```js\nconst saveTasks = __librs.match(/struct\\s+SaveTasks[^{]*{([^}]*)}/s)?.[1];\nassert.match(\n  saveTasks,\n  /pub\\s+system_program\\s*:\\s*Program\\s*<\\s*'\\s*info\\s*,\\s*System\\s*>\\s*,/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 18\n\n### --description--\n\nAs the number of tasks stored can vary between saves, the account size needs to reallocate to the correct amount of space.\n\nWithin the `save_tasks` instruction handle, add an `if` statement to check if the `tasks` account `tasks` data length is not equal to the `replacing_tasks` length.\n\n### --tests--\n\nThe `save_tasks` instruction handle should have `if tasks.tasks.len() != replacing_tasks.len() {}`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /if\\s+tasks\\.tasks\\.len\\s*\\(\\s*\\)\\s*!=\\s*replacing_tasks\\.len\\s*\\(\\s*\\)\\s*{}/\n);\n```\n\nYou should declare a `tasks` variable with value `ctx.accounts.tasks`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(saveTasks, /let\\s+tasks\\s*=/);\nassert.match(saveTasks, /ctx\\.accounts\\.tasks\\s*;/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 19\n\n### --description--\n\nWithin the `if` statement, declare a `new_space` variable with the new **total** size of the `tasks` account with the `replacing_tasks` data.\n\n### --tests--\n\nThe `new_space` variable should be declared with a value of `8 + replacing_tasks.len() * (4 + (4 + 32) + 1)`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /let\\s+new_space\\s*=\\s*8\\s*\\+\\s*replacing_tasks\\.len\\s*\\(\\s*\\)\\s*\\*\\s*\\(\\s*4\\s*\\+\\s*\\(\\s*4\\s*\\+\\s*32\\s*\\)\\s*\\+\\s*1\\s*\\)/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {}\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 20\n\n### --description--\n\nWith the new space, the new rent exemption can be calculated:\n\n```rust\nlet minimum_balance: u64 = Rent::get()?.minimum_balance(space);\n```\n\nUse the `new_space` to calculate the new rent exemption, and assign it to a `new_minimum_balance` variable.\n\n### --tests--\n\nThe `new_minimum_balance` variable should be declared with a value of `Rent::get()?.minimum_balance(new_space)`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /let\\s+new_minimum_balance\\s*=\\s*Rent\\s*::\\s*get\\s*\\(\\s*\\)\\s*\\?\\s*\\.\\s*minimum_balance\\s*\\(\\s*new_space\\s*\\)/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 21\n\n### --description--\n\nAn `Account` is just a wrapper around `AccountInfo`. The `tasks` account info is needed to get its current balance, and adjust the balance to the new minimum balance.\n\n```rust\nlet account_info = &ctx.accounts.account.to_account_info();\n```\n\nDeclare a `tasks_account_info` variable, and assign it the `tasks` account info.\n\n### --tests--\n\nThe `tasks_account_info` variable should be declared with a value of `tasks.to_account_info()`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /let\\s+tasks_account_info\\s*=\\s*tasks\\s*\\.to_account_info\\s*\\(\\s*\\)/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 22\n\n### --description--\n\nTo get the difference in balance, the `saturating_sub` method can be used on the `new_minimum_balance` to ensure the balance is not negative.\n\nUse `saturating_sub` to subtract `tasks_account_info.lamports()` from `new_minimum_balance`, and assign it to a `lamports_diff` variable.\n\n### --tests--\n\nThe `lamports_diff` variable should be declared with a value of `new_minimum_balance.saturating_sub(tasks_account_info.lamports())`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /let\\s+lamports_diff\\s*=\\s*new_minimum_balance\\s*\\.\\s*saturating_sub\\s*\\(\\s*tasks_account_info\\s*\\.\\s*lamports\\s*\\(\\s*\\)\\s*\\)/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n            let tasks_account_info = tasks.to_account_info();\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 23\n\n### --description--\n\nA mutable account's balance can be adjusted by mutably borrowing its lamports and adjusting them:\n\n```rust\n**ctx.accounts.mutable_account.to_account_info().try_borrow_mut_lamports()? = 1;\n```\n\nSubtract the `lamports_diff` from the `user` account.\n\n### --tests--\n\nYou should have `**ctx.accounts.user.to_account_info().try_borrow_mut_lamports()? -= lamports_diff;`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /\\*\\*ctx\\s*\\.accounts\\s*\\.user\\s*\\.to_account_info\\s*\\(\\s*\\)\\s*\\.\\s*try_borrow_mut_lamports\\s*\\(\\s*\\)\\s*\\?\\s*-=\\s*lamports_diff\\s*;/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n            let tasks_account_info = tasks.to_account_info();\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 24\n\n### --description--\n\nNow, add the `lamports_diff` to the `tasks` account.\n\n### --tests--\n\nYou should have `**tasks_account_info.try_borrow_mut_lamports()? += lamports_diff;`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /\\*\\*tasks_account_info\\s*\\.\\s*try_borrow_mut_lamports\\s*\\(\\s*\\)\\s*\\?\\s*\\+=\\s*lamports_diff\\s*;/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n            let tasks_account_info = tasks.to_account_info();\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n\n            **ctx\n                .accounts\n                .user\n                .to_account_info()\n                .try_borrow_mut_lamports()? -= lamports_diff;\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 25\n\n### --description--\n\nNow that the `tasks` account has the correct balance, the `realloc` method can be called to reallocate the account's data:\n\n```rust\naccount_info.realloc(new_space, zero_initialize_memory)?;\n```\n\nThe `realloc` method takes the new space, and a boolean to <dfn title=\"If true, the new memory is ensured to not have any old, stale data left behind from previous use.\">zero-initialize</dfn> the new memory.\n\nCall `realloc` on the `tasks_account_info` with the `new_space` and `false`.\n\n### --tests--\n\nYou should have `tasks_account_info.realloc(new_space, false)?;`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /tasks_account_info\\s*\\.\\s*realloc\\s*\\(\\s*new_space\\s*,\\s*false\\s*\\)\\s*\\?/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n            let tasks_account_info = tasks.to_account_info();\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n\n            **ctx\n                .accounts\n                .user\n                .to_account_info()\n                .try_borrow_mut_lamports()? -= lamports_diff;\n            **tasks_account_info.try_borrow_mut_lamports()? += lamports_diff;\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 26\n\n### --description--\n\nAfter the `if` statement, set the `tasks` account's `tasks` data to `replacing_tasks`.\n\n### --tests--\n\nYou should have `tasks.tasks = replacing_tasks;`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(saveTasks, /tasks\\s*\\.\\s*tasks\\s*=\\s*replacing_tasks\\s*;/);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n            let tasks_account_info = tasks.to_account_info();\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n\n            **ctx\n                .accounts\n                .user\n                .to_account_info()\n                .try_borrow_mut_lamports()? -= lamports_diff;\n            **tasks_account_info.try_borrow_mut_lamports()? += lamports_diff;\n\n            tasks_account_info.realloc(new_space, false)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 27\n\n### --description--\n\nLastly within the `save_tasks` instruction handle, adjust the `if` statement conditional to only realloc if the replacing tasks space is less than the currently allocated space.\n\n### --tests--\n\nThe `if` statement conditional should be `if tasks.tasks.len() < replacing_tasks.len() {`.\n\n```js\nconst saveTasks = __librs.match(\n  /pub\\s+fn\\s+save_tasks\\s*\\(\\s*ctx:\\s*Context<\\s*SaveTasks\\s*>,\\s*replacing_tasks\\s*:\\s*Vec\\s*<\\s*Task\\s*>\\s*\\)\\s*->\\s*Result\\s*<\\s*\\(\\)\\s*>\\s*{(.*?)Ok\\(\\(\\)\\)/s\n)?.[1];\nassert.match(\n  saveTasks,\n  /if\\s+tasks\\s*\\.\\s*tasks\\s*\\.\\s*len\\s*\\(\\s*\\)\\s*<\\s*replacing_tasks\\s*\\.\\s*len\\s*\\(\\s*\\)\\s*{/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() != replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n            let tasks_account_info = tasks.to_account_info();\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n\n            **ctx\n                .accounts\n                .user\n                .to_account_info()\n                .try_borrow_mut_lamports()? -= lamports_diff;\n            **tasks_account_info.try_borrow_mut_lamports()? += lamports_diff;\n\n            tasks_account_info.realloc(new_space, false)?;\n        }\n\n        tasks.tasks = replacing_tasks;\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 28\n\n### --description--\n\nFinally for the program, use the `msg` attribute macro to add human-readable error messages to the `ErrorCode` enum:\n\n```rust\n#[error_code]\nenum MyErrorEnum {\n    #[msg(\"My error message\")]\n    MyError,\n}\n```\n\n### --tests--\n\nThe `TaskNameTooLong` variant should be annotated with a message.\n\n```js\nconst errorCode = __librs.match(\n  /#\\[error_code\\]\\s*pub\\s+enum\\s+ErrorCode\\s*{([\\s\\S]*?)}/\n)?.[1];\nassert.match(\n  errorCode,\n  /#\\s*\\[\\s*msg\\s*\\(\\s*\"[\\w\\s.]{1,}\"\\)\\]\\s*TaskNameTooLong\\s*,/\n);\n```\n\nThe `TaskNameTooShort` variant should be annotated with a message.\n\n```js\nconst errorCode = __librs.match(\n  /#\\[error_code\\]\\s*pub\\s+enum\\s+ErrorCode\\s*{([\\s\\S]*?)}/\n)?.[1];\nassert.match(\n  errorCode,\n  /#\\s*\\[\\s*msg\\s*\\(\\s*\"[\\w\\s.]{1,}\"\\)\\]\\s*TaskNameTooShort\\s*,/\n);\n```\n\nThe `TaskIdNotUnique` variant should be annotated with a message.\n\n```js\nconst errorCode = __librs.match(\n  /#\\[error_code\\]\\s*pub\\s+enum\\s+ErrorCode\\s*{([\\s\\S]*?)}/\n)?.[1];\nassert.match(\n  errorCode,\n  /#\\s*\\[\\s*msg\\s*\\(\\s*\"[\\w\\s.]{1,}\"\\)\\]\\s*TaskIdNotUnique\\s*,/\n);\n```\n\n### --before-all--\n\n```js\nconst __librs = await __helpers.getFile(\n  `${project.dashedName}/todo/programs/todo/src/lib.rs`\n);\nglobal.__librs = __librs;\n```\n\n### --after-all--\n\n```js\ndelete global.__librs;\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() < replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n            let tasks_account_info = tasks.to_account_info();\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n\n            **ctx\n                .accounts\n                .user\n                .to_account_info()\n                .try_borrow_mut_lamports()? -= lamports_diff;\n            **tasks_account_info.try_borrow_mut_lamports()? += lamports_diff;\n\n            tasks_account_info.realloc(new_space, false)?;\n        }\n\n        tasks.tasks = replacing_tasks;\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    TaskNameTooLong,\n    TaskNameTooShort,\n    TaskIdNotUnique,\n}\n```\n\n## 29\n\n### --description--\n\nBuild the program to get the IDL for your client app.\n\n### --tests--\n\nYou should build the program.\n\n```js\nconst { access, constants } = await import('fs/promises');\n// See if `target/types/todo.ts` exists\nawait access(\n  join(project.dashedName, 'todo/target/types/todo.ts'),\n  constants.F_OK\n);\n```\n\n### --seed--\n\n#### --\"learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs\"--\n\n```rust\nuse anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        let tasks = &mut ctx.accounts.tasks;\n\n        if tasks.tasks.len() < replacing_tasks.len() {\n            let new_space = 8 + 4 + (4 + 32) + 1 * replacing_tasks.len();\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n            let tasks_account_info = tasks.to_account_info();\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n\n            **ctx\n                .accounts\n                .user\n                .to_account_info()\n                .try_borrow_mut_lamports()? -= lamports_diff;\n            **tasks_account_info.try_borrow_mut_lamports()? += lamports_diff;\n\n            tasks_account_info.realloc(new_space, false)?;\n        }\n\n        tasks.tasks = replacing_tasks;\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * (4 + (4 + 32) + 1), payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct TasksAccount {\n    pub tasks: Vec<Task>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    #[msg(\"The task name must be less than 32 characters.\")]\n    TaskNameTooLong,\n    #[msg(\"The task name must be at least 1 character.\")]\n    TaskNameTooShort,\n    #[msg(\"The task id must be unique.\")]\n    TaskIdNotUnique,\n}\n```\n\n## 30\n\n### --description--\n\nWith the program complete, the client app can be wired to use it. The `app/` directory has a React app built with Vite. If you do not know React, do not worry; very little of the integration is React-specific.\n\nWithin `app/src/app.tsx`, under the `TODO:1` comment, attach the `Buffer` to the `window`.\n\n### --tests--\n\nYou should have `window.Buffer = Buffer`.\n\n```js\nconst expectedCodeString = 'window.Buffer=Buffer;';\nconst actualCodeString = __babelisedCode.generateCode(\n  __babelisedCode.parsedCode,\n  {\n    compact: true\n  }\n);\nassert.include(actualCodeString, expectedCodeString);\n```\n\nYou should import `Buffer` from `buffer`.\n\n```js\nconst importDeclaration = __babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === 'buffer';\n});\nassert.exists(importDeclaration, 'You should import from `buffer`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'Buffer');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nglobal.__babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\n```\n\n### --after-all--\n\n```js\ndelete global.__babelisedCode;\n```\n\n## 31\n\n### --description--\n\nWithin `app/` use `yarn` to install the `@solana/web3.js` package.\n\n### --tests--\n\nYou should have `@solana/web3.js` in your `package.json` dependencies.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(join(project.dashedName, 'todo/app/package.json'))\n);\nassert.property(\n  packageJson.dependencies,\n  '@solana/web3.js',\n  'The `package.json` file should have a `@solana/web3.js` dependency.'\n);\n```\n\n## 32\n\n### --description--\n\nUnder the `TODO:2` comment, declare a `PROGRAM_ID` variable, and assign it the value of the program's id as a `PublicKey`.\n\n### --tests--\n\nYou should have `const PROGRAM_ID = new PublicKey(\"...\")`.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/todo`\n);\nconst expectedProgramId = stdout.match(/[^\\s]{44}/)?.[0];\nconst variableDeclaration = __babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'PROGRAM_ID');\nconst actualProgramId =\n  variableDeclaration.declarations?.[0]?.init?.arguments?.[0]?.value;\nassert.equal(actualProgramId, expectedProgramId);\n```\n\nYou should import `PublicKey` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = __babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `@solana/web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'PublicKey');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nglobal.__babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\n```\n\n### --after-all--\n\n```js\ndelete global.__babelisedCode;\n```\n\n## 33\n\n### --description--\n\nUnder the `TODO:3` comment, declare an `ENDPOINT` variable, and assign it the value of `import.meta.VITE_SOLANA_CONNECTION_URL` or default to `http://localhost:8899`.\n\n**Note:** `import.meta.VITE_*` is a way to access environemnt variables in a Vitejs app during build time.\n\n### --tests--\n\nYou should have `const ENDPOINT = import.meta.VITE_SOLANA_CONNECTION_URL || \"http://localhost:8899\";`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /const\\s+ENDPOINT\\s*=\\s*import\\.meta\\.VITE_SOLANA_CONNECTION_URL\\s*\\|\\|/\n);\n```\n\n## 34\n\n### --description--\n\nUnder the `TODO:4` comment, declare a `connection` variable, and assign it the value of a new `Connection` instance with the `ENDPOINT`, and choose a suitable commitment level for a production app.\n\n### --tests--\n\nYou should have `const connection = new Connection(ENDPOINT, ...);`.\n\n```js\nconst variableDeclaration = __babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'connection';\n  });\nassert.exists(\n  variableDeclaration,\n  'A variable named `connection` should exist'\n);\nconst newExpression = variableDeclaration.declarations?.[0]?.init;\nconst callee = newExpression?.callee;\nassert.equal(callee?.name, 'Connection');\nconst args = newExpression?.arguments;\nassert.equal(args?.[0]?.name, 'ENDPOINT');\n```\n\nThe commitment config should be `\"confirmed\"` or `\"finalized\"`.\n\n```js\nconst variableDeclaration = __babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'connection';\n  });\nassert.exists(\n  variableDeclaration,\n  'A variable named `connection` should exist'\n);\nconst newExpression = variableDeclaration.declarations?.[0]?.init;\nconst args = newExpression?.arguments;\nassert.include(['confirmed', 'finalized'], args?.[1]?.value);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = __babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `@solana/web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'Connection');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nglobal.__babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\n```\n\n### --after-all--\n\n```js\ndelete global.__babelisedCode;\n```\n\n## 35\n\n### --description--\n\nWithin `app/` use `yarn` to install the `@solana/wallet-adapter-phantom` package.\n\n### --tests--\n\nYou should have `@solana/wallet-adapter-phantom` in your `package.json` dependencies.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(join(project.dashedName, 'todo/app/package.json'))\n);\nassert.property(\n  packageJson.dependencies,\n  '@solana/wallet-adapter-phantom',\n  'The `package.json` file should have a `@solana/wallet-adapter-phantom` dependency.'\n);\n```\n\n## 36\n\n### --description--\n\nUnder the `TODO:5` comment, declare a `wallet` variable as a new instance of the Phantom wallet adapter.\n\n### --tests--\n\nYou should have `const wallet = new PhantomWalletAdapter()`.\n\n```js\nconst variableDeclaration = __babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'wallet';\n  });\nassert.exists(variableDeclaration, 'A variable named `wallet` should exist');\nconst newExpression = variableDeclaration.declarations?.[0]?.init;\nconst callee = newExpression?.callee;\nassert.equal(callee?.name, 'PhantomWalletAdapter');\n```\n\nYou should import `PhantomWalletAdapter` from `@solana/wallet-adapter-phantom`.\n\n```js\nconst importDeclaration = __babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/wallet-adapter-phantom';\n});\nassert.exists(\n  importDeclaration,\n  'There should be an import from `@solana/wallet-adapter-phantom`'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'PhantomWalletAdapter');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nglobal.__babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\n```\n\n### --after-all--\n\n```js\ndelete global.__babelisedCode;\n```\n\n## 37\n\n### --description--\n\nWithin `app/` use `yarn` to install the `@coral-xyz/anchor` package.\n\n### --tests--\n\nYou should have `@coral-xyz/anchor` in your `package.json` dependencies.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(join(project.dashedName, 'todo/app/package.json'))\n);\nassert.property(\n  packageJson.dependencies,\n  '@coral-xyz/anchor',\n  'The `package.json` file should have a `@coral-xyz/anchor` dependency.'\n);\n```\n\n## 38\n\n### --description--\n\nUnder the `TODO:6` comment, declare a React context variable, `ProgramContext`, for the program in order to access the program throughout the application components.\n\n```typescript\nconst MyContext = createContext<Type | null>(null);\n```\n\n### --tests--\n\nYou should have `const ProgramContext = createContext<Program<Todo> | null>(null);`.\n\n```js\nconst variableDeclaration = __babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'ProgramContext';\n  });\nassert.exists(\n  variableDeclaration,\n  'A variable named `ProgramContext` should exist'\n);\nconst callExpression = variableDeclaration.declarations?.[0]?.init;\nconst { callee, arguments: args, typeParameters } = callExpression;\nassert.equal(callee?.name, 'createContext');\nassert.equal(args?.[0]?.value, null);\n```\n\nYou should import `createContext` from `react`.\n\n```js\nconst importDeclaration = __babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === 'react';\n});\nassert.exists(importDeclaration, 'There should be an import from `react`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'createContext');\n```\n\nYou should import `Program` from `@coral-xyz/anchor`.\n\n```js\nconst importDeclaration = __babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@coral-xyz/anchor';\n});\nassert.exists(\n  importDeclaration,\n  'There should be an import from `@coral-xyz/anchor`'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'Program');\n```\n\nYou should import `Todo` from `../../target/types/todo`.\n\n```js\nconst importDeclaration = __babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '../../target/types/todo';\n});\nassert.exists(\n  importDeclaration,\n  'There should be an import from `../../target/types/todo`'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'Todo');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nglobal.__babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\n```\n\n### --after-all--\n\n```js\ndelete global.__babelisedCode;\n```\n\n## 39\n\n### --description--\n\nUnder the `TODO:7` comment, declare a program state variable:\n\n```typescript\nconst [program, setProgram] = useState<Type | null>(null);\n```\n\n### --tests--\n\nYou should have `const [program, setProgram] = useState<Program<Todo> | null>(null);`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /const\\s*\\[\\s*program\\s*,\\s*setProgram\\s*\\]\\s*=\\s*useState\\s*<\\s*Program\\s*<\\s*Todo\\s*>\\s*\\|\\s*null\\s*>\\s*\\(\\s*null\\s*\\)/\n);\n```\n\n## 40\n\n### --description--\n\nUnder the `TODO:8` comment, connect to the wallet.\n\n### --tests--\n\nYou should have `await wallet.connect()`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(codeString, /await\\s+wallet\\s*\\.\\s*connect\\s*\\(\\s*\\)/);\n```\n\n## 41\n\n### --description--\n\nUnder the `TODO:9` comment, add an `if` statement with a condition to check if the wallet is actually connected. You can use the provided `isWalletConnected` function from `./utils.ts`.\n\n### --tests--\n\nYou should have `if (isWalletConnected(wallet)) {}`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /if\\s*\\(\\s*isWalletConnected\\s*\\(\\s*wallet\\s*\\)\\s*\\)\\s*{\\s*}/\n);\n```\n\nYou should import `isWalletConnected` from `./utils`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils';\n});\nassert.exists(importDeclaration, 'There should be an import from `./utils`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'isWalletConnected');\n```\n\n## 42\n\n### --description--\n\nUnder the `TODO:10` comment, and within the `if` statement, declare a `provider` variable as a new Anchor provider.\n\n### --tests--\n\nYou should have `const provider = new AnchorProvider(connection, wallet, {})`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /const\\s+provider\\s*=\\s*new\\s+AnchorProvider\\s*\\(\\s*connection\\s*,\\s*wallet\\s*,\\s*\\{\\s*\\}\\s*\\)/\n);\n```\n\nYou should import `AnchorProvider` from `@coral-xyz/anchor`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@coral-xyz/anchor';\n});\nassert.exists(\n  importDeclaration,\n  'There should be an import from `@coral-xyz/anchor`'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'AnchorProvider');\n```\n\n## 43\n\n### --description--\n\nUnder the `TODO:11` comment, and within the `if` statement, declare a `program` variable as an instance of an Anchor program.\n\n### --tests--\n\nYou should have `const program = new Program(IDL, PROGRAM_ID, provider)`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /const\\s+program\\s*=\\s*new\\s+Program\\s*\\(\\s*IDL\\s*,\\s*PROGRAM_ID\\s*,\\s*provider\\s*\\)/\n);\n```\n\nYou should import `IDL` from `../../target/types/todo`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '../../target/types/todo';\n});\nassert.exists(\n  importDeclaration,\n  'There should be an import from `../../target/types/todo`'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'IDL');\n```\n\n## 44\n\n### --description--\n\nUnder the `TODO:12` comment, and within the `if` statment, use the `setProgram` function to set the `program` state variable to `program`.\n\n### --tests--\n\nYou should have `setProgram(program)`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(codeString, /setProgram\\s*\\(\\s*program\\s*\\)/);\n```\n\n## 45\n\n### --description--\n\nUnder the `TODO:13` comment, conditionally either render the `Landing` page or the `LogIn` page based on whether the `program` is set:\n\n```tsx\n<>{condition ? <Landing /> : <LogIn connectWallet={connectWallet} />}</>\n```\n\n### --tests--\n\nYou should have `<>{program ? <Landing /> : LogIn connectWallet={connectWallet} />}</>`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'App';\n});\n\nconst returnStatement = functionDeclaration.body.body?.find(\n  b => b.type === 'ReturnStatement'\n);\nconst expressionContainer = returnStatement.argument?.children?.find(c => {\n  return c.expression?.test?.name === 'program';\n});\n\nconst { test, consequent, alternate } = expressionContainer.expression;\nassert.equal(test.name, 'program');\nassert.equal(consequent.openingElement.name?.name, 'Landing');\nassert.equal(alternate.openingElement.name?.name, 'LogIn');\n```\n\n## 46\n\n### --description--\n\nUnder the `TODO:14` comment, replace the component fragment (`<></>`) with a context provider for the program:\n\n```tsx\n<ProgramContext.Provider value={program}></ProgramContext.Provider>\n```\n\n### --tests--\n\nYou should have `<ProgramContext.Provider value={program}>{program ? <Landing /> : LogIn connectWallet={connectWallet} />}</ProgramContext.Provider>`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /<\\s*ProgramContext\\s*\\.\\s*Provider\\s*value\\s*=\\s*\\{\\s*program\\s*\\}\\s*>/\n);\nassert.match(codeString, /<\\/\\s*ProgramContext\\s*\\.\\s*Provider\\s*>/);\n```\n\n## 47\n\n### --description--\n\nThe `Landing` component fetches any tasks associated with a connected account, and handles the main logic for displaying ToDos.\n\nUnder the `TODO:15` comment, declare a `program` variable with the program context hook:\n\n```js\nconst state = useContext(MyContext);\n```\n\n### --tests--\n\nYou should have `const program = useContext(ProgramContext)`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /const\\s+program\\s*=\\s*useContext\\s*\\(\\s*ProgramContext\\s*\\)/\n);\n```\n\nYou should import `useContext` from `react`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['typescript', 'jsx']\n});\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === 'react';\n});\nassert.exists(importDeclaration, 'There should be an import from `react`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(importSpecifiers, 'useContext');\n```\n\n## 48\n\n### --description--\n\nUnder the `TODO:16` comment, add an `if` statement with a condition to check if the program exists.\n\n### --tests--\n\nYou should have `if (program) {}`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(codeString, /if\\s*\\(\\s*program\\s*\\)\\s*{\\s*}/);\n```\n\n## 49\n\n### --description--\n\nUnder the `TODO:17` comment, and within the `if` statement, declare a `tasksPublicKey` variable with a value of the program derived address.\n\n### --tests--\n\nYou should have `const [tasksPublicKey, _] = PublicKey.findProgramAddressSync([program.provider.publicKey.toBuffer()], PROGRAM_ID)`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /const\\s*\\[\\s*tasksPublicKey\\s*,\\s*_\\s*\\]\\s*=\\s*PublicKey\\s*\\.\\s*findProgramAddressSync\\s*\\(\\s*\\[\\s*program\\s*\\.\\s*provider\\s*\\.\\s*publicKey\\s*\\.\\s*toBuffer\\s*\\(\\s*\\)\\s*\\]\\s*,\\s*PROGRAM_ID\\s*\\)/\n);\n```\n\n## 50\n\n### --description--\n\nUnder the `TODO:18` comment, and within the `if` statement, declare a `tasks` variable with a value of the program's task account.\n\n### --tests--\n\nYou should have `const tasks = await program.account.tasksAccount.fetch(tasksPublicKey)`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /const\\s*tasks\\s*=\\s*await\\s*program\\s*\\.\\s*account\\s*\\.\\s*tasksAccount\\s*\\.\\s*fetch\\s*\\(\\s*tasksPublicKey\\s*\\)/\n);\n```\n\n## 51\n\n### --description--\n\nUnder the `TODO:19` comment, and within the `if` statement, call the `setTasks` function with the tasks data.\n\n### --tests--\n\nYou should have `setTasks(tasks.tasks)`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(codeString, /setTasks\\s*\\(\\s*tasks\\s*\\.\\s*tasks\\s*\\)/);\n```\n\n## 52\n\n### --description--\n\nUnder the `TODO:20` comment, ensure the program exists, derive the program address, and save the `tasks` state to the program's task account.\n\n### --tests--\n\nYou should have `if (program) { ... }`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(codeString, /if\\s*\\(\\s*program\\s*\\)\\s*{/);\n```\n\nYou should have `const [tasksPublicKey, _] = PublicKey.findProgramAddressSync([program.provider.publicKey.toBuffer()], PROGRAM_ID)`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /const\\s*\\[\\s*tasksPublicKey\\s*,\\s*_\\s*\\]\\s*=\\s*PublicKey\\s*\\.\\s*findProgramAddressSync\\s*\\(\\s*\\[\\s*program\\s*\\.\\s*provider\\s*\\.\\s*publicKey\\s*\\.\\s*toBuffer\\s*\\(\\s*\\)\\s*\\]\\s*,\\s*PROGRAM_ID\\s*\\)/\n);\n```\n\nYou should have `await program.methods.saveTasks(tasks).accounts({ tasks: tasksPublicKey }).rpc()`.\n\n```js\nconst codeString = await __helpers.getFile(\n  join(project.dashedName, 'todo/app/src/app.tsx')\n);\nassert.match(\n  codeString,\n  /await\\s*program\\s*\\.\\s*methods\\s*\\.\\s*saveTasks\\s*\\(\\s*tasks\\s*\\)\\s*\\.\\s*accounts\\s*\\(\\s*\\{\\s*tasks\\s*:\\s*tasksPublicKey\\s*\\}\\s*\\)\\s*\\.\\s*rpc\\s*\\(\\s*\\)/\n);\n```\n\n## 53\n\n### --description--\n\nYour client app is finished!\n\nBefore testing, set the environment variables. Within `todo/` create a `.env` file, and add `VITE_SOLANA_CONNECTION_URL=http://localhost:8899`.\n\n### --tests--\n\nYou should create a `todo/.env` file.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(join(project.dashedName, 'todo/.env'), constants.F_OK);\n```\n\nYou should set the `VITE_SOLANA_CONNECTION_URL` variable to `http://localhost:8899`.\n\n```js\nconst { readFile } = await import('fs/promises');\nconst envFile = await readFile(join(project.dashedName, 'todo/.env'), 'utf8');\nassert.match(\n  envFile,\n  /VITE_SOLANA_CONNECTION_URL\\s*=\\s*['`\"]?http:\\/\\/localhost:8899/\n);\n```\n\n## 54\n\n### --description--\n\nStart a local Solana cluster with the program deployed.\n\nThen, start the client app with `yarn dev` in the `app/` directory.\n\n### --tests--\n\nYou should start a local Solana cluster.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nYou should deploy the program to the local cluster.\n\n```js\nconst { stdout: keys } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/todo`\n);\nconst expectedProgramId = keys.match(/[^\\s]{44}/)?.[0];\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '\n  {\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"getAccountInfo\",\n    \"params\": [\n      \"${expectedProgramId}\", {\n        \"encoding\": \"base64\",\n        \"dataSlice\": {\n          \"length\": 0,\n          \"offset\": 0\n        }\n      }\n    ]\n}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.equal(jsonOut.result?.value?.executable, true);\n} catch (e) {\n  assert.fail(\n    e,\n    `Try running \\`solana-test-validator --bpf-program ${expectedProgramId} ./target/deploy/todo.so --reset\\``\n  );\n}\n```\n\nYou should start the client app server.\n\n```js\nconst response = await fetch('http://localhost:5173');\nassert.equal(response.status, 200, 'The server should be running.');\n```\n\n## 55\n\n### --description--\n\nOpen the client app in your browser, and connect your Phantom wallet, and play with your app making a few transactions.\n\n### --tests--\n\nYou should perform a few transactions using the client interface.\n\n```js\nconst { Connection, PublicKey } = await import('@solana/web3.js');\nconst connection = new Connection('http://127.0.0.1:8899', 'confirmed');\n\nconst { stdout: keys } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/todo`\n);\nconst expectedProgramId = keys.match(/[^\\s]{44}/)?.[0];\nconst pubkey = new PublicKey(expectedProgramId);\nconst transactions = await connection.getConfirmedSignaturesForAddress2(pubkey);\nassert.isAtLeast(\n  transactions,\n  2,\n  'Try using the client interface and your wallet to make a few transactions'\n);\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-deploy-to-devnet.md",
    "content": "# Solana - Learn How to Deploy to Devnet\n\n## 1\n\n### --description--\n\nYou have been started with the same programa and app as the previous project. This time, you will deploy it to the public Devnet!\n\nFirst, start by ensuring it still works locally:\n\n1. Install all dependencies\n2. Build the program\n3. Start a local Solana cluster\n   - Deploy the program to the local cluster\n4. Start the client server\n5. Test the program\n\n### --tests--\n\nYou should run `yarn` in the `learn-how-to-deploy-to-devnet/todo/` directory to install all dependencies.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(join(project.dashedName, 'todo/node_modules'), constants.F_OK);\n```\n\nYou should run `anchor build` in the `learn-how-to-deploy-to-devnet/todo/` directory to build the program.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(join(project.dashedName, 'todo/target'), constants.F_OK);\n```\n\nYou should have a local cluster running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nYou should deploy the program to the local cluster.\n\n```js\nconst { stdout: keys } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/todo`\n);\nconst expectedProgramId = keys.match(/[^\\s]{44}/)?.[0];\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '\n  {\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"getAccountInfo\",\n    \"params\": [\n      \"${expectedProgramId}\", {\n        \"encoding\": \"base64\",\n        \"dataSlice\": {\n          \"length\": 0,\n          \"offset\": 0\n        }\n      }\n    ]\n}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\n\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.equal(jsonOut.result?.value?.executable, true);\n} catch (e) {\n  assert.fail(\n    e,\n    `Try running \\`solana-test-validator --bpf-program ${expectedProgramId} ./target/deploy/todo.so --reset\\``\n  );\n}\n```\n\nYou should run `yarn dev` in the `learn-how-to-deploy-to-devnet/todo/app/` directory to start the client server.\n\n```js\nconst response = await fetch('http://localhost:5173');\nassert.equal(response.status, 200, 'The server should be running.');\n```\n\nYou should perform some transactions using the client app.\n\n```js\nconst { Connection, PublicKey } = await import('@solana/web3.js');\nconst connection = new Connection('http://127.0.0.1:8899', 'confirmed');\n\nconst { stdout: keys } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/todo`\n);\nconst expectedProgramId = keys.match(/[^\\s]{44}/)?.[0];\nconst pubkey = new PublicKey(expectedProgramId);\nconst transactions = await connection.getConfirmedSignaturesForAddress2(pubkey);\nassert.isAtLeast(\n  transactions,\n  2,\n  'Try using the client interface and your wallet to make a few transactions'\n);\n```\n\n## 2\n\n### --description--\n\nIt is often good practice to _vet_ a public program befor using it. In the best case scenario, you can vet the source code yourself to ensure it is safe.\n\nWith programs deployed as bytecode, it is difficult to determine what the program does. Anchor provides a tool to help you verify a program matches the source code:\n\n```bash\nanchor verify <PROGRAM_ID>\n```\n\nProvided you have the supposed source code for a program, you can run that command in the program directory to verify it is the source code for the public program id.\n\nVerify the program you deployed to the local cluster matches its source code.\n\n### --tests--\n\nYou should run `anchor verify <PROGRAM_ID>` in the `learn-how-to-deploy-to-devnet/todo/programs/todo` directory to verify the program.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nconst { stdout: keys } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/todo`\n);\nconst expectedProgramId = keys.match(/[^\\s]{44}/)?.[0];\nassert.include(lastCommand, `anchor verify \"${expectedProgramId}\"`);\n```\n\n## 3\n\n### --description--\n\nIn order to work with the public Devnet, you will need to create a wallet. Instead of creating a wallet directly in Phantom, create a wallet using the Solana CLI. However, in order for this wallet to be compatible with Phantom, you will need to create the wallet using the `--derivation-path` flag.\n\nA derivation path is a way to create a wallet that is compatible with other wallets. Phantom uses the derivation path `m/44'/501'/0'/0'` to create wallets.\n\nSave this wallet to `learn-how-to-deploy-to-devnet/todo/wallet.json`.\n\n### --tests--\n\nYou should run `solana-keygen new --outfile wallet.json --derivation-path` in the `learn-how-to-deploy-to-devnet/todo/` directory to create a wallet.\n\n```js\nconst { access, constants } = await import('fs/promises');\nawait access(join(project.dashedName, 'todo/wallet.json'), constants.F_OK);\n```\n\n## 4\n\n### --description--\n\nNow, to work on the Devnet, change your Solana config to use the Devnet URL.\n\n### --tests--\n\nYou should run `solana config set --url devnet`.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'solana config set --url devnet');\n```\n\n## 5\n\n### --description--\n\nIn order to deploy to Devnet, you will need to have enough SOL to pay for the transaction fees.\n\nAs Devnet is considered a testing network, you can get SOL by requesting an airdrop to your public key.\n\nAirdrop 2 SOL to your public key.\n\n### --tests--\n\nYou should run `solana airdrop 2 --keypair wallet.json` in the `learn-how-to-deploy-to-devnet/todo/` directory to airdrop 2 SOL to your public key.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ${project.dashedName}/todo/wallet.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAtLeast(\n  parseInt(balance),\n  2,\n  'Try running `solana airdrop 2 --keypair wallet.json` within `todo/`'\n);\n```\n\n## 6\n\n### --description--\n\nNow that you have a wallet with SOL, you can deploy the program to Devnet.\n\nFirst, adjust the `Anchor.toml` file so the `provider.cluster` points to Devnet.\n\n### --tests--\n\nYou should have `cluster = \"devnet\"` in the `learn-how-to-deploy-to-devnet/todo/Anchor.toml` file.\n\n```js\nconst codeString = await __helpers.readFile(\n  join(project.dashedName, 'todo/Anchor.toml')\n);\nassert.match(codeString, /cluster\\s*=\\s*\"devnet\"/);\n```\n\n## 7\n\n### --description--\n\nAdjust the wallet path in the `Anchor.toml` file to point to the wallet you created.\n\n### --tests--\n\nYou should have `wallet = \"./wallet.json\"` in the `learn-how-to-deploy-to-devnet/todo/Anchor.toml` file.\n\n```js\nconst codeString = await __helpers.readFile(\n  join(project.dashedName, 'todo/Anchor.toml')\n);\nassert.match(codeString, /wallet\\s*=\\s*\"\\.\\/wallet\\.json\"/);\n```\n\n## 8\n\n### --description--\n\nAdd a `[programs.devnet]` section to the `Anchor.toml` file, and add a `todo` key with the value of the program id you will deploy to.\n\n### --tests--\n\nYou should have a `[programs.devnet]` section in the `learn-how-to-deploy-to-devnet/todo/Anchor.toml` file.\n\n```js\nconst codeString = await __helpers.readFile(\n  join(project.dashedName, 'todo/Anchor.toml')\n);\nassert.match(codeString, /\\[programs\\.devnet\\]/);\n```\n\nYou should have a `todo = \"<PROGRAM_ID>\"` key in the `[programs.devnet]` section.\n\n```js\nconst codeString = await __helpers.readFile(\n  join(project.dashedName, 'todo/Anchor.toml')\n);\nconst { stdout: keys } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/todo`\n);\nconst expectedProgramId = keys.match(/[^\\s]{44}/)?.[0];\nconst actualProgramId = codeString.match(\n  /\\[programs\\.devnet\\]\\s*todo\\s*=\\s*\"([^\"]{44})\"/\n)?.[1];\nassert.equal(expectedProgramId, actualProgramId);\n```\n\n## 9\n\n### --description--\n\nUse the Anchor CLI to deploy the program to Devnet. _This should fail_.\n\n### --tests--\n\nYou should run `anchor deploy` in the `learn-how-to-deploy-to-devnet/todo/` directory and see an error.\n\n```js\n// TODO\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, 'insufficient funds for fee');\n```\n\n## 10\n\n### --description--\n\nYou do not have enough funds to pay for the program deployment transactions. You will need to airdrop more SOL to your wallet.\n\nAirdrop 2 SOL to your `wallet.json` account. _This should fail_.\n\n### --tests--\n\nYou should run `solana airdrop 2 --keypair wallet.json` in `learn-how-to-deploy-to-devnet/todo/`.\n\n```js\n// TODO\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, '');\n```\n\n## 11\n\n### --description--\n\nYou have been rate limited by the Solana Devnet. You can wait a few minutes before airdropping again, or:\n\n```bash\n# Create a temporary wallet\ntodo/ $ solana-keygen new --outfile temp.json --derivation-path\n# Airdrop 2 SOL to the temporary wallet\ntodo/ $ solana airdrop 2 --keypair temp.json\n# Set the Solana CLI to use the temporary wallet\ntodo/ $ solana config set --keypair temp.json\n# Transfer 1.95 SOL from the temporary wallet to your wallet\ntodo/ $ solana transfer 1.95 wallet.json\n```\n\n### --tests--\n\nYour `wallet.json` account should have `3.95` SOL.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ${project.dashedName}/todo/wallet.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAtLeast(parseInt(balance), 3.95);\n```\n\n## 12\n\n### --description--\n\nDeploy the program to Devnet.\n\n_This usually takes a few minutes to complete._\n\n### --tests--\n\nYou should run `anchor deploy` in the `learn-how-to-deploy-to-devnet/todo/` directory.\n\n```js\n// TODO\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, 'Deploying program to chain');\n```\n\n## 13\n\n### --description--\n\nVerify the program matches the source code.\n\n### --tests--\n\nYou should run `anchor verify \"<PROGRAM_ID>\"` in the `learn-how-to-deploy-to-devnet/todo/programs/todo` directory to verify the program.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nconst { stdout: keys } = await __helpers.getCommandOutput(\n  'anchor keys list',\n  `${project.dashedName}/todo`\n);\nconst expectedProgramId = keys.match(/[^\\s]{44}/)?.[0];\nassert.include(lastCommand, `anchor verify \"${expectedProgramId}\"`);\n```\n\n## 14\n\n### --description--\n\nWith your program deployed, ensure your `.env` file has the correct program id and cluster endpoint.\n\nThen start the app client server.\n\n### --tests--\n\nYou should run `yarn dev` in the `learn-how-to-deploy-to-devnet/todo/app/` directory.\n\n```js\nconst response = await fetch('http://localhost:5173');\nassert.equal(response.status, 200, 'The client server should be running.');\n```\n\n## 15\n\n### --description--\n\nIn your browser, import your wallet into Phantom:\n\n![add account](../../images/devnet/image.png)\n\n![use your secret recovery phrase](../../images/devnet/image-1.png)\n\nAfter inputting your secret recovery phrase, you should see your wallet in Phantom.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 16\n\n### --description--\n\nOpen the app in your browser, and perform some transactions.\n\n### --tests--\n\nYou should type `done` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## 17\n\n### --description--\n\nCongratulations! You have deployed your program to Devnet!\n\n**Summary**\n\n1. Adjust all config files to use Devnet\n2. Create a wallet using the Solana CLI\n3. Airdrop SOL to your wallet\n4. Use `anchor verify` to verify any programs you use match their source code\n5. Deploy the program to Devnet\n6. Share your app with others\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-interact-with-on-chain-programs.md",
    "content": "# Solana - Learn How to Interact with On-Chain Programs\n\n## 1\n\n### --description--\n\nWelcome to the second Solana project! For the duration of this project, you will be working in the `learn-how-to-interact-with-on-chain-programs/` directory.\n\nChange into the above directory in your bash terminal.\n\n### --tests--\n\nYou should use `cd` to change into the `learn-how-to-interact-with-on-chain-programs/` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-how-to-interact-with-on-chain-programs');\n```\n\n## 2\n\n### --description--\n\nPreviously, you developed a smart contract that kept count of the number of times it was invoked.\n\nNow, you will develop a client to call your smart contract. Start by building your smart contract with:\n\n```bash\nnpm run build\n```\n\nIt will take a moment.\n\n### --tests--\n\nYou should run `npm run build` in the terminal.\n\n```js\nawait new Promise(res => setTimeout(res, 1000));\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(lastCommand.trim(), 'npm run build');\n```\n\nYou should be in the `learn-how-to-interact-with-on-chain-programs` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-how-to-interact-with-on-chain-programs');\n```\n\n## 3\n\n### --description--\n\nWithin the `src` directory, create a directory named `client` to hold your code.\n\n### --tests--\n\nYou should have a `src/client` directory.\n\n```js\nconst pathExists = __helpers.fileExists(\n  'learn-how-to-interact-with-on-chain-programs/src/client'\n);\nassert.isTrue(pathExists, 'You should have a `src/client` directory.');\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\nnpm run build\n```\n\n## 4\n\n### --description--\n\nWithin the `src/client` directory, create a file named `main.js` which will be the entrypoint of your script.\n\n### --tests--\n\nYou should have a `src/client/main.js` file.\n\n```js\nconst pathExists = __helpers.fileExists(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nassert.isTrue(pathExists, 'You should have a `src/client/main.js` file.');\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\nmkdir src/client\n```\n\n## 5\n\n### --description--\n\nWithin `src/client/main.js`, create an asynchronous function named `main`.\n\n### --tests--\n\nYou should have an asynchronous function with the handle `main`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst mainFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'main');\nassert.exists(\n  mainFunctionDeclaration,\n  'You should have a function named `main`'\n);\nassert(\n  mainFunctionDeclaration.async,\n  'main should be an asynchronous function'\n);\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\ntouch src/client/main.js\n```\n\n## 6\n\n### --description--\n\nCall your `main` function, awaiting the process before exiting.\n\n### --tests--\n\nYou should call `main` with `await`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst mainExpressionStatement = babelisedCode.getExpressionStatement('main');\nassert.exists(mainExpressionStatement, 'You should call `main`');\nassert.equal(\n  mainExpressionStatement?.expression?.type,\n  'AwaitExpression',\n  'You should call `main` with `await`'\n);\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nasync function main() {}\n```\n\n## 7\n\n### --description--\n\nWithin the `main` function, log to the console the string `Saying 'hello' to a Solana account`.\n\n### --tests--\n\nYou should have `console.log(\"Saying 'hello' to a Solana account\")` within `main`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst consoleLogExpressionStatement =\n  babelisedCode.getExpressionStatement('console.log');\nassert.exists(\n  consoleLogExpressionStatement,\n  'You should have a `console.log` statement'\n);\nassert.equal(\n  consoleLogExpressionStatement.scope.join('.'),\n  'global.main',\n  'The console.log should be within the main function'\n);\nconst arg = consoleLogExpressionStatement?.expression?.arguments?.[0];\nconst code = babelisedCode.generateCode(arg);\nassert.match(\n  code,\n  /(\"|'|`)Saying (\"|'|`)hello\\2 to a Solana account\\1/,\n  'You should have `console.log(\"Saying \\'hello\\' to a Solana account\")`'\n);\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nasync function main() {}\n\nawait main();\n```\n\n## 8\n\n### --description--\n\nAlongside the entrypoint for this program, you will need a module to hold all the methods needed to interact with your smart contract.\n\nWithin the `src/client` directory, create a file named `hello-world.js`.\n\n### --tests--\n\nYou should have a `src/client/hello-world.js` file.\n\n```js\nconst pathExists = __helpers.fileExists(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nassert.isTrue(\n  pathExists,\n  'You should have a `src/client/hello-world.js` file.'\n);\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nasync function main() {\n  console.log(\"Saying 'hello' to a Solana account\");\n}\n\nawait main();\n```\n\n## 9\n\n### --description--\n\nWithin `hello-world.js`, export a function named `establishConnection`.\n\n### --tests--\n\nYou should define a function named `establishConnection` in `src/client/hello-world.js`.\n\n```js\nconst establishConnectionFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'establishConnection';\n  });\nassert.exists(\n  establishConnectionFunctionDeclaration,\n  'You should define a function named `establishConnection` in `src/client/hello-world.js`'\n);\n```\n\nYou should export `establishConnection` as a named export.\n\n```js\nconst establishConnectionFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'establishConnection';\n  });\nassert.exists(\n  establishConnectionFunctionDeclaration,\n  'You should define a function named `establishConnection` in `src/client/hello-world.js`'\n);\n\nconst establishConnectionExportNamedDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    const name = e.declaration?.id?.name;\n    return name === 'establishConnection';\n  });\nassert.exists(\n  establishConnectionExportNamedDeclaration,\n  'You should export `establishConnection` as a named export'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\ntouch src/client/hello-world.js\n```\n\n## 10\n\n### --description--\n\nThe `web3.js` module from Solana provides all the functionality you will need to interact with the Solana blockchain.\n\nIn your `package.json` file, `@solana/web3.js` is included as a dependency, along with `borsh`. Run `npm install` to install them.\n\n### --tests--\n\nYou should run `npm install` in your project folder to install the modules\n\n```js\nawait new Promise(res => setTimeout(res, 1000));\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand.trim(), /npm\\s+(i|install)/);\n```\n\nYou should have a `node_modules/@solana/web3.js` folder as a result of installing the dependencies\n\n```js\nconst dir = await __helpers.getDirectory(\n  'learn-how-to-interact-with-on-chain-programs/node_modules/@solana'\n);\n\nassert.include(dir, 'web3.js');\n```\n\nYou should have a `node_modules/borsh` folder as a result of installing the dependencies\n\n```js\nconst dir = await __helpers.getDirectory(\n  'learn-how-to-interact-with-on-chain-programs/node_modules'\n);\n\nassert.include(dir, 'borsh');\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nexport function establishConnection() {}\n```\n\n## 11\n\n### --description--\n\nWithin `establishConnection`, connect to your local cluster, using the `Connection` class from the `@solana/web3.js` module.\n\nReturn this new connection from `establishConnection`.\n\n### --tests--\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'You should import from `@solana/web3.js`'\n);\nconst connectionImportSpecifier = solanaWeb3ImportDeclaration.specifiers.find(\n  s => {\n    return s.imported.name === 'Connection';\n  }\n);\nassert.exists(\n  connectionImportSpecifier,\n  'You should import `Connection` from `@solana/web3.js`'\n);\n```\n\nYou should create a new connection with `new Connection('http://localhost:8899')`.\n\n```js\nconst newConnectionExpression = babelisedCode\n  .getType('NewExpression')\n  .find(e => {\n    return e.callee.name === 'Connection';\n  });\nassert.exists(\n  newConnectionExpression,\n  'You should create a new connection with `new Connection()`'\n);\nassert.equal(\n  newConnectionExpression.scope.join(),\n  'global,establishConnection',\n  'You should create the new connection within the `establishConnection` function'\n);\nassert.equal(\n  newConnectionExpression.arguments[0].value,\n  'http://localhost:8899',\n  \"You should create a new connection with `new Connection('http://localhost:8899')`\"\n);\n```\n\nYour `establishConnection` function should return the new connection.\n\n```js\nconst { Connection } = await __helpers.importSansCache(\n  './learn-how-to-interact-with-on-chain-programs/node_modules/@solana/web3.js/lib/index.cjs.js'\n);\nconst { establishConnection } = await __helpers.importSansCache(\n  './learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst connection = establishConnection();\nassert.instanceOf(\n  connection,\n  Connection,\n  'Your `establishConnection` function should return the new connection'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\nnpm install @solana/web3.js@1\n```\n\n## 12\n\n### --description--\n\nWithin the `main` function in `main.js`, make a call to `establishConnection`, and store the value in a variable named `connection`.\n\n### --tests--\n\nYou should have `const connection = establishConnection()` in `main.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'connection';\n  });\nassert.exists(\n  connectionVariableDeclaration,\n  'You should declare a variable named `connection` in `main.js`'\n);\nassert.equal(\n  connectionVariableDeclaration.scope.join(),\n  'global,main',\n  'You should declare the `connection` variable within the `main` function'\n);\nconst init = connectionVariableDeclaration?.declarations?.[0]?.init;\nassert.exists(\n  init,\n  'You should initialise the `connection` variable in `main.js`'\n);\nassert.equal(\n  init?.callee?.name,\n  'establishConnection',\n  'You should initialise the `connection` variable with `establishConnection()`'\n);\n```\n\nYou should import `establishConnection` from `./hello-world.js`.\n\n```js\nconst helloWorldImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === './hello-world.js';\n  });\nassert.exists(\n  helloWorldImportDeclaration,\n  'You should import from `./hello-world.js`'\n);\nconst establishConnectionImportSpecifier =\n  helloWorldImportDeclaration.specifiers.find(s => {\n    return s.imported.name === 'establishConnection';\n  });\nassert.exists(\n  establishConnectionImportSpecifier,\n  'You should import `establishConnection` from `./hello-world.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n```\n\n## 13\n\n### --description--\n\nCreating transactions takes compute power. So, an account has to pay for any transaction made.\n\nWithin `hello-world.js`, export a function named `establishPayer`.\n\n### --tests--\n\nYou should define a function with the handle `establishPayer`.\n\n```js\nconst getProgramIdFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'establishPayer';\n  });\nassert.exists(\n  getProgramIdFunctionDeclaration,\n  'You should define a function named `establishPayer` in `src/client/hello-world.js`'\n);\n```\n\nYou should define `establishPayer` as asynchronous.\n\n```js\nconst getProgramIdFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'establishPayer';\n  });\nassert.isTrue(\n  getProgramIdFunctionDeclaration.async,\n  'You should define `establishPayer` as being asynchronous'\n);\n```\n\nYou should export `establishPayer` as a named export.\n\n```js\nconst getProgramIdExportNamedDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    const name = e.declaration?.id?.name;\n    return name === 'establishPayer';\n  });\nassert.exists(\n  getProgramIdExportNamedDeclaration,\n  'You should export `establishPayer` as a named export'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nimport { establishConnection } from './hello-world.js';\n\nasync function main() {\n  console.log(\"Saying 'hello' to a Solana account\");\n  const connection = establishConnection();\n}\n\nawait main();\n```\n\n## 14\n\n### --description--\n\nWithin `establishPayer`, declare a variable `secretKeyString`, and assign it the value of your local account keypair:\n\n```js\nawait readFile('../../../root/.config/solana/id.json', 'utf8');\n```\n\n### --tests--\n\nYou should import `readFile` from `fs/promises`.\n\n```js\nconst readFileImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === 'fs/promises';\n  });\nassert.exists(\n  readFileImportDeclaration,\n  'You should import `readFile` from `fs/promises`'\n);\n```\n\nYou should define a variable named `secretKeyString` within `establishPayer`.\n\n```js\nconst secretKeyStringVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'secretKeyString' &&\n      v.scope.join() === 'global,establishPayer'\n    );\n  });\nassert.exists(\n  secretKeyStringVariableDeclaration,\n  'You should define a variable named `secretKeyString`'\n);\n```\n\n`secretKeyString` should be assigned the result of `readFile`.\n\n```js\nconst secretKeyStringVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'secretKeyString' &&\n      v.scope.join() === 'global,establishPayer'\n    );\n  });\nconst awaitExpression =\n  secretKeyStringVariableDeclaration?.declarations?.[0]?.init;\nassert.exists(\n  awaitExpression,\n  '`secretKeyString` should be assigned the result of awaiting `readFile`'\n);\nconst readFileCallExpression = awaitExpression?.argument;\nassert.equal(\n  readFileCallExpression?.callee?.name,\n  'readFile',\n  '`secretKeyString` should be assigned the result of awaiting `readFile`'\n);\n```\n\nYou should pass `\"../../../root/.config/solana/id.json\"` as the first argument to `readFile`.\n\n```js\nconst readFileCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return (\n      c.callee.name === 'readFile' &&\n      c.scope.includes('global') &&\n      c.scope.includes('establishPayer')\n    );\n  });\nassert.exists(\n  readFileCallExpression,\n  'You should pass `\"../../../root/.config/solana/id.json\"` as the first argument to `readFile`'\n);\nconst firstArgument = readFileCallExpression.arguments?.[0]?.value;\nassert.equal(\n  readFileCallExpression?.arguments?.[0]?.value,\n  '../../../root/.config/solana/id.json',\n  'You should pass `../../../root/.config/solana/id.json` as the first argument to `readFile`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {}\n```\n\n## 15\n\n### --description--\n\nThe payer keypair needs to be constructed from an array of 8-bit unsigned integers.\n\nWithin `establishPayer`, define a variable `secretKey`, and assign it the value of:\n\n```js\nUint8Array.from(JSON.parse(secretKeyString));\n```\n\n### --tests--\n\nYou should define a variable named `secretKey` within `establishPayer`.\n\n```js\nconst secretKeyVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'secretKey' &&\n      v.scope.join() === 'global,establishPayer'\n    );\n  });\nassert.exists(\n  secretKeyVariableDeclaration,\n  'You should define a variable named `secretKey`'\n);\n```\n\n`secretKey` should be assigned the result of `Uint8Array.from`.\n\n```js\nconst secretKeyVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'secretKey' &&\n      v.scope.join() === 'global,establishPayer'\n    );\n  });\nconst callExpression = secretKeyVariableDeclaration?.declarations?.[0]?.init;\nconst uintMemberExpression = callExpression?.callee;\nassert.equal(\n  uintMemberExpression?.object?.name,\n  'Uint8Array',\n  '`secretKey` should be assigned the result of `Uint8Array.from`'\n);\nassert.equal(\n  uintMemberExpression?.property?.name,\n  'from',\n  '`secretKey` should be assigned the result of `Uint8Array.from`'\n);\n```\n\nYou should pass the result of `JSON.parse(secretKeyString)` as the first argument to `Uint8Array.from`.\n\n```js\nconst secretKeyVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'secretKey' &&\n      v.scope.join() === 'global,establishPayer'\n    );\n  });\nconst callExpression = secretKeyVariableDeclaration?.declarations?.[0]?.init;\nconst jsonCallExpression = callExpression?.arguments?.[0];\nconst jsonMemberExpression = jsonCallExpression?.callee;\nassert.equal(\n  jsonMemberExpression?.object?.name,\n  'JSON',\n  'You should pass the result of `JSON.parse(secretKeyString)` as the first argument to `Uint8Array.from`'\n);\nassert.equal(\n  jsonMemberExpression?.property?.name,\n  'parse',\n  'You should pass the result of `JSON.parse(secretKeyString)` as the first argument to `Uint8Array.from`'\n);\nassert.equal(\n  jsonCallExpression?.arguments?.[0]?.name,\n  'secretKeyString',\n  'You should pass the result of `JSON.parse(secretKeyString)` as the first argument to `Uint8Array.from`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n}\n```\n\n## 16\n\n### --description--\n\nWithin `establishPayer`, return the value of calling the `fromSecretKey` method on `Keypair`, passing `secretKey` as the first argument.\n\n### --tests--\n\nYou should return the result of calling the `fromSecretKey` method on `Keypair`.\n\n```js\nconst returnStatement = babelisedCode.getType('ReturnStatement').find(r => {\n  return r.scope.join() === 'global,establishPayer';\n});\nassert.exists(returnStatement, 'No return statement found');\nconst callExpression = returnStatement?.argument;\nassert.equal(\n  callExpression?.callee?.object?.name,\n  'Keypair',\n  'You should return the result of calling `Keypair.`'\n);\nassert.equal(\n  callExpression?.callee?.property?.name,\n  'fromSecretKey',\n  'You should return the result of calling `Keypair.fromSecretKey`'\n);\n```\n\nYou should pass `secretKey` as the first argument to `fromSecretKey`.\n\n```js\nconst returnStatement = babelisedCode.getType('ReturnStatement').find(r => {\n  return r.scope.join() === 'global,establishPayer';\n});\nconst callExpression = returnStatement?.argument;\nassert.equal(\n  callExpression?.arguments?.[0]?.name,\n  'secretKey',\n  'You should pass `secretKey` as the first argument to `fromSecretKey`'\n);\n```\n\nYou should import `Keypair` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nconst specifier = importDeclaration?.specifiers?.find(s => {\n  return s.imported.name === 'Keypair';\n});\nassert.exists(specifier, 'You should import `Keypair` from `@solana/web3.js`');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n}\n```\n\n## 17\n\n### --description--\n\nInteracting with an on-chain program requires its program id.\n\nWithin `hello-world.js`, export an asynchronous function with the handle `getProgramId`.\n\n### --tests--\n\nYou should define a function with the handle `getProgramId`.\n\n```js\nconst getProgramIdFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'getProgramId';\n  });\nassert.exists(\n  getProgramIdFunctionDeclaration,\n  'You should define a function named `getProgramId` in `src/client/hello-world.js`'\n);\n```\n\nYou should define `getProgramId` as asynchronous.\n\n```js\nconst getProgramIdFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'getProgramId';\n  });\nassert.isTrue(\n  getProgramIdFunctionDeclaration.async,\n  'You should define `getProgramId` as being asynchronous'\n);\n```\n\nYou should export `getProgramId` as a named export.\n\n```js\nconst getProgramIdExportNamedDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    const name = e.declaration?.id?.name;\n    return name === 'getProgramId';\n  });\nassert.exists(\n  getProgramIdExportNamedDeclaration,\n  'You should export `getProgramId` as a named export'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n```\n\n## 18\n\n### --description--\n\nThe program id is its public key. You can derive it from the program's keypair.\n\nWithin `getProgramId`, declare a variable `secretKeyString`, and assign it the value of:\n\n```js\nawait readFile(<PATH_TO_KEYPAIR_JSON>, 'utf8');\n```\n\nWhere `<PATH_TO_KEYPAIR_JSON>` is the keypair json file path (relative to this project's root) created when building the smart contract.\n\n### --tests--\n\nYou should define a variable named `secretKeyString` within `getProgramId`.\n\n```js\nconst secretKeyStringVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'secretKeyString' &&\n      v.scope.join() === 'global,getProgramId'\n    );\n  });\nassert.exists(\n  secretKeyStringVariableDeclaration,\n  'You should define a variable named `secretKeyString`'\n);\n```\n\n`secretKeyString` should be assigned the result of `readFile`.\n\n```js\nconst secretKeyStringVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'secretKeyString' &&\n      v.scope.join() === 'global,getProgramId'\n    );\n  });\nconst awaitExpression =\n  secretKeyStringVariableDeclaration?.declarations?.[0]?.init;\nassert.exists(\n  awaitExpression,\n  '`secretKeyString` should be assigned the result of awaiting `readFile`'\n);\nconst readFileCallExpression = awaitExpression?.argument;\nassert.equal(\n  readFileCallExpression?.callee?.name,\n  'readFile',\n  '`secretKeyString` should be assigned the result of awaiting `readFile`'\n);\n```\n\nYou should pass `dist/program/helloworld-keypair.json` as the first argument to `readFile`.\n\n```js\nconst readFileCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return (\n      c.callee.name === 'readFile' &&\n      c.scope.includes('global') &&\n      c.scope.includes('getProgramId')\n    );\n  });\nassert.exists(\n  readFileCallExpression,\n  'You should pass `dist/program/helloworld-keypair.json` as the first argument to `readFile`'\n);\nconst firstArgument = readFileCallExpression.arguments?.[0]?.value;\nconst urlToAssert = new URL(firstArgument, 'file://');\nassert.equal(\n  readFileCallExpression?.arguments?.[0]?.value,\n  'dist/program/helloworld-keypair.json',\n  'You should pass `dist/program/helloworld-keypair.json` as the first argument to `readFile`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair } from '@solana/web3.js';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {}\n```\n\n## 19\n\n### --description--\n\nWithin `getProgramId`, define a variable `secretKey`, and assign it the value of:\n\n```js\nUint8Array.from(JSON.parse(secretKeyString));\n```\n\n### --tests--\n\nYou should define a variable named `secretKey`.\n\n```js\nconst secretKeyVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'secretKey';\n  });\nassert.exists(\n  secretKeyVariableDeclaration,\n  'You should define a variable named `secretKey`'\n);\n```\n\n`secretKey` should be assigned the result of `Uint8Array.from`.\n\n```js\nconst secretKeyVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'secretKey';\n  });\nconst callExpression = secretKeyVariableDeclaration?.declarations?.[0]?.init;\nconst uintMemberExpression = callExpression?.callee;\nassert.equal(\n  uintMemberExpression?.object?.name,\n  'Uint8Array',\n  '`secretKey` should be assigned the result of `Uint8Array.from`'\n);\nassert.equal(\n  uintMemberExpression?.property?.name,\n  'from',\n  '`secretKey` should be assigned the result of `Uint8Array.from`'\n);\n```\n\nYou should pass the result of `JSON.parse(secretKeyString)` as the first argument to `Uint8Array.from`.\n\n```js\nconst secretKeyVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'secretKey';\n  });\nconst callExpression = secretKeyVariableDeclaration?.declarations?.[0]?.init;\nconst jsonCallExpression = callExpression?.arguments?.[0];\nconst jsonMemberExpression = jsonCallExpression?.callee;\nassert.equal(\n  jsonMemberExpression?.object?.name,\n  'JSON',\n  'You should pass the result of `JSON.parse(secretKeyString)` as the first argument to `Uint8Array.from`'\n);\nassert.equal(\n  jsonMemberExpression?.property?.name,\n  'parse',\n  'You should pass the result of `JSON.parse(secretKeyString)` as the first argument to `Uint8Array.from`'\n);\nassert.equal(\n  jsonCallExpression?.arguments?.[0]?.name,\n  'secretKeyString',\n  'You should pass the result of `JSON.parse(secretKeyString)` as the first argument to `Uint8Array.from`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n}\n```\n\n## 20\n\n### --description--\n\nWithin `getProgramId`, define a variable `keypair`, and assign it the value of calling the `fromSecretKey` method on `Keypair`, passing `secretKey` as the first argument.\n\n### --tests--\n\nYou should define a variable named `keypair`.\n\n```js\nconst keypairVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'keypair';\n  });\nassert.exists(\n  keypairVariableDeclaration,\n  'You should define a variable named `keypair`'\n);\nassert.equal(\n  keypairVariableDeclaration.scope.join(),\n  'global,getProgramId',\n  'You should define `keypair` within `getProgramId`'\n);\n```\n\n`keypair` should be assigned the result of calling the `fromSecretKey` method on `Keypair`.\n\n```js\nconst keypairVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'keypair';\n  });\nconst callExpression = keypairVariableDeclaration?.declarations?.[0]?.init;\nconst fromSecretKeyMemberExpression = callExpression?.callee;\nassert.equal(\n  fromSecretKeyMemberExpression?.object?.name,\n  'Keypair',\n  '`keypair` should be assigned the result of calling the `fromSecretKey` method on `Keypair`'\n);\nassert.equal(\n  fromSecretKeyMemberExpression?.property?.name,\n  'fromSecretKey',\n  '`keypair` should be assigned the result of calling the `fromSecretKey` method on `Keypair`'\n);\n```\n\nYou should pass `secretKey` as the first argument to `fromSecretKey`.\n\n```js\nconst keypairVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'keypair';\n  });\nconst callExpression = keypairVariableDeclaration?.declarations?.[0]?.init;\nconst secretKeyArgument = callExpression?.arguments?.[0];\nassert.equal(\n  secretKeyArgument?.name,\n  'secretKey',\n  'You should pass `secretKey` as the first argument to `fromSecretKey`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n}\n```\n\n## 21\n\n### --description--\n\nWithin `getProgramId`, return the `publicKey` property of `keypair`.\n\n### --tests--\n\nYou should return the `publicKey` property of `keypair`.\n\n```js\nconst returnStatement = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'getProgramId';\n});\nconst blockStatement = returnStatement?.body?.body;\nconst returnExpression = blockStatement?.find(b => {\n  return b.type === 'ReturnStatement';\n});\nassert.equal(\n  returnExpression?.argument?.object?.name,\n  'keypair',\n  'You should return the `publicKey` property of `keypair`'\n);\nassert.equal(\n  returnExpression?.argument?.property?.name,\n  'publicKey',\n  'You should return the `publicKey` property of `keypair`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n}\n```\n\n## 22\n\n### --description--\n\nIn Solana, program accounts (smart contracts) are stateless. As such, separate accounts (data accounts) need to be created to persist data.\n\nWithin `hello-world.js`, export an asynchronous function with the handle `getAccountPubkey`. This function should expect two arguments: `payer` and `programId`.\n\n### --tests--\n\nYou should define a function with the handle `getAccountPubkey`.\n\n```js\nconst getAccountPubkeyFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'getAccountPubkey';\n  });\nassert.exists(\n  getAccountPubkeyFunctionDeclaration,\n  'You should define a function with the handle `getAccountPubkey`'\n);\n```\n\nYou should define `getAccountPubkey` as asynchronous.\n\n```js\nconst getAccountPubkeyFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'getAccountPubkey';\n  });\nassert.isTrue(\n  getAccountPubkeyFunctionDeclaration?.async,\n  'You should define `getAccountPubkey` as asynchronous'\n);\n```\n\nYou should define `getAccountPubkey` with a first parameter `payer`.\n\n```js\nconst getAccountPubkeyFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'getAccountPubkey';\n  });\nconst firstParameter = getAccountPubkeyFunctionDeclaration?.params?.[0];\nassert.exists(\n  firstParameter,\n  'You should define `getAccountPubkey` to accept a first argument'\n);\nassert.equal(\n  firstParameter?.name,\n  'payer',\n  'You should define `getAccountPubkey` with a first parameter `payer`'\n);\n```\n\nYou should define `getAccountPubkey` with a second parameter `programId`.\n\n```js\nconst getAccountPubkeyFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id.name === 'getAccountPubkey';\n  });\nconst secondParameter = getAccountPubkeyFunctionDeclaration?.params?.[1];\nassert.exists(\n  secondParameter,\n  'You should define `getAccountPubkey` to accept a second argument'\n);\nassert.equal(\n  secondParameter?.name,\n  'programId',\n  'You should define `getAccountPubkey` with a second parameter `programId`'\n);\n```\n\nYou should export `getAccountPubkey` as a named export.\n\n```js\nconst getAccountPubkeyExportDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    return e.declaration?.id?.name === 'getAccountPubkey';\n  });\nassert.exists(\n  getAccountPubkeyExportDeclaration,\n  'You should export `getAccountPubkey` as a named export'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n```\n\n## 23\n\n### --description--\n\nWithin `getAccountPubkey`, use the `createWithSeed` function on the `PublicKey` class from `@solana/web3.js` to create a public key, passing in the following arguments:\n\n1. `payer.publicKey`\n2. Any string of your choosing which will act as the seed\n3. `programId`\n\nThen, return the awaited result.\n\n### --tests--\n\nYou should import `PublicKey` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source?.value === '@solana/web3.js';\n});\nconst specifier = importDeclaration?.specifiers?.find(s => {\n  return s.local?.name === 'PublicKey';\n});\nassert.exists(\n  specifier,\n  'You should import `PublicKey` from `@solana/web3.js`'\n);\n```\n\nYou should call `PublicKey.createWithSeed` within `getAccountPubkey`.\n\n```js\nconst createWithSeedCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return c.callee?.property?.name === 'createWithSeed';\n  });\nassert.exists(\n  createWithSeedCallExpression,\n  'You should call `PublicKey.createWithSeed`'\n);\nassert.equal(\n  createWithSeedCallExpression?.scope?.join(),\n  'global,getAccountPubkey',\n  'You should call `PublicKey.createWithSeed` within `getAccountPubkey`'\n);\nassert.equal(\n  createWithSeedCallExpression?.callee?.object?.name,\n  'PublicKey',\n  \"You should use `PublicKey`'s `createWithSeed` function\"\n);\n```\n\nYou should pass `payer.publicKey` as the first argument to `createWithSeed`.\n\n```js\nconst publicKeyCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return c.callee?.property?.name === 'createWithSeed';\n  });\nconst payerPropertyAccessExpression = publicKeyCallExpression?.arguments?.[0];\nassert.exists(\n  payerPropertyAccessExpression,\n  'You should pass a the first argument to `createWithSeed`'\n);\nassert.equal(\n  payerPropertyAccessExpression?.object?.name,\n  'payer',\n  'You should pass `payer.publicKey` as the first argument to `createWithSeed`'\n);\nassert.equal(\n  payerPropertyAccessExpression?.property?.name,\n  'publicKey',\n  'You should call `publicKey` on `payer`'\n);\n```\n\nYou should pass a string as the second argument to `createWithSeed`.\n\n```js\nconst createWithSeedCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return c.callee?.property?.name === 'createWithSeed';\n  });\nconst secondArgument = createWithSeedCallExpression?.arguments?.[1];\nassert.exists(\n  secondArgument,\n  'You should pass a second argument to `createWithSeed`'\n);\nassert.equal(\n  secondArgument.type,\n  'StringLiteral',\n  'You should pass a string as the second argument to `createWithSeed`'\n);\n```\n\nYou should pass `programId` as the third argument to `createWithSeed`.\n\n```js\nconst createWithSeedCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return c.callee?.property?.name === 'createWithSeed';\n  });\nconst programIdPropertyAccessExpression =\n  createWithSeedCallExpression?.arguments?.[2];\nassert.exists(\n  programIdPropertyAccessExpression,\n  'You should pass a third argument to `createWithSeed`'\n);\nassert.equal(\n  programIdPropertyAccessExpression?.name,\n  'programId',\n  'You should pass `programId` as the third argument to `createWithSeed`'\n);\n```\n\nYou should return the result of `await PublicKey.createWithSeed`.\n\n```js\nconst getAccountPubkeyFunctionDeclaration = babelisedCode\n  .getType('FunctionDeclaration')\n  .find(f => {\n    return f.id?.name === 'getAccountPubkey';\n  });\nconst returnStatement = getAccountPubkeyFunctionDeclaration?.body?.body?.find(\n  b => {\n    return b.type === 'ReturnStatement';\n  }\n);\nassert.exists(returnStatement, 'You should return within `getAccountPubkey`');\nconst awaitExpression = returnStatement?.argument;\nassert.equal(\n  awaitExpression?.type,\n  'AwaitExpression',\n  'You should await the result of `PublicKey.createWithSeed`'\n);\nconst memberExpression = awaitExpression?.argument?.callee;\nassert.equal(\n  memberExpression?.object?.name,\n  'PublicKey',\n  'You should return `await PublicKey...`'\n);\nassert.equal(\n  memberExpression?.property?.name,\n  'createWithSeed',\n  'You should return `PublicKey.createWithSeed(...)`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {}\n```\n\n## 24\n\n### --description--\n\nCreating a transaction interacting with a non-existent account still costs _lamports_ (one lamport is the minimum token value divisible). So, it is best to double-check the program account exists.\n\nWithin `hello-world.js`, define and export a function with the following signature:\n\n```typescript\nfunction checkProgram(\n  connection: Connection,\n  payer: Keypair,\n  programId: PublicKey,\n  accountPubkey: PublicKey\n): Promise<void>;\n```\n\n### --tests--\n\nYou should define a function with the handle `checkProgram`.\n\n```js\nconst checkProgramFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id?.name === 'checkProgram';\n  });\nassert.exists(\n  checkProgramFunctionDeclaration,\n  'You should define a function with the handle `checkProgram`'\n);\n```\n\nYou should define `checkProgram` with a first parameter named `connection`.\n\n```js\nconst checkProgramFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id?.name === 'checkProgram';\n  });\nconst firstParameter = checkProgramFunctionDeclaration?.params?.[0];\nassert.exists(\n  firstParameter,\n  'You should define `checkProgram` to expect at least one argument'\n);\nassert.equal(\n  firstParameter?.name,\n  'connection',\n  'You should define `checkProgram` with a first parameter named `connection`'\n);\n```\n\nYou should define `checkProgram` with a second parameter named `payer`.\n\n```js\nconst checkProgramFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id?.name === 'checkProgram';\n  });\nconst secondParameter = checkProgramFunctionDeclaration?.params?.[1];\nassert.exists(\n  secondParameter,\n  'You should define `checkProgram` to expect at least two arguments'\n);\nassert.equal(\n  secondParameter?.name,\n  'payer',\n  'You should define `checkProgram` with a second parameter named `payer`'\n);\n```\n\nYou should define `checkProgram` with a third parameter named `programId`.\n\n```js\nconst checkProgramFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id?.name === 'checkProgram';\n  });\nconst thirdParameter = checkProgramFunctionDeclaration?.params?.[2];\nassert.exists(\n  thirdParameter,\n  'You should define `checkProgram` to expect at least three arguments'\n);\nassert.equal(\n  thirdParameter?.name,\n  'programId',\n  'You should define `checkProgram` with a third parameter named `programId`'\n);\n```\n\nYou should define `checkProgram` with a fourth parameter named `accountPubkey`.\n\n```js\nconst checkProgramFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id?.name === 'checkProgram';\n  });\nconst fourthParameter = checkProgramFunctionDeclaration?.params?.[3];\nassert.exists(\n  fourthParameter,\n  'You should define `checkProgram` to expect at least four arguments'\n);\nassert.equal(\n  fourthParameter?.name,\n  'accountPubkey',\n  'You should define `checkProgram` with a fourth parameter named `accountPubkey`'\n);\n```\n\nYou should define `checkProgram` to be a named export.\n\n```js\nconst exportNamedDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(d => {\n    return d.declaration?.id?.name === 'checkProgram';\n  });\nassert.exists(\n  exportNamedDeclaration,\n  'You should define `checkProgram` to be a named export'\n);\n```\n\nYou should define `checkProgram` to be asynchronous.\n\n```js\nconst checkProgramFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id?.name === 'checkProgram';\n  });\nassert.isTrue(\n  checkProgramFunctionDeclaration?.async,\n  'You should define `checkProgram` to be asynchronous'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n```\n\n## 25\n\n### --description--\n\nWithin, `checkProgram`, use the `getAccountInfo` method on `connection` to get the **program account** information _if any exists_. The `getAccountInfo` method expects a `PublicKey` as an argument.\n\nIf the result is equal to `null`, throw an `Error` with a string message.\n\n### --tests--\n\n`checkProgram` should throw an `Error` instance, if `await connection.getAccountInfo(programId)` returns `null`.\n\n```js\nconst { checkProgram } = await __helpers.importSansCache(\n  './learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst payer = {};\nconst programId = 'programId';\nconst accountPubkey = 'accountPubkey';\nconst connection = {\n  getAccountInfo: async a =>\n    a !== programId\n      ? assert.fail('incorrect parameter passed to `getAccountInfo`')\n      : null\n};\ntry {\n  await checkProgram(connection, payer, programId, accountPubkey);\n  assert.fail(\n    '`checkProgram` should throw an `Error` instance, if `await connection.getAccountInfo(programId)` returns `null`'\n  );\n} catch (e) {\n  if (e instanceof AssertionError) {\n    throw e;\n  }\n  assert.instanceOf(e, Error);\n}\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {}\n```\n\n## 26\n\n### --description--\n\nWithin `checkProgram`, make use of the `executable` (boolean) property of the `getAccountInfo` result to throw an `Error` if the program account is not executable.\n\n### --tests--\n\n`checkProgram` should throw an `Error` instance, if the program account `executable` property equals `false`.\n\n```js\nconst { checkProgram } = await __helpers.importSansCache(\n  './learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst connection = {\n  getAccountInfo: async () => ({\n    executable: true\n  })\n};\nconst payer = {};\nconst programId = {};\nconst accountPubkey = {};\n// Should NOT throw if is executable\ntry {\n  await checkProgram(connection, payer, programId, accountPubkey);\n} catch (e) {\n  assert.fail(\n    '`checkProgram` should NOT throw an `Error` instance, if the program account `executable` property IS `true`'\n  );\n}\n// Should throw if is NOT executable\nconnection.getAccountInfo = async () => ({\n  executable: false\n});\ntry {\n  await checkProgram(connection, payer, programId, accountPubkey);\n  assert.fail(\n    '`checkProgram` should throw an `Error` instance, if the program account `executable` property equals `false`'\n  );\n} catch (e) {\n  if (e instanceof AssertionError) {\n    throw e;\n  }\n  assert.instanceOf(e, Error);\n}\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n}\n```\n\n## 27\n\n### --description--\n\nIf this is the first time the program account is being invoked, it will not own a _data account_ to store any state.\n\nWithin `checkProgram`, get the account info of the program **data** account (`accountPubkey`), _if any exists_. If the result is equal to `null`, throw an `Error` with a string message.\n\n### --tests--\n\n`checkProgram` should throw an `Error` instance, if the program **data account** does not exist.\n\n```js\nconst { checkProgram } = await __helpers.importSansCache(\n  './learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst connection = {\n  getAccountInfo: async flip =>\n    flip\n      ? null\n      : {\n          executable: true\n        }\n};\nconst payer = {};\nconst programId = false;\nlet accountPubkey = true;\ntry {\n  await checkProgram(connection, payer, programId, accountPubkey);\n  assert.fail(\n    '`checkProgram` should throw an `Error` instance, if the program data account does not exist'\n  );\n} catch (e) {\n  if (e instanceof AssertionError) {\n    throw e;\n  }\n  assert.instanceOf(e, Error);\n}\n// Should NOT throw if data account exists\naccountPubkey = false;\ntry {\n  await checkProgram(connection, payer, programId, accountPubkey);\n} catch (e) {\n  assert.fail(\n    '`checkProgram` should NOT throw an `Error` instance, if the program **data account** exists'\n  );\n}\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n}\n```\n\n## 28\n\n### --description--\n\nInstead of throwing when a program data account is not found, you can create the account.\n\nWithin `hello-world.js`, define and export a function with the following signature:\n\n```javascript\nfunction createAccount(\n  connection: Connection,\n  payer: Keypair,\n  programId: PublicKey,\n  accountPubkey: PublicKey\n): Promise<void>\n```\n\n### --tests--\n\nYou should define a function with the handle `createAccount`.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'createAccount';\n});\nassert.exists(\n  functionDeclaration,\n  'You should define a function with the handle `createAccount`'\n);\n```\n\nYou should define `createAccount` with a first parameter named `connection`.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'createAccount';\n});\nconst firstParameter = functionDeclaration?.params?.[0];\nassert.exists(\n  firstParameter,\n  'You should define `createAccount` to expect at least one argument'\n);\nassert.equal(\n  firstParameter?.name,\n  'connection',\n  'You should define `createAccount` with a first parameter named `connection`'\n);\n```\n\nYou should define `createAccount` with a second parameter named `payer`.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'createAccount';\n});\nconst secondParameter = functionDeclaration?.params?.[1];\nassert.exists(\n  secondParameter,\n  'You should define `createAccount` to expect at least two arguments'\n);\nassert.equal(\n  secondParameter?.name,\n  'payer',\n  'You should define `createAccount` with a second parameter named `payer`'\n);\n```\n\nYou should define `createAccount` with a third parameter named `programId`.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'createAccount';\n});\nconst thirdParameter = functionDeclaration?.params?.[2];\nassert.exists(\n  thirdParameter,\n  'You should define `createAccount` to expect at least three arguments'\n);\nassert.equal(\n  thirdParameter?.name,\n  'programId',\n  'You should define `createAccount` with a third parameter named `programId`'\n);\n```\n\nYou should define `createAccount` with a fourth parameter named `accountPubkey`.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'createAccount';\n});\nconst fourthParameter = functionDeclaration?.params?.[3];\nassert.exists(\n  fourthParameter,\n  'You should define `createAccount` to expect at least four arguments'\n);\nassert.equal(\n  fourthParameter?.name,\n  'accountPubkey',\n  'You should define `createAccount` with a fourth parameter named `accountPubkey`'\n);\n```\n\nYou should define `createAccount` to be a named export.\n\n```js\nconst exportNamedDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => {\n    return e.declaration?.id?.name === 'createAccount';\n  });\nassert.exists(\n  exportNamedDeclaration,\n  'You should define `createAccount` to be a named export'\n);\n```\n\nYou should define `createAccount` to be asynchronous.\n\n```js\nconst functionDeclaration = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'createAccount';\n});\nassert.isTrue(\n  functionDeclaration?.async,\n  'You should define `createAccount` to be asynchronous'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n```\n\n## 29\n\n### --description--\n\nStoring data on accounts costs a _rent_ fee. This fee is paid in _lamports_ and is calculated based on the size of the account. The `getMinimumBalanceForRentExemption` method on the `Connection` class can be used to calculate the rent fee payable to prevent an account from being purged.\n\nWithin `createAccount`, use the `getMinimumBalanceForRentExemption` method on `connection` to get the minimum balance required to create an account with a size of `10000` bytes. Store this in a variable named `lamports`.\n\n### --tests--\n\nYou should call `connection.getMinimumBalanceForRentExemption` within `createAccount`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.object?.name === 'connection' &&\n    c.callee?.property?.name === 'getMinimumBalanceForRentExemption'\n  );\n});\nassert.exists(\n  callExpression,\n  'You should call `connection.getMinimumBalanceForRentExemption`'\n);\nassert.include(\n  callExpression?.scope?.join(),\n  'global,createAccount',\n  '`connection.getMinimumBalanceForRentExemption()` should be within `createAccount`'\n);\n```\n\nYou should pass `10000` as the first argument to `getMinimumBalanceForRentExemption`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.object?.name === 'connection' &&\n    c.callee?.property?.name === 'getMinimumBalanceForRentExemption'\n  );\n});\nassert.equal(\n  callExpression?.arguments?.[0]?.value,\n  10000,\n  'You should pass `10000` as the first argument to `getMinimumBalanceForRentExemption`'\n);\n```\n\nYou should await the result of `getMinimumBalanceForRentExemption`.\n\n```js\nconst awaitExpression = babelisedCode.getType('AwaitExpression').find(a => {\n  return (\n    a.argument?.callee?.object?.name === 'connection' &&\n    a.scope?.join().includes('global,createAccount')\n  );\n});\nassert.exists(\n  awaitExpression,\n  'You should await the result of `connection.getMinimumBalanceForRentExemption(10000)`'\n);\nassert.equal(\n  awaitExpression.type,\n  'AwaitExpression',\n  'You should await the result of `connection.getMinimumBalanceForRentExemption(10000)`'\n);\n```\n\nYou should assign the value to a variable named `lamports`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'lamports';\n});\nassert.exists(\n  variableDeclaration,\n  'You should assign the value to a variable named `lamports`'\n);\nassert.equal(\n  variableDeclaration?.scope?.join(),\n  'global,createAccount',\n  '`lamports` should be defined within `createAccount`'\n);\nconst awaitExpression = variableDeclaration?.declarations?.[0]?.init;\nassert.equal(\n  awaitExpression?.argument?.callee?.object?.name,\n  'connection',\n  '`lamports` should be assigned the result of `connection.getMinimumBalanceForRentExemption(10000)`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {}\n```\n\n## 30\n\n### --description--\n\nYou can estimate the cost of creating a program data account of size `10000` bytes by using the following CLI command:\n\n```bash\nsolana rent 10000\n```\n\n### --tests--\n\nYou should run `solana rent 10000` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(lastCommand?.trim(), 'solana rent 10000');\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(10000);\n}\n```\n\n## 31\n\n### --description--\n\nRandomly guessing the size of your account might not always be best.\n\nWithin `hello-world.js`, define an `ACCOUNT_SIZE` constant, and set its value to:\n\n```javascript\nborsh.serialize(HelloWorldSchema, new HelloWorldAccount()).length;\n```\n\nThen, at the top of the file import `*` as `borsh` from the `borsh` module.\n\n### --tests--\n\nYou should define a constant named `ACCOUNT_SIZE` in the `hello-world.js` file.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'ACCOUNT_SIZE';\n});\nassert.exists(\n  variableDeclaration,\n  'You should define a constant named `ACCOUNT_SIZE`'\n);\nassert.equal(\n  variableDeclaration?.scope?.join(),\n  'global',\n  '`ACCOUNT_SIZE` should be defined in the global scope'\n);\nassert.equal(\n  variableDeclaration?.kind,\n  'const',\n  '`ACCOUNT_SIZE` should be defined as a constant'\n);\n```\n\nYou should set the value of `ACCOUNT_SIZE` to `borsh.serialize(HelloWorldSchema, new HelloWorldAccount()).length`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'ACCOUNT_SIZE';\n});\nconst memberExpression = variableDeclaration?.declarations?.[0]?.init;\nconst callExpression = memberExpression?.object;\nassert.equal(\n  callExpression?.callee?.object?.name,\n  'borsh',\n  '`ACCOUNT_SIZE` should use `borsh`'\n);\nassert.equal(\n  callExpression?.callee?.property?.name,\n  'serialize',\n  '`ACCOUNT_SIZE` should use `borsh.serialize`'\n);\nassert.equal(\n  callExpression?.arguments?.[0]?.name,\n  'HelloWorldSchema',\n  '`ACCOUNT_SIZE` should use `borsh.serialize(HelloWorldSchema, ...)`'\n);\nconst newExpression = callExpression?.arguments?.[1];\nassert.equal(\n  newExpression?.callee?.name,\n  'HelloWorldAccount',\n  '`ACCOUNT_SIZE` should use `borsh.serialize(..., new HelloWorldAccount())`'\n);\nassert.equal(\n  newExpression?.type,\n  'NewExpression',\n  '`HelloWorldAccount()` should be instantiated with `new`'\n);\nassert.equal(\n  memberExpression?.property?.name,\n  'length',\n  '`ACCOUNT_SIZE` should use `borsh.serialize(...).length`'\n);\n```\n\nYou should `import * as borsh from borsh`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source?.value === 'borsh';\n});\nassert.exists(importDeclaration, 'You should import from `borsh`');\nconst importNamespaceSpecifier = importDeclaration?.specifiers?.find(s => {\n  return s.type === 'ImportNamespaceSpecifier';\n});\nassert.exists(\n  importNamespaceSpecifier,\n  'You should import `borsh` as a namespace: `import * as borsh from \"borsh\"`'\n);\nassert.equal(\n  importNamespaceSpecifier?.local?.name,\n  'borsh',\n  'You should import `borsh` as a namespace named `borsh`: `import * as borsh from \"borsh\"`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 32\n\n### --description--\n\nDefine a class named `HelloWorldAccount` whose constructor takes a single parameter named `fields`.\n\nThen, assign the value of `fields.counter` to a property named `counter` on the instance, on the condition `fields` exists.\n\n### --tests--\n\nYou should define a class named `HelloWorldAccount`.\n\n```js\nconst classDeclaration = babelisedCode.getType('ClassDeclaration').find(c => {\n  return c.id?.name === 'HelloWorldAccount';\n});\nassert.exists(\n  classDeclaration,\n  'You should define a class named `HelloWorldAccount`'\n);\nassert.equal(\n  classDeclaration?.scope?.join(),\n  'global',\n  '`HelloWorldAccount` should be defined in the global scope'\n);\n```\n\nYou should define a constructor for `HelloWorldAccount` with a single parameter named `fields`.\n\n```js\nconst classDeclaration = babelisedCode.getType('ClassDeclaration').find(c => {\n  return c.id?.name === 'HelloWorldAccount';\n});\nconst constructor = classDeclaration?.body?.body?.find(m => {\n  return m.kind === 'constructor';\n});\nassert.exists(\n  constructor,\n  'You should define a constructor for `HelloWorldAccount`'\n);\nassert.equal(\n  constructor?.params?.length,\n  1,\n  'The constructor for `HelloWorldAccount` should take a single parameter'\n);\nassert.equal(\n  constructor?.params?.[0]?.name,\n  'fields',\n  'The constructor for `HelloWorldAccount` should take a single parameter named `fields`'\n);\n```\n\nYou should assign the value of `fields.counter` to `this.counter` only if `fields` is not `undefined`.\n\n```js\nconst classDeclaration = babelisedCode.getType('ClassDeclaration').find(c => {\n  return c.id?.name === 'HelloWorldAccount';\n});\nconst constructor = classDeclaration?.body?.body?.find(m => {\n  return m.kind === 'constructor';\n});\n\nconst code = babelisedCode.generateCode(classDeclaration);\n\nconst testCode = `\n${code}\nconst t = new HelloWorldAccount();\n`;\n\ntry {\n  const res = eval(testCode);\n} catch (e) {\n  assert.fail(\n    `The constructor for \\`HelloWorldAccount\\` should not throw an error when called without any arguments.`\n  );\n}\n```\n\nYou should declare `HelloWorldAccount` before `ACCOUNT_SIZE`.\n\n```js\nconst classDeclaration = babelisedCode.getType('ClassDeclaration').find(c => {\n  return c.id?.name === 'HelloWorldAccount';\n});\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'ACCOUNT_SIZE';\n});\nassert.isBelow(\n  classDeclaration?.end,\n  variableDeclaration?.start,\n  '`HelloWorldAccount` should be declared before `ACCOUNT_SIZE`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(10000);\n}\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n```\n\n## 33\n\n### --description--\n\nDefine a variable named `HelloWorldSchema` and set its value to:\n\n```javascript\nnew Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n```\n\nThis is a _schema_ that matches the definition of the `GreetingAccount` struct in `src/program-rust/src/lib.rs`.\n\n### --tests--\n\nYou should define a variable named `HelloWorldSchema`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'HelloWorldSchema';\n});\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `HelloWorldSchema`'\n);\nassert.equal(\n  variableDeclaration?.scope?.join(),\n  'global',\n  '`HelloWorldSchema` should be defined in the global scope'\n);\n```\n\nYou should set the value of `HelloWorldSchema` to the given schema.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'HelloWorldSchema';\n});\nconst newExpression = variableDeclaration?.declarations?.[0]?.init;\nassert.equal(\n  newExpression?.callee?.name,\n  'Map',\n  '`HelloWorldSchema` should use `Map(...)`'\n);\nassert.equal(\n  newExpression?.type,\n  'NewExpression',\n  '`HelloWorldSchema` should be set to `new Map(...)`'\n);\nconst arrayExpressionOne = newExpression?.arguments?.[0];\nassert.equal(\n  arrayExpressionOne?.type,\n  'ArrayExpression',\n  '`HelloWorldSchema` should use `Map([ ... ])`'\n);\nconst arrayExpressionTwo = arrayExpressionOne?.elements?.[0];\nassert.equal(\n  arrayExpressionTwo?.type,\n  'ArrayExpression',\n  '`HelloWorldSchema` should use `Map([ [ ... ] ])`'\n);\nassert.equal(\n  arrayExpressionTwo?.elements?.[0]?.name,\n  'HelloWorldAccount',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, ... ] ])`'\n);\nconst objectExpression = arrayExpressionTwo?.elements?.[1];\nassert.equal(\n  objectExpression?.type,\n  'ObjectExpression',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, { ... } ] ])`'\n);\nconst kindObjectProperty = objectExpression?.properties?.[0];\nassert.equal(\n  kindObjectProperty?.key?.name,\n  'kind',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, { kind: ... } ] ])`'\n);\nassert.equal(\n  kindObjectProperty?.value?.value,\n  'struct',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, { kind: \"struct\" } ] ])`'\n);\nconst fieldsObjectProperty = objectExpression?.properties?.[1];\nassert.equal(\n  fieldsObjectProperty?.key?.name,\n  'fields',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, { fields: ... } ] ])`'\n);\nassert.equal(\n  fieldsObjectProperty?.value?.type,\n  'ArrayExpression',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, { fields: [ ... ] } ] ])`'\n);\nconst arrayExpressionThree = fieldsObjectProperty?.value?.elements?.[0];\nassert.equal(\n  arrayExpressionThree?.type,\n  'ArrayExpression',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, { fields: [ [ ... ] ] } ] ])`'\n);\nassert.equal(\n  arrayExpressionThree?.elements?.[0]?.value,\n  'counter',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, { fields: [ [ \"counter\", ... ] ] } ] ])`'\n);\nassert.equal(\n  arrayExpressionThree?.elements?.[1]?.value,\n  'u32',\n  '`HelloWorldSchema` should use `Map([ [ HelloWorldAccount, { fields: [ [ \"counter\", \"u32\" ] ] } ] ])`'\n);\n```\n\nYou should declare `HelloWorldSchema` before `ACCOUNT_SIZE`.\n\n```js\nconst hello = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'HelloWorldSchema';\n});\nconst account = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'ACCOUNT_SIZE';\n});\n\nassert.isBelow(\n  hello?.end,\n  account?.start,\n  '`HelloWorldSchema` should be declared before `ACCOUNT_SIZE`'\n);\n```\n\nYou should declare `HelloWorldSchema` after `HelloWorldAccount`.\n\n```js\nconst schema = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'HelloWorldSchema';\n});\nconst clas = babelisedCode.getType('ClassDeclaration').find(c => {\n  return c.id?.name === 'HelloWorldAccount';\n});\n\nassert.isAbove(\n  schema?.start,\n  clas?.end,\n  '`HelloWorldSchema` should be declared after `HelloWorldAccount`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```javascript\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(10000);\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n```\n\n## 34\n\n### --description--\n\nWithin `createAccount`, replace the hard-coded value of `10000` with the `ACCOUNT_SIZE` constant.\n\n### --tests--\n\nYou should replace the hard-coded value of `10000` with the `ACCOUNT_SIZE` constant.\n\n```js\nconst createAccountFunctionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => {\n    return f.id?.name === 'createAccount';\n  });\nconst variableDeclaration = createAccountFunctionDeclaration?.body?.body?.find(\n  v => {\n    return v.declarations?.[0]?.id?.name === 'lamports';\n  }\n);\nconst awaitExpression = variableDeclaration?.declarations?.[0]?.init;\nassert.equal(\n  awaitExpression?.argument?.arguments?.[0]?.name,\n  'ACCOUNT_SIZE',\n  'You should replace the hard-coded value of `10000` with the `ACCOUNT_SIZE` constant'\n);\n```\n\nYou should declare `ACCOUNT_SIZE` before `createAccount`.\n\n```js\nconst account = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'ACCOUNT_SIZE';\n});\nconst createAccount = babelisedCode.getFunctionDeclarations().find(f => {\n  return f.id?.name === 'createAccount';\n});\n\nconst { end } = account;\nconst { start } = createAccount;\n\nconst { line: accountLine } = babelisedCode.getLineAndColumnFromIndex(end);\nconst { line: createAccountLine } =\n  babelisedCode.getLineAndColumnFromIndex(start);\n\nassert.isBelow(\n  accountLine,\n  createAccountLine,\n  `'ACCOUNT_SIZE' declared on line ${accountLine}, but should be declared before ${createAccountLine}`\n);\n\n// Check HelloWorldSchema and HelloWorldAccount are declared before ACCOUNT_SIZE\nconst schema = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'HelloWorldSchema';\n});\nconst clas = babelisedCode.getType('ClassDeclaration').find(c => {\n  return c.id?.name === 'HelloWorldAccount';\n});\n\nassert.isBelow(\n  schema?.end,\n  account.start,\n  '`HelloWorldSchema` should be declared before `ACCOUNT_SIZE`'\n);\nassert.isBelow(\n  clas?.end,\n  account.start,\n  '`HelloWorldAccount` should be declared before `ACCOUNT_SIZE`'\n);\n// HelloWorldAccount should be declared before HelloWorldSchema\nassert.isBelow(\n  clas?.end,\n  schema?.start,\n  '`HelloWorldAccount` should be declared before `HelloWorldSchema`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```javascript\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(10000);\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n```\n\n## 35\n\n### --description--\n\nIn order to create the program data account, you need to define a `Transaction` that will be signed by the `payer` and sent to the network.\n\nWithin `createAccount`, create a new `Transaction` instance and store it in a variable named `transaction`.\n\n_Be sure to import the `Transaction` class from `@solana/web3.js`_\n\n### --tests--\n\nYou should create a new `Transaction` instance within `createAccount`.\n\n```js\nconst transactionNewExpression = babelisedCode\n  .getType('NewExpression')\n  .find(n => {\n    return n.callee?.name === 'Transaction';\n  });\nassert.exists(\n  transactionNewExpression,\n  'You should create a new `Transaction`'\n);\nassert.equal(\n  transactionNewExpression?.scope?.join(),\n  'global,createAccount,transaction',\n  'You should create a new `Transaction` instance within `createAccount`'\n);\n```\n\nYou should store the result in a variable named `transaction`.\n\n```js\nconst transactionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'transaction' &&\n      v.scope?.join() === 'global,createAccount'\n    );\n  });\nassert.exists(\n  transactionVariableDeclaration,\n  'You should define a variable named `transaction`'\n);\nconst newExpression = transactionVariableDeclaration?.declarations?.[0]?.init;\nassert.equal(\n  newExpression?.callee?.name,\n  'Transaction',\n  '`transaction` should be initialised with `new Transaction()`'\n);\n```\n\nYou should import `Transaction` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(e => {\n  return (\n    e.source?.value === '@solana/web3.js' &&\n    e.specifiers?.find(s => s.imported?.name === 'Transaction')\n  );\n});\nassert.exists(\n  importDeclaration,\n  '`Transaction` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n}\n```\n\n## 36\n\n### --description--\n\nWithin `createAccount`, create a new variable named `instruction`, and set its value to:\n\n```javascript\n{\n  basePubkey: payer.publicKey,\n  fromPubkey: payer.publicKey,\n  lamports,\n  newAccountPubkey: accountPubkey,\n  programId,\n  seed: <SAME_SEED_USED_IN_GET_ACCOUNT_PUBKEY_FUNCTION>,\n  space: ACCOUNT_SIZE,\n}\n```\n\n### --tests--\n\nYou should create a new variable named `instruction` within `createAccount`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return (\n    v.declarations?.[0]?.id?.name === 'instruction' &&\n    v.scope?.join() === 'global,createAccount'\n  );\n});\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `instruction`'\n);\n```\n\nYou should set the value of `instruction` to the given object.\n\n```js\nconst instructionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'instruction' &&\n      v.scope?.join() === 'global,createAccount'\n    );\n  });\n\nconst instructionCode = babelisedCode.generateCode(\n  instructionVariableDeclaration\n);\n\nconst testCode = `\nconst ACCOUNT_SIZE = 1;\nconst payer = { publicKey: 'payer-public-key' };\nconst programId = 'program-id';\nconst accountPubkey = 'account-pubkey';\nconst lamports = 123;\n${instructionCode};\nreturn instruction;\n`;\n\nconst instruction = new Function(testCode)();\n\nconst expected = {\n  basePubkey: 'payer-public-key',\n  fromPubkey: 'payer-public-key',\n  lamports: 123,\n  newAccountPubkey: 'account-pubkey',\n  programId: 'program-id',\n  space: 1\n};\nassert.deepInclude(instruction, expected);\n```\n\nYou should use the same seed you used in the `getAccountPubkey` function.\n\n```js\nconst instructionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return (\n      v.declarations?.[0]?.id?.name === 'instruction' &&\n      v.scope?.join() === 'global,createAccount'\n    );\n  });\nconst instructionSeedValue =\n  instructionVariableDeclaration?.declarations?.[0]?.init?.properties?.find(\n    p => p.key?.name === 'seed'\n  )?.value?.value;\n\nconst createWithSeedCall = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.object?.name === 'PublicKey' &&\n    c.callee?.property?.name === 'createWithSeed'\n  );\n});\nconst seedValue = createWithSeedCall?.arguments?.[1]?.value;\n\nassert.equal(\n  instructionSeedValue,\n  seedValue,\n  'You should use the same seed you used in the `getAccountPubkey` function.'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey, Transaction } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n}\n```\n\n## 37\n\n### --description--\n\nSolana has a native program called the _System Program_. It provides functionality to create accounts, allocate account data, assign an account to programs, work with nonce accounts, and transfer lamports.\n\nWithin `createAccount`, use the `createAccountWithSeed` method on the `SystemProgram` class from `@solana/web3.js`. Pass it your `instruction` variable and store the return value in a variable named `tx`.\n\n### --tests--\n\nYou should call `SystemProgram.createAccountWithSeed` within `createAccount`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.object?.name === 'SystemProgram' &&\n    c.callee?.property?.name === 'createAccountWithSeed'\n  );\n});\nassert.exists(\n  callExpression,\n  'You should call `SystemProgram.createAccountWithSeed`'\n);\nassert.include(\n  callExpression?.scope?.join(),\n  'global,createAccount',\n  '`SystemProgram.CreateAccountWithSeed()` should be within `createAccount`'\n);\n```\n\nYou should pass `instruction` as the first argument to `createAccountWithSeed`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(c => {\n  return (\n    c.callee?.object?.name === 'SystemProgram' &&\n    c.callee?.property?.name === 'createAccountWithSeed'\n  );\n});\nassert.equal(\n  callExpression?.arguments?.[0]?.name,\n  'instruction',\n  '`instruction` should be the first argument to `createAccountWithSeed`'\n);\n```\n\nYou should assign the value to a variable named `tx`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'tx';\n});\nassert.exists(variableDeclaration, 'A `tx` variable declaration should exist');\nassert.equal(\n  variableDeclaration?.scope?.join(),\n  'global,createAccount',\n  '`tx` should be defined within `createAccount`'\n);\nconst expression = variableDeclaration?.declarations?.[0]?.init;\nassert.equal(\n  expression?.callee?.object?.name,\n  'SystemProgram',\n  '`lamports` should be assigned the result of `SystemProgram.createAccountWithSeed(instruction)`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport { Connection, Keypair, PublicKey, Transaction } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n}\n```\n\n## 38\n\n### --description--\n\nWithin `createAccount`, use the `add` method on `transaction` to add the transaction with the instruction to create the program data account.\n\n### --tests--\n\nYou should call `transaction.add` within `createAccount`.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.callee?.property?.name === 'add' &&\n    e.expression?.callee?.object?.name === 'transaction' &&\n    e.scope?.join() === 'global,createAccount'\n  );\n});\nassert.exists(\n  expressionStatement,\n  'You should call `transaction.add` within `createAccount`'\n);\n```\n\nYou should pass `tx` as the first argument to `transaction.add`.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.callee?.property?.name === 'add' &&\n    e.expression?.callee?.object?.name === 'transaction' &&\n    e.scope?.join() === 'global,createAccount'\n  );\n});\nconst callExpression = expressionStatement?.expression?.arguments?.[0];\nassert.equal(\n  callExpression?.name,\n  'tx',\n  '`tx` should be the first argument to `transaction.add`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n}\n```\n\n## 39\n\n### --description--\n\nYou have created a transaction, but need to send it to the network.\n\nAwait the `sendAndConfirmTransaction` function from `@solana/web3.js` to send the transaction to the network. This function expects at least three arguments:\n\n- `connection`\n- `transaction`\n- An array of _signers_ (use the `payer` as the only signer)\n\n### --tests--\n\nYou should call `sendAndConfirmTransaction` within `createAccount`.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(e => {\n  return (\n    e?.callee?.name === 'sendAndConfirmTransaction' &&\n    e.scope.join() === 'global,createAccount'\n  );\n});\nassert.exists(\n  callExpression,\n  'You should call `sendAndConfirmTransaction` within `createAccount`'\n);\n```\n\nYou should pass `connection` as the first argument.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(e => {\n  return (\n    e?.callee?.name === 'sendAndConfirmTransaction' &&\n    e.scope.join() === 'global,createAccount'\n  );\n});\nconst ident = callExpression?.arguments?.[0];\nassert.equal(\n  ident?.name,\n  'connection',\n  '`connection` should be the first argument to `sendAndConfirmTransaction`'\n);\n```\n\nYou should pass `transaction` as the second argument.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(e => {\n  return (\n    e?.callee?.name === 'sendAndConfirmTransaction' &&\n    e.scope.join() === 'global,createAccount'\n  );\n});\nconst ident = callExpression?.arguments?.[1];\nassert.equal(\n  ident?.name,\n  'transaction',\n  '`transaction` should be the second argument to `sendAndConfirmTransaction`'\n);\n```\n\nYou should pass `[payer]` as the third argument.\n\n```js\nconst callExpression = babelisedCode.getType('CallExpression').find(e => {\n  return (\n    e?.callee?.name === 'sendAndConfirmTransaction' &&\n    e.scope.join() === 'global,createAccount'\n  );\n});\nconst arrayExpression = callExpression?.arguments?.[2];\nassert.equal(\n  arrayExpression?.elements?.[0]?.name,\n  'payer',\n  '`[payer]` should be the third argument to `sendAndConfirmTransaction`'\n);\n```\n\nYou should await the result of `sendAndConfirmTransaction`.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.argument?.callee?.name === 'sendAndConfirmTransaction' &&\n    e.scope?.join() === 'global,createAccount'\n  );\n});\nassert.equal(\n  expressionStatement?.expression?.type,\n  'AwaitExpression',\n  'You should await the result of `sendAndConfirmTransaction`'\n);\n```\n\nYou should import `sendAndConfirmTransaction` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(e => {\n  return (\n    e.source?.value === '@solana/web3.js' &&\n    e.specifiers?.find(s => s.imported?.name === 'sendAndConfirmTransaction')\n  );\n});\nassert.exists(\n  importDeclaration,\n  'You should import `sendAndConfirmTransaction` from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n}\n```\n\n## 40\n\n### --description--\n\nWithin `checkProgram`, instead of throwing an error when the program data account is not found, use `createAccount` to create the program data account.\n\n### --tests--\n\nYou should no longer throw an error when the program data account is not found.\n\n```js\nconst { checkProgram } = await __helpers.importSansCache(\n  './learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\n\nconst connection = {\n  getAccountInfo: a => (a === 'accountPubkey' ? null : { executable: true }),\n  getMinimumBalanceForRentExemption: s => 10\n};\nconst payer = {\n  publicKey: 'payer'\n};\nconst programId = 'programId';\nconst accountPubkey = 'accountPubkey';\ntry {\n  await checkProgram(connection, payer, programId, accountPubkey);\n} catch (e) {\n  if (!(e instanceof TypeError)) {\n    assert.fail(\n      'You should no longer throw an error when the program data account is not found'\n    );\n  }\n}\n```\n\nYou should call `createAccount` within `checkProgram`.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.argument?.callee?.name === 'createAccount' &&\n    e.scope?.join() === 'global,checkProgram'\n  );\n});\nassert.exists(\n  expressionStatement,\n  'You should call `createAccount` within `checkProgram`'\n);\n```\n\nYou should pass `connection` as the first argument.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.argument?.callee?.name === 'createAccount' &&\n    e.scope?.join() === 'global,checkProgram'\n  );\n});\nconst callExpression = expressionStatement?.expression?.argument;\nassert.equal(\n  callExpression?.arguments?.[0]?.name,\n  'connection',\n  'You should pass `connection` as the first argument'\n);\n```\n\nYou should pass `payer` as the second argument.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.argument?.callee?.name === 'createAccount' &&\n    e.scope?.join() === 'global,checkProgram'\n  );\n});\nconst callExpression = expressionStatement?.expression?.argument;\nassert.equal(\n  callExpression?.arguments?.[1]?.name,\n  'payer',\n  'You should pass `payer` as the second argument'\n);\n```\n\nYou should pass `programId` as the third argument.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.argument?.callee?.name === 'createAccount' &&\n    e.scope?.join() === 'global,checkProgram'\n  );\n});\nconst callExpression = expressionStatement?.expression?.argument;\nassert.equal(\n  callExpression?.arguments?.[2]?.name,\n  'programId',\n  'You should pass `programId` as the third argument'\n);\n```\n\nYou should pass `accountPubkey` as the fourth argument.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.argument?.callee?.name === 'createAccount' &&\n    e.scope?.join() === 'global,checkProgram'\n  );\n});\nconst callExpression = expressionStatement?.expression?.argument;\nassert.equal(\n  callExpression?.arguments?.[3]?.name,\n  'accountPubkey',\n  'You should pass `accountPubkey` as the fourth argument'\n);\n```\n\nYou should await the result of `createAccount`.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.argument?.callee?.name === 'createAccount' &&\n    e.scope?.join() === 'global,checkProgram'\n  );\n});\nconst callExpression = expressionStatement?.expression;\nassert.equal(\n  callExpression?.type,\n  'AwaitExpression',\n  'You should await the result of `createAccount`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    throw new Error('Data account info not found');\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n```\n\n## 41\n\n### --description--\n\nWithin `hello-world.js`, export an asynchronous function named `sayHello` with the following signature:\n\n```javascript\nfunction sayHello(\n  connection,\n  payer,\n  programId,\n  accountPubkey,\n): Promise<void>\n```\n\n### --tests--\n\nYou should define a function with the handle `sayHello`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'sayHello');\nassert.exists(\n  functionDeclaration,\n  'You should define a function with the handle `sayHello`'\n);\n```\n\nYou should define `sayHello` with a first parameter named `connection`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'sayHello');\nconst parameter = functionDeclaration?.params?.[0];\nassert.equal(\n  parameter?.name,\n  'connection',\n  'You should define `sayHello` with a first parameter named `connection`'\n);\n```\n\nYou should define `sayHello` with a second parameter named `payer`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'sayHello');\nconst parameter = functionDeclaration?.params?.[1];\nassert.equal(\n  parameter?.name,\n  'payer',\n  'You should define `sayHello` with a second parameter named `payer`'\n);\n```\n\nYou should define `sayHello` with a third parameter named `programId`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'sayHello');\nconst parameter = functionDeclaration?.params?.[2];\nassert.equal(\n  parameter?.name,\n  'programId',\n  'You should define `sayHello` with a third parameter named `programId`'\n);\n```\n\nYou should define `sayHello` with a fourth parameter named `accountPubkey`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'sayHello');\nconst parameter = functionDeclaration?.params?.[3];\nassert.equal(\n  parameter?.name,\n  'accountPubkey',\n  'You should define `sayHello` with a fourth parameter named `accountPubkey`'\n);\n```\n\nYou should define `sayHello` as an asynchronous function.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'sayHello');\nassert.isTrue(\n  functionDeclaration?.async,\n  'You should define `sayHello` as an asynchronous function'\n);\n```\n\nYou should define `sayHello` to be a named export.\n\n```js\nconst exportNamedDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => e.declaration?.id?.name === 'sayHello');\nassert.exists(\n  exportNamedDeclaration,\n  'You should define `sayHello` to be a named export'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n```\n\n## 42\n\n### --description--\n\nTo say hello to your smart contract, you need to send a transaction with some data.\n\nWithin `sayHello`, create a `transaction` variable with a value of:\n\n```javascript\n{\n  keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n  programId,\n  data: Buffer.alloc(0),\n}\n```\n\n_The `data` field is empty because the program does not do anything with it._\n\n### --tests--\n\nYou should define a variable named `transaction`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return (\n    v.declarations?.[0]?.id?.name === 'transaction' &&\n    v.scope.join() === 'global,sayHello'\n  );\n});\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `transaction`, within `sayHello`'\n);\n```\n\nYou should give `transaction` a value of the above object literal.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return (\n    v.declarations?.[0]?.id?.name === 'transaction' &&\n    v.scope.join() === 'global,sayHello'\n  );\n});\n\nconst txCode = babelisedCode.generateCode(variableDeclaration);\n\nconst testCode = `\nconst accountPubkey = '123';\nconst programId = '456';\n${txCode}\nreturn transaction;\n`;\n\nlet transaction = new Function(testCode)();\nassert.deepEqual(\n  transaction,\n  {\n    keys: [{ pubkey: '123', isSigner: false, isWritable: true }],\n    programId: '456',\n    data: Buffer.alloc(0)\n  },\n  'You should give `transaction` a value of the above object literal'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {}\n```\n\n## 43\n\n### --description--\n\nWithin `sayHello`, define an `instruction` variable to be a new instance `TransactionInstruction` from `@solana/web3.js`. The constructor expects your transaction object as an argument.\n\n### --tests--\n\nYou should define a variable named `instruction`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'instruction' &&\n      v.scope.join() === 'global,sayHello'\n  );\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `instruction`, within `sayHello`'\n);\n```\n\nYou should give `instruction` a value of `new TransactionInstruction(transaction)`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'instruction' &&\n      v.scope.join() === 'global,sayHello'\n  );\nconst newExpression = variableDeclaration?.declarations?.[0]?.init;\nassert.exists(\n  newExpression,\n  'You should give `instruction` a value of `new ...`'\n);\nassert.equal(\n  newExpression?.callee?.name,\n  'TransactionInstruction',\n  'You should give `instruction` a value of `new TransactionInstruction`'\n);\n\nconst transactionArgument = newExpression?.arguments?.[0];\nassert.equal(\n  transactionArgument?.name,\n  'transaction',\n  'You should give `instruction` a value of `new TransactionInstruction(transaction)`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0)\n  };\n}\n```\n\n## 44\n\n### --description--\n\nNow, send and confirm the transaction.\n\n### --tests--\n\nYou should call `sendAndConfirmTransaction` within `sayHello`.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return (\n    e.expression?.argument?.callee?.name === 'sendAndConfirmTransaction' &&\n    e.scope.join() === 'global,sayHello'\n  );\n});\nassert.exists(\n  expressionStatement,\n  'You should call `sendAndConfirmTransaction` within `sayHello`'\n);\n```\n\nCalling `sayHello` should send the correct transaction with `sendAndConfirmTransaction(connection, new Transaction().add(instruction), [payer])`.\n\n```js\nconst sayHelloDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id?.name === 'sayHello');\n\nconst helloCode = babelisedCode.generateCode(sayHelloDeclaration);\n\nconst testCode = `\nclass TransactionInstruction {\n  constructor(transaction) {\n    this.transaction = '5';\n  }\n}\nclass Transaction {\n  add(instruction) {\n    this.instruction = instruction;\n    return this;\n  }\n}\nlet error;\nconst sendAndConfirmTransaction = (a, b, c) => {\n  try {\n  assert.equal(a, '1', 'You should call sendAndConfirmTransaction with connection as the first argument');\n  assert.equal(b.instruction.transaction, '5', 'You should call sendAndConfirmTransaction with new Transaction().add(instruction) as the second argument');\n  assert.include(c, '2', 'You should call sendAndConfirmTransaction with [payer] as the third argument');\n  } catch (e) {\n    error = e;\n  }\n};\n${helloCode}\nsayHello('1', '2', '3', '4');\nreturn error;\n`;\n\ntry {\n  const t = eval(`(() => {${testCode}})()`);\n  if (t) {\n    throw t;\n  }\n} catch (e) {\n  if (e instanceof AssertionError) {\n    throw e;\n  }\n}\n```\n\nYour `sayHello` function should create a new `Transaction` instance\n\n```js\nconst { sayHello } = await __helpers.importSansCache(\n  './learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nassert.match(sayHello.toString(), /new\\s+Transaction\\s*\\(/);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction,\n  TransactionInstruction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0)\n  };\n  const instruction = new TransactionInstruction(transaction);\n}\n```\n\n## 45\n\n### --description--\n\nWithin `main.js` in the `main` function, create a variable named `programId` and use the function you created to assign it the program id.\n\n### --tests--\n\nYou should define a variable named `programId`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'programId' &&\n      v.scope.join() === 'global,main'\n  );\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `programId`, within `main`'\n);\n```\n\nYou should assign `programId` the value of `await getProgramId()`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'programId' &&\n      v.scope.join() === 'global,main'\n  );\nconst awaitExpression = variableDeclaration?.declarations?.[0]?.init;\nassert.equal(awaitExpression?.type, 'AwaitExpression');\nassert.equal(\n  awaitExpression?.argument?.callee?.name,\n  'getProgramId',\n  'You should assign `programId` the value of `await getProgramId()`'\n);\n```\n\nYou should import `getProgramId` from `./hello-world.js`.\n\n```js\nconst importDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => i.source.value === './hello-world.js');\nassert.exists(importDeclaration, 'You should import from `./hello-world.js`');\nconst importSpecifier = importDeclaration?.specifiers?.find(\n  s => s.imported.name === 'getProgramId'\n);\nassert.exists(\n  importSpecifier,\n  'You should import `getProgramId` from `./hello-world.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction,\n  TransactionInstruction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0)\n  };\n  const instruction = new TransactionInstruction(transaction);\n  await sendAndConfirmTransaction(\n    connection,\n    new Transaction().add(instruction),\n    [payer]\n  );\n}\n```\n\n## 46\n\n### --description--\n\nWithin `main`, create a variable named `payer` and use the function you created to assign it the payer.\n\n### --tests--\n\nYou should define a variable named `payer`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'payer' &&\n      v.scope.join() === 'global,main'\n  );\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `payer`, within `main`'\n);\n```\n\nYou should assign `payer` the value of `await establishPayer()`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'payer' &&\n      v.scope.join() === 'global,main'\n  );\nconst awaitExpression = variableDeclaration?.declarations?.[0]?.init;\nassert.equal(\n  awaitExpression?.argument?.callee?.name,\n  'establishPayer',\n  'You should assign `payer` the value of `establishPayer()`'\n);\n```\n\nYou should import `establishPayer` from `./hello-world.js`.\n\n```js\nconst importDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => i.source.value === './hello-world.js');\nassert.exists(importDeclaration, 'You should import from `./hello-world.js`');\nconst importSpecifier = importDeclaration?.specifiers?.find(\n  s => s.imported.name === 'establishPayer'\n);\nassert.exists(\n  importSpecifier,\n  'You should import `establishPayer` from `./hello-world.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nimport { establishConnection, getProgramId } from './hello-world.js';\n\nasync function main() {\n  console.log(`Saying 'hello' to a Solana account`);\n  const connection = establishConnection();\n  const programId = await getProgramId();\n}\n\nawait main();\n```\n\n## 47\n\n### --description--\n\nWithin `main`, create a variable named `accountPubkey` and use the function you created to assign it the account pubkey.\n\n### --tests--\n\nYou should define a variable named `accountPubkey`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'accountPubkey' &&\n      v.scope.join() === 'global,main'\n  );\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `accountPubkey`, within `main`'\n);\n```\n\nYou should assign `accountPubkey` the value of `await getAccountPubkey(payer, programId)`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'accountPubkey' &&\n      v.scope.join() === 'global,main'\n  );\nconst awaitExpression = variableDeclaration?.declarations?.[0]?.init;\nassert.equal(awaitExpression?.type, 'AwaitExpression');\nassert.equal(\n  awaitExpression?.argument?.callee?.name,\n  'getAccountPubkey',\n  'You should assign `accountPubkey` the value of `await getAccountPubkey(payer, programId)`'\n);\nassert.equal(awaitExpression?.argument?.arguments[0]?.name, 'payer');\nassert.equal(awaitExpression?.argument?.arguments[1]?.name, 'programId');\n```\n\nYou should import `getAccountPubkey` from `./hello-world.js`.\n\n```js\nconst importDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => i.source.value === './hello-world.js');\nassert.exists(importDeclaration, 'You should import from `./hello-world.js`');\nconst importSpecifier = importDeclaration?.specifiers?.find(\n  s => s.imported.name === 'getAccountPubkey'\n);\nassert.exists(\n  importSpecifier,\n  'You should import `getAccountPubkey` from `./hello-world.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nimport {\n  establishConnection,\n  establishPayer,\n  getProgramId\n} from './hello-world.js';\n\nasync function main() {\n  console.log(`Saying 'hello' to a Solana account`);\n  const connection = establishConnection();\n  const programId = await getProgramId();\n  const payer = await establishPayer();\n}\n\nawait main();\n```\n\n## 48\n\n### --description--\n\nWithin `main`, ensure the program account is deployed, and the program data account is created.\n\n### --tests--\n\nYou should call `await checkProgram(connection, payer, programId, accountPubkey)` within `main`.\n\n```js\nconst expressionStatement = babelisedCode\n  .getExpressionStatements()\n  .find(e => e.expression?.argument?.callee?.name === 'checkProgram');\nassert.exists(expressionStatement, 'You should call `checkProgram`');\nassert.equal(\n  expressionStatement?.scope?.join(),\n  'global,main',\n  'You should call `checkProgram` within `main`'\n);\nconst awaitExpression = expressionStatement?.expression;\nassert.equal(awaitExpression?.type, 'AwaitExpression');\nconst args = awaitExpression?.argument?.arguments;\nconst [connection, payer, programId, accountPubkey] = args;\nassert.equal(\n  connection?.name,\n  'connection',\n  '`connection` should be the first argument'\n);\nassert.equal(payer?.name, 'payer', '`payer` should be the second argument');\nassert.equal(\n  programId?.name,\n  'programId',\n  '`programId` should be the third argument'\n);\nassert.equal(\n  accountPubkey?.name,\n  'accountPubkey',\n  '`accountPubkey` should be the fourth argument'\n);\n```\n\nYou should import `checkProgram` from `./hello-world.js`.\n\n```js\nconst importDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => i.source.value === './hello-world.js');\nassert.exists(importDeclaration, 'You should import from `./hello-world.js`');\nconst importSpecifier = importDeclaration?.specifiers?.find(\n  s => s.imported.name === 'checkProgram'\n);\nassert.exists(\n  importSpecifier,\n  'You should import `checkProgram` from `./hello-world.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nimport {\n  establishConnection,\n  establishPayer,\n  getAccountPubkey,\n  getProgramId\n} from './hello-world.js';\n\nasync function main() {\n  console.log(`Saying 'hello' to a Solana account`);\n  const connection = establishConnection();\n  const programId = await getProgramId();\n  const payer = await establishPayer();\n  const accountPubkey = await getAccountPubkey(payer, programId);\n}\n\nawait main();\n```\n\n## 49\n\n### --description--\n\nWithin `main`, say hello to the program.\n\n### --tests--\n\nYou should call `await sayHello(connection, payer, programId, accountPubkey)` within `main`.\n\n```js\nconst expressionStatement = babelisedCode\n  .getExpressionStatements()\n  .find(e => e.expression?.argument?.callee?.name === 'sayHello');\nassert.exists(expressionStatement, 'You should call `sayHello`');\nassert.equal(\n  expressionStatement?.scope?.join(),\n  'global,main',\n  'You should call `sayHello` within `main`'\n);\nconst awaitExpression = expressionStatement?.expression;\nassert.equal(awaitExpression?.type, 'AwaitExpression');\nconst args = awaitExpression?.argument?.arguments;\nconst [connection, payer, programId, accountPubkey] = args;\nassert.equal(\n  connection?.name,\n  'connection',\n  '`connection` should be the first argument'\n);\nassert.equal(payer?.name, 'payer', '`payer` should be the second argument');\nassert.equal(\n  programId?.name,\n  'programId',\n  '`programId` should be the third argument'\n);\nassert.equal(\n  accountPubkey?.name,\n  'accountPubkey',\n  '`accountPubkey` should be the fourth argument'\n);\n```\n\nYou should import `sayHello` from `./hello-world.js`.\n\n```js\nconst importDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => i.source.value === './hello-world.js');\nassert.exists(importDeclaration, 'You should import from `./hello-world.js`');\nconst importSpecifier = importDeclaration?.specifiers?.find(\n  s => s.imported.name === 'sayHello'\n);\nassert.exists(\n  importSpecifier,\n  'You should import `sayHello` from `./hello-world.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nimport {\n  checkProgram,\n  establishConnection,\n  establishPayer,\n  getAccountPubkey,\n  getProgramId\n} from './hello-world.js';\n\nasync function main() {\n  console.log(`Saying 'hello' to a Solana account`);\n  const connection = establishConnection();\n  const programId = await getProgramId();\n  const payer = await establishPayer();\n  const accountPubkey = await getAccountPubkey(payer, programId);\n  await checkProgram(connection, payer, programId, accountPubkey);\n}\n\nawait main();\n```\n\n## 50\n\n### --description--\n\nRun `solana-test-validator` to start a local Solana cluster if you do not already have one running.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nimport {\n  checkProgram,\n  establishConnection,\n  establishPayer,\n  getAccountPubkey,\n  getProgramId,\n  sayHello\n} from './hello-world.js';\n\nasync function main() {\n  console.log(`Saying 'hello' to a Solana account`);\n  const connection = establishConnection();\n  const programId = await getProgramId();\n  const payer = await establishPayer();\n  const accountPubkey = await getAccountPubkey(payer, programId);\n  await checkProgram(connection, payer, programId, accountPubkey);\n  await sayHello(connection, payer, programId, accountPubkey);\n}\n\nawait main();\n```\n\n## 51\n\n### --description--\n\nTest your script by using `node` to run it. It should produce an error.\n\n### --tests--\n\nYou should run `node src/client/main.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand.trim(),\n  'node src/client/main.js',\n  'You should run `node src/client/main.js` in the terminal'\n);\n```\n\nYou should be in the `learn-how-to-interact-with-on-chain-programs` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-how-to-interact-with-on-chain-programs');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 52\n\n### --description--\n\nYour script does not work as intended, because the program has not been deployed to the cluster. In order to deploy a program, you need to create an account.\n\nUse the terminal to create a new local keypair.\n\n### --tests--\n\nYou should run `solana-keygen new` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand.trim(),\n  'solana-keygen new',\n  'You should run `solana-keygen new` in the terminal'\n);\n```\n\nYou should have an `id.json` file in the `/root/.config/solana/` directory as a result of generating a key\n\n```js\nconst solanaDir = await __helpers.getDirectory('../../root/.config/solana');\nassert.exists(solanaDir, 'id.json');\n```\n\n## 53\n\n### --description--\n\nSet your Solana config RPC URL to the local cluster.\n\n### --tests--\n\nYou should run `solana config set --url localhost` in the terminal.\n\n```js\nconst command = `solana config get`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\n\nassert.include(\n  stdout,\n  'http://localhost:8899',\n  'You should run `solana config set --url localhost` in the terminal'\n);\n```\n\n## 54\n\n### --description--\n\nDeploy the program to the cluster.\n\n### --tests--\n\nYou should run `solana program deploy dist/program/helloworld.so` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand.trim(),\n  'solana program deploy dist/program/helloworld.so',\n  'You should run `solana program deploy dist/program/helloworld.so` in the terminal'\n);\n```\n\nYou should be in the `learn-how-to-interact-with-on-chain-programs` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-how-to-interact-with-on-chain-programs');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 55\n\n### --description--\n\nYour program might not have deployed, because you do not have enough SOL in your account. Airdrop some SOL to your account.\n\n### --tests--\n\nYou should run `solana airdrop <NUMBER>` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(\n  lastCommand,\n  'solana airdrop',\n  'You should run `solana airdrop 1` in the terminal'\n);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 56\n\n### --description--\n\nDeploy the program to the cluster.\n\n### --tests--\n\nYou should run `solana program deploy dist/program/helloworld.so` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand.trim(),\n  'solana program deploy dist/program/helloworld.so',\n  'You should run `solana program deploy dist/program/helloworld.so` in the terminal'\n);\n```\n\nYou should be in the `learn-how-to-interact-with-on-chain-programs` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-how-to-interact-with-on-chain-programs');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 57\n\n### --description--\n\nTest your script by using `node` to run it.\n\n### --tests--\n\nYou should run `node src/client/main.js` from the `learn-how-to-interact-with-on-chain-programs` directory.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand.trim(),\n  'node src/client/main.js',\n  'You should run `node src/client/main.js` from the `learn-how-to-interact-with-on-chain-programs` directory'\n);\n```\n\nYou should be in the `learn-how-to-interact-with-on-chain-programs` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-how-to-interact-with-on-chain-programs');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 58\n\n### --description--\n\nNow that you can say hello to the program, you will want to find out how many times the program has been said \"hello\" to.\n\nWithin `hello-world.js`, export an asynchronous function named `getHelloCount` with the following signature:\n\n```javascript\nfunction getHelloCount(\n  connection,\n  accountPubkey,\n): Promise<number>\n```\n\n### --tests--\n\nYou should define a function with the handle `getHelloCount`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'getHelloCount');\nassert.exists(\n  functionDeclaration,\n  'You should define a function with the handle `getHelloCount`'\n);\n```\n\nYou should define `getHelloCount` with a first parameter named `connection`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'getHelloCount');\nconst [connection] = functionDeclaration?.params;\nassert.equal(\n  connection?.name,\n  'connection',\n  'You should define `getHelloCount` with a first parameter named `connection`'\n);\n```\n\nYou should define `getHelloCount` with a second parameter named `accountPubkey`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'getHelloCount');\nconst [, accountPubkey] = functionDeclaration?.params;\nassert.equal(\n  accountPubkey?.name,\n  'accountPubkey',\n  'You should define `getHelloCount` with a second parameter named `accountPubkey`'\n);\n```\n\nYou should define `getHelloCount` as an asynchronous function.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'getHelloCount');\nassert.isTrue(\n  functionDeclaration?.async,\n  'You should define `getHelloCount` as an asynchronous function'\n);\n```\n\nYou should define `getHelloCount` to be a named export.\n\n```js\nconst exportNamedDeclaration = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => e.declaration?.id?.name === 'getHelloCount');\nassert.exists(\n  exportNamedDeclaration,\n  'You should define `getHelloCount` to be a named export'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 59\n\n### --description--\n\nWithin `getHelloCount`, create an `accountInfo` variable with a value of the account info for the public key passed as a parameter.\n\n### --tests--\n\nYou should define a variable named `accountInfo`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'accountInfo' &&\n      v.scope.join() === 'global,getHelloCount'\n  );\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `accountInfo`'\n);\n```\n\nYou should assign `accountInfo` the value of `await connection.getAccountInfo(accountPubkey)`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'accountInfo' &&\n      v.scope.join() === 'global,getHelloCount'\n  );\nconst awaitExpression = variableDeclaration?.declarations[0]?.init;\nconst callExpression = awaitExpression?.argument;\nassert.equal(\n  callExpression?.callee?.object?.name,\n  'connection',\n  'You should assign `accountInfo` the value of `await connection.getAccountInfo(accountPubkey)`'\n);\nassert.equal(\n  callExpression?.callee?.property?.name,\n  'getAccountInfo',\n  'You should assign `accountInfo` the value of `await connection.getAccountInfo(accountPubkey)`'\n);\nassert.equal(\n  callExpression?.arguments?.[0]?.name,\n  'accountPubkey',\n  'You should assign `accountInfo` the value of `await connection.getAccountInfo(accountPubkey)`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction,\n  TransactionInstruction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0)\n  };\n  const instruction = new TransactionInstruction(transaction);\n  await sendAndConfirmTransaction(\n    connection,\n    new Transaction().add(instruction),\n    [payer]\n  );\n}\n\nexport async function getHelloCount(connection, accountPubkey) {}\n```\n\n## 60\n\n### --description--\n\nIn order to read the data from an account, you need to deserialize it based on the program's schema.\n\nWithin `getHelloCount`, create a `greeting` variable with a value of:\n\n```javascript\nborsh.deserialize(<SCHEMA>, <CLASS_TYPE>, <ACCOUNT_DATA>)\n```\n\n### --tests--\n\nYou should define a variable named `greeting`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations[0].id.name === 'greeting' &&\n      v.scope.join() === 'global,getHelloCount'\n  );\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `greeting`'\n);\n```\n\nYou should give `greeting` a value of `borsh.deserialize(HelloWorldSchema, HelloWorldAccount, accountInfo.data)`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations[0].id.name === 'greeting' &&\n      v.scope.join() === 'global,getHelloCount'\n  );\nconst callExpression = variableDeclaration?.declarations[0]?.init;\nassert.equal(\n  callExpression?.callee?.object?.name,\n  'borsh',\n  'You should give `greeting` a value of `borsh...()`'\n);\nassert.equal(\n  callExpression?.callee?.property?.name,\n  'deserialize',\n  'You should give `greeting` a value of `borsh.deserialize()`'\n);\nassert.equal(\n  callExpression?.arguments[0]?.name,\n  'HelloWorldSchema',\n  'You should give `greeting` a value of `borsh.deserialize(HelloWorldSchema)`'\n);\nassert.equal(\n  callExpression?.arguments[1]?.name,\n  'HelloWorldAccount',\n  'You should give `greeting` a value of `borsh.deserialize(, HelloWorldAccount)`'\n);\nassert.equal(\n  callExpression?.arguments[2]?.object?.name,\n  'accountInfo',\n  'You should give `greeting` a value of `borsh.deserialize(, , accountInfo.data)`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction,\n  TransactionInstruction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0)\n  };\n  const instruction = new TransactionInstruction(transaction);\n  await sendAndConfirmTransaction(\n    connection,\n    new Transaction().add(instruction),\n    [payer]\n  );\n}\n\nexport async function getHelloCount(connection, accountPubkey) {\n  const accountInfo = await connection.getAccountInfo(accountPubkey);\n}\n```\n\n## 61\n\n### --description--\n\nWithin `getHelloCount`, return the `counter` property of the `greeting` variable.\n\n### --tests--\n\nYou should return the `counter` property of `greeting`.\n\n```js\nconst functionDeclaration = babelisedCode\n  .getFunctionDeclarations()\n  .find(f => f.id.name === 'getHelloCount');\nconst returnStatement = functionDeclaration?.body?.body?.find(\n  b => b.type === 'ReturnStatement'\n);\nassert.exists(returnStatement, '`getHelloCount` should return');\nassert.equal(\n  returnStatement?.argument?.object?.name,\n  'greeting',\n  'You should return the `counter` property of `greeting`'\n);\nassert.equal(\n  returnStatement?.argument?.property?.name,\n  'counter',\n  'You should return the `counter` property of `greeting`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/hello-world.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction,\n  TransactionInstruction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0)\n  };\n  const instruction = new TransactionInstruction(transaction);\n  await sendAndConfirmTransaction(\n    connection,\n    new Transaction().add(instruction),\n    [payer]\n  );\n}\n\nexport async function getHelloCount(connection, accountPubkey) {\n  const accountInfo = await connection.getAccountInfo(accountPubkey);\n  const greeting = borsh.deserialize(\n    HelloWorldSchema,\n    HelloWorldAccount,\n    accountInfo.data\n  );\n}\n```\n\n## 62\n\n### --description--\n\nWithin `main.js` in the `main` function, get the hello count, and store it in a variable named `helloCount`.\n\n### --tests--\n\nYou should define a variable named `helloCount`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'helloCount' &&\n      v.scope.join() === 'global,main'\n  );\nassert.exists(\n  variableDeclaration,\n  'You should define a variable named `helloCount`'\n);\n```\n\nYou should assign `helloCount` the value of `await getHelloCount(connection, accountPubkey)`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(\n    v =>\n      v.declarations?.[0]?.id?.name === 'helloCount' &&\n      v.scope.join() === 'global,main'\n  );\nconst awaitExpression = variableDeclaration?.declarations[0]?.init;\nconst callExpression = awaitExpression?.argument;\nassert.equal(\n  callExpression?.callee?.name,\n  'getHelloCount',\n  'You should assign `helloCount` the value of `await getHelloCount`'\n);\nassert.equal(\n  callExpression?.arguments[0]?.name,\n  'connection',\n  'You should assign `helloCount` the value of `await getHelloCount(connection, ...)`'\n);\nassert.equal(\n  callExpression?.arguments[1]?.name,\n  'accountPubkey',\n  'You should assign `helloCount` the value of `await getHelloCount(..., accountPubkey)`'\n);\n```\n\nYou should import `getHelloCount` from `./hello-world.js`.\n\n```js\nconst importDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => i.source.value === './hello-world.js');\nconst importSpecifier = importDeclaration?.specifiers?.find(\n  s => s.imported.name === 'getHelloCount'\n);\nassert.exists(\n  importSpecifier,\n  'You should import `getHelloCount` from `./hello-world.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/hello-world.js\"--\n\n```js\nimport {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction,\n  TransactionInstruction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0)\n  };\n  const instruction = new TransactionInstruction(transaction);\n  await sendAndConfirmTransaction(\n    connection,\n    new Transaction().add(instruction),\n    [payer]\n  );\n}\n\nexport async function getHelloCount(connection, accountPubkey) {\n  const accountInfo = await connection.getAccountInfo(accountPubkey);\n  const greeting = borsh.deserialize(\n    HelloWorldSchema,\n    HelloWorldAccount,\n    accountInfo.data\n  );\n  return greeting.counter;\n}\n```\n\n## 63\n\n### --description--\n\nWithin `main`, log the `helloCount` variable value.\n\n### --tests--\n\nYou should log the `helloCount` variable value.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  const callExpression = e.expression;\n  const object = callExpression?.callee?.object;\n  const property = callExpression?.callee?.property;\n  const helloCountInArgs = callExpression?.arguments?.some(\n    a =>\n      a.name === 'helloCount' ||\n      a.expressions?.find(e => e.name === 'helloCount')\n  );\n  return (\n    object?.name === 'console' &&\n    ['log', 'info', 'error', 'debug', 'table'].includes(property?.name) &&\n    helloCountInArgs\n  );\n});\nassert.exists(\n  expressionStatement,\n  'You should log the `helloCount` variable value'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-how-to-interact-with-on-chain-programs/src/client/main.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nimport {\n  checkProgram,\n  establishConnection,\n  establishPayer,\n  getAccountPubkey,\n  getHelloCount,\n  getProgramId,\n  sayHello\n} from './hello-world.js';\n\nasync function main() {\n  console.log(`Saying 'hello' to a Solana account`);\n  const connection = establishConnection();\n  const programId = await getProgramId();\n  const payer = await establishPayer();\n  const accountPubkey = await getAccountPubkey(payer, programId);\n  await checkProgram(connection, payer, programId, accountPubkey);\n  await sayHello(connection, payer, programId, accountPubkey);\n  const helloCount = await getHelloCount(connection, accountPubkey);\n}\n\nawait main();\n```\n\n## 64\n\n### --description--\n\nUse Nodejs to execute the `main.js` script.\n\n### --tests--\n\nYou should run `node src/client/main.js` in the terminal.\n\n```js\nawait new Promise(res => setTimeout(res, 1000));\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node src/client/main.js',\n  'You should run `node src/client/main.js` in the terminal'\n);\n```\n\nYour terminal should print `Hello count: <number>`\n\n```js\nconst output = await __helpers.getTerminalOutput();\nconst splitOutput = output.split('node src/client/main.js');\nconst lastOutput = splitOutput[splitOutput.length - 1];\nassert.match(lastOutput, /Hello count: \\d+/);\n```\n\n### --seed--\n\n#### --\"src/client/main.js\"--\n\n```js\nimport {\n  checkProgram,\n  establishConnection,\n  establishPayer,\n  getAccountPubkey,\n  getHelloCount,\n  getProgramId,\n  sayHello\n} from './hello-world.js';\n\nasync function main() {\n  console.log(`Saying 'hello' to a Solana account`);\n  const connection = establishConnection();\n  const programId = await getProgramId();\n  const payer = await establishPayer();\n  const accountPubkey = await getAccountPubkey(payer, programId);\n  await checkProgram(connection, payer, programId, accountPubkey);\n  await sayHello(connection, payer, programId, accountPubkey);\n  const helloCount = await getHelloCount(connection, accountPubkey);\n  console.log(`Hello count: ${helloCount}`);\n}\n\nawait main();\n```\n\n## 65\n\n### --description--\n\nContratulations on finishing this project! Feel free to play with your code.\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract.md",
    "content": "# Solana - Learn How to Set Up Solana by Building a Hello World Smart Contract\n\n## 1\n\n### --description--\n\nWelcome to the Solana curriculum! For the duration of this project, you will be working in the `learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/` directory.\n\nOpen a new terminal, and change into the above directory.\n\n_Note: Do not change the existing terminal_\n\n### --tests--\n\nYou should use `cd` to change into the `learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(\n  cwd,\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract'\n);\n```\n\n## 2\n\n### --description--\n\nYou will be using the Solana CLI to:\n\n- Configure your cluster\n- Create Keypairs\n- Log useful information\n- Deploy your on-chain program\n\nThe Solana CLI can be installed with:\n\n```bash\nsh -c \"$(curl -sSfL https://release.solana.com/v1.17.18/install)\"\n```\n\nSolana is already installed in this environment. So, run `solana --version` to confirm it is installed.\n\n### --tests--\n\nYou should run `solana --version` in the console.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\n\nassert.match(lastCommand, /solana --version/);\n```\n\nThe version should be printed to the console.\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.include(terminalOut, \"1.17.18\");\n```\n\n## 3\n\n### --description--\n\nThe Solana CLI is feature rich and has many commands.\n\nView the list of commands with:\n\n```bash\nsolana --help\n```\n\n### --tests--\n\nYou should see the list of commands.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /solana --help/);\n```\n\n## 4\n\n### --description--\n\nSee the default Solana configuration by running:\n\n```bash\nsolana config get\n```\n\n### --tests--\n\nYou should run `solana config get` in the console.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /solana config get/);\n```\n\n## 5\n\n### --description--\n\nThe Solana network consists of multiple <dfn>clusters</dfn>:\n\n- Devnet\n- Testnet\n- Mainnet\n\nDuring the initial stages of development, you are most likely to be working on a local cluster.\n\nChange your configuration to use `localhost` as the cluster:\n\n```bash\nsolana config set --url localhost\n```\n\n### --tests--\n\nYou should set the configuration with `solana config set --url localhost`.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /solana config set --url localhost/);\n```\n\n## 6\n\n### --description--\n\nView the your changed config settings.\n\n### --tests--\n\nYou should view the config with `solana config get`.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /solana config get/);\n```\n\n## 7\n\n### --description--\n\n`solana config` reads and writes to the `.config/solana` directory.\n\nView the config file contents in the terminal with:\n\n```bash\ncat ~/.config/solana/cli/config.yml\n```\n\n### --tests--\n\nYou should use `cat` to view the config file contents.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /cat ~\\/\\.config\\/solana\\/cli\\/config.yml/);\n```\n\n## 8\n\n### --description--\n\nAs this is your first time using the Solana CLI, you should generate a new keypair with:\n\n```bash\nsolana-keygen new\n```\n\n_Note:_ When prompted, hit _ENTER_ in the terminal to generate a keypair with an empty passphrase.\n\n### --tests--\n\nYou should run `solana-keygen new` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /solana-keygen new/);\n```\n\n## 9\n\n### --description--\n\nThe `keypair_path` is the path to your Solana keypair used when making transactions.\n\nView your keypair in the terminal with:\n\n```bash\ncat ~/.config/solana/id.json\n```\n\n### --tests--\n\nYou should use `cat` to view your keypair in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /cat ~\\/\\.config\\/solana\\/id\\.json/);\n```\n\n## 10\n\n### --description--\n\nView/get your wallet public key with:\n\n```bash\nsolana address\n```\n\n### --tests--\n\nYou should use `solana address` to view your public key.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /solana address/);\n```\n\n## 11\n\n### --description--\n\nYou have set your Solana config to use a locally hosted cluster, but do not have one running yet.\n\nOpen a new terminal, and start a Solana test validator with:\n\n```bash\nsolana-test-validator\n```\n\n**Note:** You need to manually click the _Run Tests_ button.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl -s -S http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout.trim());\n  assert.include(\n    jsonOut,\n    { result: 'ok' },\n    'The validator should have a \"health\" result of \"ok\"'\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 12\n\n### --description--\n\nManually make an RPC call with:\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getBalance\",\"params\":[\"your_address_public_key\",{\"commitment\":\"finalized\"}]}' http://localhost:8899\n```\n\n_Remember to replace `your_address_public_key`_\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should make an RPC call using `curl`.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput('solana address');\nconst camperPublicKey = stdout.trim();\nconst toMatch = `curl -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getBalance\", \"params\": [\"${camperPublicKey}\", { \"commitment\": \"finalized\" }]}' http://localhost:8899`;\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand.replace(/\\s/g, ''), toMatch.replace(/\\s/g, ''));\n```\n\nThe RPC call should return a successful response. _Try again_\n\n```js\nconst terminalOut = await __helpers.getTerminalOutput();\nassert.match(terminalOut, /\"result\":/);\n```\n\n## 13\n\n### --description--\n\nIt is not the best user experience using curl commands to interact with the network.\n\nView your wallet's balance with:\n\n```bash\nsolana balance <ACCOUNT_ADDRESS>\n```\n\n_Remember to replace `<ACCOUNT_ADDRESS>`_\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should use the command `solana balance <ACCOUNT_ADDRESS>`.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput('solana address');\nconst accountAddress = stdout.trim();\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, `solana balance ${accountAddress}`);\n```\n\n## 14\n\n### --description--\n\nYou can see your balance is `500000000` 😲. Maybe that is not enough for yourself 😨.\n\nRequest an _airdrop_ of 1 SOL to your account with:\n\n```bash\nsolana airdrop 1 <RECIPIENT_ACCOUNT_ADDRESS>\n```\n\n_Remember to replace `<RECIPIENT_ACCOUNT_ADDRESS>` with your public key_\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should use the above command to request an airdrop.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput('solana address');\nconst accountAddress = stdout.trim();\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, `solana airdrop 1 ${accountAddress}`);\n```\n\nYour account should have at least `500000001` SOL.\n\n```js\nconst { stdout: stdout1 } = await __helpers.getCommandOutput('solana address');\nconst accountAddress = stdout1.trim();\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ${accountAddress}`\n);\nconst balance = stdout.trim()?.match(/\\d+/)[0];\nassert.isAtLeast(Number(balance), 500000001);\n```\n\n## 15\n\n### --description--\n\nCheck out your balance.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\n\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should use `solana balance <ACCOUNT_ADDRESS>` to check your balance.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput('solana address');\nconst accountAddress = stdout.trim();\nconst lastCommand = await __helpers.getLastCommand();\n\nassert.include(lastCommand, `solana balance ${accountAddress}`);\n```\n\n## 16\n\n### --description--\n\nOpen the `src/program-rust/src/lib.rs` file. This is where you will be developing your first Solana smart contract.\n\nStart by importing the `solana_program` crate.\n\n### --tests--\n\nYou should have `use solana_program;` in `src/program-rust/src/lib.rs`.\n\n```js\nconst file = await __helpers.getFile(\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs'\n);\nassert.include(file, 'use solana_program;');\n```\n\n## 17\n\n### --description--\n\nWhen your smart contract is called, a function needs to be run.\n\nDefine a public function with the handle `process_instruction`.\n\n### --tests--\n\nYou should define a **PUBLIC** function with the handle `process_instruction`.\n\n```js\nconst file = await __helpers.getFile(\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs'\n);\n\nassert.match(file, /pub\\s+fn\\s+process_instruction\\s*\\(/s);\n```\n\n## 18\n\n### --description--\n\nIn order to tell your program which function is the entrypoint for the contract, import the `entrypoint` macro from the `solana_program` crate, and pass `process_instruction` as the argument.\n\n### --tests--\n\nYou should import `solana_program::entrypoint`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nconst variants = [\n  /solana_program\\s*::\\s*entrypoint/,\n  /solana_program\\s*::\\s*\\{\\s*entrypoint\\s*\\}/,\n  /solana_program::prelude::\\*/\n];\n\nconst someMatch = variants.some(r => file.match(r));\nassert.isTrue(someMatch, `Your code should match one of ${variants}`);\n```\n\nYou should call the `entrypoint` macro in the root of your program.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nassert.match(file, /entrypoint!\\(/);\n```\n\nYou should pass `process_instruction` as an argument: `entrypoint!(process_instruction);`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nassert.match(file, /entrypoint!\\(\\s*process_instruction\\s*\\)/s);\n```\n\n## 19\n\n### --description--\n\nTo make debugging your application easier, import the `msg` macro from `solana_program`, and use it to log the string slice `Hello World` to the console whenever `process_instruction` is called.\n\n### --tests--\n\nYou should import `solana_program::msg;`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nconst variants = [\n  /solana_program\\s*::\\s*msg/,\n  /solana_program\\s*::\\s*\\{[\\s\\S]*msg/,\n  /solana_program::prelude::\\*/\n];\n\nconst someMatch = variants.some(r => file.match(r));\nassert.isTrue(someMatch, `Your code should match one of ${variants}`);\n```\n\nYou should call the `msg` macro within the `process_instruction` function.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nassert.match(file, /msg!\\(/);\n```\n\nYou should pass `\"Hello World\"` to `msg` as an argument: `msg!(\"Hello World\");`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nassert.match(file, /msg!\\(\\s*\"Hello World\"\\s*\\)/s);\n```\n\n## 20\n\n### --description--\n\nWithin `src/program-rust/`, run `cargo build` to try build your library. You should see an error, because an entrypoint function is supposed to take 3 arguments.\n\n### --tests--\n\nYou should run `cargo build` within the `src/program-rust/` directory.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /cargo build/, 'Last command was incorrect');\n\nconst dir = await __helpers.getCWD();\nconst cwd = dir.split('\\n').filter(Boolean).pop();\nassert.match(\n  cwd,\n  /src\\/program-rust\\/?$/,\n  \"You should be in the 'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust' dir\"\n);\n```\n\n## 21\n\n### --description--\n\nThe first argument an entrypoint function takes is a reference to a `Pubkey` which is the public key of the account the program was loaded into.\n\nAdd a parameter to the function definition named `program_id` with the correct type.\n\n_Import the `Pubkey` struct from the `pubkey` module of `solana_program`._\n\n### --tests--\n\nYou should have `use solana_program::pubkey::Pubkey;` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nconst variants = [\n  /solana_program\\s*::\\s*pubkey::Pubkey/,\n  /solana_program\\s*::\\s*\\{[\\s\\S]*pubkey::Pubkey/,\n  /solana_program::prelude::\\*/\n];\n\nconst someMatch = variants.some(r => file.match(r));\nassert.isTrue(someMatch, `Your code should match one of ${variants}`);\n```\n\nYou should define `process_instruction` to have one parameter named `program_id`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /process_instruction\\s*\\(\\s*program_id\\s*/s);\n```\n\nYou should type `program_id` with `&Pubkey`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /program_id\\s*:\\s*&Pubkey/s);\n```\n\n## 22\n\n### --description--\n\nThe second argument an entrypoint function takes is a slice of accounts with which the program can interact.\n\nAdd a parameter to the function definition named `accounts` with the type `&[AccountInfo]`.\n\n_Import the `AccountInfo` struct from the `account_info` module of `solana_program`._\n\n### --tests--\n\nYou should have `use solana_program::account_info::AccountInfo;` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nconst variants = [\n  /solana_program\\s*::\\s*account_info::AccountInfo/,\n  /solana_program\\s*::\\s*\\{[\\s\\S]*account_info::AccountInfo/,\n  /solana_program::prelude::\\*/\n];\n\nconst someMatch = variants.some(r => file.match(r));\nassert.isTrue(someMatch, `Your code should match one of ${variants}`);\n```\n\nYou should define `process_instruction` to have a second parameter named `accounts`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /process_instruction\\s*\\(.*?,\\s*accounts/s);\n```\n\nYou should type `accounts` with `&[AccountInfo]`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /accounts\\s*:\\s*&\\[\\s*AccountInfo\\s*\\]/s);\n```\n\n## 23\n\n### --description--\n\nThe third argument an entrypoint function takes is instruction data from the smart contract call.\n\nAdd a parameter to the function definition named `instruction_data` with the type `&[u8]`.\n\n### --tests--\n\nYou should define `process_instruction` to have a third parameter named `instruction_data`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /process_instruction\\s*\\(.*?,\\s*instruction_data/s);\n```\n\nYou should type `instruction_data` with `&[u8]`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /instruction_data\\s*:\\s*&\\[\\s*u8\\s*\\]/s);\n```\n\n## 24\n\n### --description--\n\nGive your entrypoint function a return of `ProgramResult`. This type comes from the `entrypoint` module of `solana_program`.\n\nAlso, return an empty tuple wrapped in the `Ok` variant of the `Result` enum.\n\n### --tests--\n\nYou should have `use solana_program::entrypoint::ProgramResult;` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nconst variants = [\n  /solana_program\\s*::\\s*entrypoint::ProgramResult/,\n  /solana_program\\s*::\\s*\\{[\\s\\S]*entrypoint::ProgramResult/,\n  /solana_program::prelude::\\*/\n];\n\nconst someMatch = variants.some(r => file.match(r));\nassert.isTrue(someMatch, `Your code should match one of ${variants}`);\n```\n\nYou should define `process_instruction` to return `ProgramResult`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /\\)\\s*->\\s*ProgramResult\\s*\\{/s);\n```\n\nYou should return `Ok(())` from `process_instruction`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /Ok\\s*\\(\\s*\\(\\s*\\)\\s*\\)/s);\n```\n\n## 25\n\n### --description--\n\nNow that the entrypoint function definition is correct, rebuild your program to see if it compiles.\n\n### --tests--\n\nYou should run `cargo build` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'cargo build');\n```\n\nYou should be in the `src/program-rust` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(cwd, /src\\/program-rust\\/?$/);\n```\n\n## 26\n\n### --description--\n\nBefore deploying, build your program with:\n\n```bash\ncargo build-sbf --sbf-out-dir=../../dist/program\n```\n\n### --tests--\n\nYou should run the above command to build your program.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(lastCommand, /cargo build-sbf --sbf-out-dir=.*?/s);\n```\n\nYou should be in the `src/program-rust` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(cwd, /src\\/program-rust\\/?$/);\n```\n\n## 27\n\n### --description--\n\nYour program is located in `dist/program/helloworld.so`. You can deploy it to your localnet with:\n\n```bash\nsolana program deploy <PATH_TO_PROGRAM>\n```\n\n**NOTE:** `solana deploy <PATH_TO_PROGRAM>` will **not** work, because that deploys a non-upgradeable program.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should run `solana program deploy dist/program/helloworld.so` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'solana program deploy dist/program/helloworld.so');\n```\n\nYou should be in the `learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(\n  cwd,\n  /learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\\/?$/\n);\n```\n\n## 28\n\n### --description--\n\nAfter deploying, your program id should be printed to the console.\n\nView the program account with:\n\n```bash\nsolana program show <PROGRAM_ID>\n```\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should run `solana program show <PROGRAM_ID>` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'solana program show');\n```\n\n## 29\n\n### --description--\n\nTo view the logs from an on-chain program, open a new terminal, and run:\n\n```bash\nsolana logs\n```\n\n### --tests--\n\nYou should run `solana logs` in a new terminal.\n\n```js\nconst temp = await __helpers.getTemp();\nassert.include(temp, 'Streaming transaction logs');\n```\n\n## 30\n\n### --description--\n\nTo call your program, run:\n\n```bash\nnpm run call:hello-world\n```\n\nThis will run the code in `src/client/`. Watch the `solana logs` terminal for the _'Program log'_ output.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should run `npm run call:hello-world` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'npm run call:hello-world');\n```\n\nYou should be in the `/learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(\n  cwd,\n  /learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\\/?$/\n);\n```\n\n## 31\n\n### --description--\n\nHaving a program that prints `Hello World` to the console is fun, but it is not very useful. Make it do something more interesting.\n\nWithin the `process_instruction` function, create an iterator over the `accounts`, and store the iterator in a variable named `accounts_iter`.\n\n_Note: Make `accounts_iter` mutable_\n\n### --tests--\n\nYou should have `let mut accounts_iter = accounts.iter();` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /let\\s+mut\\s+accounts_iter\\s*=\\s*accounts\\.iter\\(\\s*\\)\\s*;/s\n);\n```\n\n## 32\n\n### --description--\n\nSafely access the next element of the `accounts_iter` collection with:\n\n```rust\nif let Some(account) = accounts_iter.next() {\n\n}\n```\n\nAlso, add an `else` clause to the `if let`, and use `msg` to log an appropriate message to the console.\n\n### --tests--\n\nYou should have `if let Some(account) = accounts_iter.next() {}` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /if\\s+let\\s+Some\\s*\\(\\s*account\\s*\\)\\s*=\\s*accounts_iter\\.next\\s*\\(\\s*\\)\\s*\\{\\s*\\}/s\n);\n```\n\nYou should add an `else` clause with a call to the `msg` macro.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /\\}\\s*else\\s*\\{\\s*msg!\\s*\\(/s);\n```\n\n## 33\n\n### --description--\n\nImport the `ProgramError` enum from the `program_error` module of `solana_program`.\n\n### --tests--\n\nYou should have `use solana_program::program_error::ProgramError;` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nconst variants = [\n  /solana_program\\s*::\\s*program_error::ProgramError/,\n  /solana_program\\s*::\\s*\\{[\\s\\S]*program_error::ProgramError/,\n  /solana_program::prelude::\\*/\n];\n\nconst someMatch = variants.some(r => file.match(r));\nassert.isTrue(someMatch, `Your code should match one of ${variants}`);\n```\n\n## 34\n\n### --description--\n\nBe sure to call `Ok(())` when your function succeeds. Otherwise use the `NotEnoughAccountKeys` variant of `ProgramError` as the return for the `Err` variant of your function.\n\n### --tests--\n\nYou should return `Ok(())` in the `if let` block of `process_instruction`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /(?<=\\.next\\s*\\(\\s*\\)\\s*\\{).*?Ok\\(\\s*\\(\\s*\\)\\s*\\);?\\s*\\}/s);\n```\n\nYou should return `Err(ProgramError::NotEnoughAccountKeys)` in the `else` block of `process_instruction`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /(?<=else\\s*\\{).*?Err\\s*\\(\\s*ProgramError::NotEnoughAccountKeys\\s*\\)/s\n);\n```\n\n## 35\n\n### --description--\n\nEach `AccountInfo` element has an `owner` field which is the public key of the program that owns the account.\n\nAdd an `if` statement checking for the case where this field's value does **not** match the `program_id` value.\n\n### --tests--\n\nYou should have `if account.owner != program_id {}` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /if\\s+(account\\.owner\\s*!=\\s*program_id)|(program_id\\s*!=\\s*account\\.owner)\\s*\\{/s\n);\n```\n\n## 36\n\n### --description--\n\nWithin the `if` statement, use `msg` to log `Account info does not match program id` to the console.\n\n### --tests--\n\nYou should have `msg!(\"Account info does not match program id\");` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /msg!\\s*\\(\\s*\"Account info does not match program id\"\\s*\\)\\s*;/s\n);\n```\n\n## 37\n\n### --description--\n\nWithin the `if` statement, return the `IncorrectProgramId` variant of `ProgramError` as the `Err`.\n\n### --tests--\n\nYou should return `Err(ProgramError::IncorrectProgramId)` in the `if` statement.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /(?<=if.*?\\{).*?Err\\s*\\(\\s*ProgramError::IncorrectProgramId\\s*\\);?\\s*\\}/s\n);\n```\n\n## 38\n\n### --description--\n\nIn order to interact with the data associated with this smart contract account, you need to define a struct resembling the account data.\n\nDefine a struct named `GreetingAccount` with a public field named `counter` with a value of `u32`.\n\n### --tests--\n\nYou should have `pub struct GreetingAccount { pub counter: u32 }` in the root of `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /pub\\s+struct\\s+GreetingAccount\\s*\\{\\s*pub\\s+counter\\s*:\\s*u32\\s*\\}/s\n);\n```\n\n## 39\n\n### --description--\n\nDerive `BorshSerialize` and `BorshDeserialize` for your `GreetingAccount` struct to be able to serialize and deserilize the data in the account.\n\n### --tests--\n\nYou should add `#[derive(BorshSerialize, BorshDeserialize)]` above `GreetingAccount`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file.replace(/\\s+/g, ''),\n  /#\\[derive\\((BorshSerialize,BorshDeserialize)|(BorshDeserialize,BorshSerialize)\\)\\]pubstructGreetingAccount/s\n);\n```\n\n## 40\n\n### --description--\n\nBring `BorshSerialize` and `BorshDeserialize` in scope from the `borsh` crate.\n\n### --tests--\n\nYou should have `use borsh::BorshSerialize;` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nconst variants = [\n  /borsh\\s*::\\s*BorshSerialize/,\n  /borsh\\s*::\\s*\\{[\\s\\S]*BorshSerialize/,\n  /borsh::prelude::\\*/\n];\n\nconst someMatch = variants.some(r => file.match(r));\nassert.isTrue(someMatch, `Your code should match one of ${variants}`);\n```\n\nYou should have `use borsh::BorshDeserialize;` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\n\nconst variants = [\n  /borsh\\s*::\\s*BorshDeserialize/,\n  /borsh\\s*::\\s*\\{[\\s\\S]*BorshDeserialize/,\n  /borsh::prelude::\\*/\n];\n\nconst someMatch = variants.some(r => file.match(r));\nassert.isTrue(someMatch, `Your code should match one of ${variants}`);\n```\n\n## 41\n\n### --description--\n\nDeserialize the `data` in the `account` variable with:\n\n```rust\nGreetingAccount::try_from_slice(&account.data.borrow())?;\n```\n\nAssign this value to a mutable variable named `greeting_account`.\n\n### --tests--\n\nYou should declare a mutable variable named `greeting_account`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /let\\s+mut\\s+greeting_account\\s*=/s);\n```\n\nYou should assign `GreetingAccount::try_from_slice(&account.data.borrow())?;` to `greeting_account`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /greeting_account\\s*=\\s*GreetingAccount::try_from_slice\\s*\\(\\s*&account.data.borrow\\s*\\(\\s*\\)\\s*\\)\\s*\\?\\s*;/s\n);\n```\n\n## 42\n\n### --description--\n\nIncrement the `counter` field of `greeting_account` by one.\n\n### --tests--\n\nYou should have `greeting_account.counter += 1;` in `src/program-rust/src/lib.rs`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /greeting_account\\.counter\\s*\\+=\\s*1\\s*;/s);\n```\n\n## 43\n\n### --description--\n\nDeclare a new variable named `acc_data`, and assign it the value of `&mut account.data.borrow_mut()[..]`.\n\n### --tests--\n\nYou should declare a new variable named `acc_data`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /let\\s+acc_data\\s*=/s);\n```\n\nYou should assign `&mut account.data.borrow_mut()[..]` to `acc_data`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /acc_data\\s*=\\s*&mut\\s+account\\.data\\.borrow_mut\\s*\\(\\s*\\)\\s*\\[\\s*\\.\\.\\s*\\]\\s*;/s\n);\n```\n\n## 44\n\n### --description--\n\nSerialize the account data into your program with:\n\n```rust\ngreeting_account.serialize(&mut acc_data.as_mut())?;\n```\n\n### --tests--\n\nYou should serialize the mutated data with `greeting_account.serialize(&mut acc_data.as_mut())?`.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(\n  file,\n  /greeting_account\\.serialize\\s*\\(\\s*&mut\\s+acc_data\\.as_mut\\s*\\(\\s*\\)\\s*\\)\\s*\\?\\s*;/s\n);\n```\n\n## 45\n\n### --description--\n\nLog the number of times the account has been greeted, using the `msg` macro.\n\n### --tests--\n\nYou should use `msg` to log the number of times the account has been greeted.\n\n```js\nconst filePath =\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs';\nconst file = await __helpers.getFile(filePath);\nassert.match(file, /msg!\\(/);\n```\n\n## 46\n\n### --description--\n\nNow that your program is complete, rebuild it.\n\n### --tests--\n\nYou should run `cargo build-sbf --sbf-out-dir=../../dist/program` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'cargo build-sbf --sbf-out-dir=../../dist/program');\n```\n\nYou should be in the `src/program-rust` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(cwd, /src\\/program-rust\\/?$/);\n```\n\n## 47\n\n### --description--\n\nRe-deploy your program to your local Solana cluster.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should run `solana program deploy dist/program/helloworld.so` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'solana program deploy dist/program/helloworld.so');\n```\n\nYou should be in the `learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(\n  cwd,\n  /learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\\/?$/\n);\n```\n\n## 48\n\n### --description--\n\nIf you read closely, you should see your program failed to deploy.\n\nWhen you initially deployed, Solana allocated twice the amount of data it needed to store your account.\n\nUse `solana program show <PROGRAM_ID>` to view the data allocated for your program account.\n\n### --tests--\n\nYou should run `solana program show <PROGRAM_ID>` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'solana program show');\n```\n\n## 49\n\n### --description--\n\nThe `Data Length` field returned shows the size (in bytes) allocated for your program account.\n\nCheck your current program account size by running:\n\n```bash\ndu -b dist/program/helloworld.so\n```\n\n### --tests--\n\nYou should run `du -b dist/program/helloworld.so` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'du -b dist/program/helloworld.so');\n```\n\nYou should be in the `learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(\n  cwd,\n  /learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\\/?$/\n);\n```\n\n## 50\n\n### --description--\n\nDeploy a new program, by deleting the `dist/` directory, building again, then deploying.\n\n### --tests--\n\nYou should rebuild your program with `cargo build-sbf`.\n\n```js\nconst isFileExists = __helpers.fileExists(\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/dist/program/helloworld.so'\n);\nassert.isTrue(\n  isFileExists,\n  'The `dist/program/helloworld.so` file should exist'\n);\n```\n\nYou should deploy your new program with `solana program deploy <PATH_TO_PROGRAM>`.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'solana program deploy');\n```\n\n## 51\n\n### --description--\n\nSend a message to your program to increment the counter by running:\n\n```bash\nnpm run call:hello-world\n```\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should run `npm run call:hello-world` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'npm run call:hello-world');\n```\n\nYou should be in the `learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(\n  cwd,\n  /learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\\/?$/\n);\n```\n\n## 52\n\n### --description--\n\nSolana does not store smart contract state using the program account. Instead, a new account is used solely for the program state (data).\n\nView the account address for this _data account_ by running:\n\n```bash\nnpm run call:hello-world\n```\n\nSee which address the program says hello to.\n\n### --tests--\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should run `npm run call:hello-world` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'npm run call:hello-world');\n```\n\nYou should be in the `learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(\n  cwd,\n  /learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\\/?$/\n);\n```\n\n## 53\n\n### --description--\n\nView the updated account state associated with your program account:\n\n```bash\nsolana account <ACCOUNT_ADDRESS>\n```\n\n### --tests--\n\nYou should run `solana account <ACCOUNT_ADDRESS>` in the terminal.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  'npm run call:hello-world',\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract'\n);\nconst programStateAccount = stdout.match(/Saying hello to: (\\w+)/)?.[1];\nconst toMatch = `solana account ${programStateAccount}`;\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, toMatch);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 54\n\n### --description--\n\nNotice the final line of output with the name `0000`. This is the data you are storing!\n\nTake note of its value. Then, run `npm run call:hello-world` again.\n\n### --tests--\n\nYou should run `npm run call:hello-world` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'npm run call:hello-world');\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nYou should be in the `learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/` directory.\n\n```js\nconst wds = await __helpers.getCWD();\nconst cwd = wds.split('\\n').filter(Boolean).pop();\nassert.match(\n  cwd,\n  /learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\\/?$/\n);\n```\n\n## 55\n\n### --description--\n\nNow, get the data account state again, and look at the change in the data value.\n\n### --tests--\n\nYou should run `solana account <ACCOUNT_ADDRESS>` in the terminal.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  'npm run call:hello-world',\n  'learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract'\n);\nconst programStateAccount = stdout.match(/Saying hello to: (\\w+)/)?.[1];\nconst toMatch = `solana account ${programStateAccount}`;\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, toMatch);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 56\n\n### --description--\n\nContratulations on finishing this project! Feel free to play with your code.\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-solanas-token-program-by-minting-a-fungible-token.md",
    "content": "# Solana - Learn Solana's Token Program by Minting a Fungible Token\n\n## 1\n\n### --description--\n\nIn this project, you will learn how to create a <dfn title=\"A digitally transferable asset whose supply is alterable.\">fungible token</dfn> on the Solana blockchain. You will be able to create your own token, and mint an unlimited supply of that token. You will be able to send your tokens to other Solana wallets.\n\nFor the duration of this project, you will be working in the `learn-solanas-token-program-by-minting-a-fungible-token/` directory.\n\nChange into the above directory in a new bash terminal.\n\n### --tests--\n\nYou can use `cd` to change into the `learn-solanas-token-program-by-minting-a-fungible-token/` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-solanas-token-program-by-minting-a-fungible-token');\n```\n\n## 2\n\n### --description--\n\nPreviously, you interacted with the _System Program_ to create accounts. Now, you will interact with the _Token Program_ to work with fungible tokens.\n\nCreating a <dfn title=\"A digitally transferable asset whose supply is alterable.\">fungible token</dfn> on Solana requires the following steps:\n\n1. Creating a _Mint_ account\n2. Creating a _Token_ account\n3. Minting tokens\n\nCreate a file named `create-mint-account.js`.\n\n### --tests--\n\nYou should have a `create-mint-account.js` file.\n\n```js\nconst fileExists = __helpers.fileExists(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-mint-account.js'\n);\nassert.isTrue(fileExists);\n```\n\n## 3\n\n### --description--\n\nWithin `create-mint-account.js`, declare a variable `connection`, and assign it a new instance of the `Connection` class. Pass in your local Solana RPC URL as the first argument.\n\n### --tests--\n\nYou should have `const connection = new Connection('http://localhost:8899');` in `create-mint-account.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'You should declare a variable named `connection`'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee.name,\n  'Connection',\n  'You should initialise `connection` with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments[0].value,\n  'http://localhost:8899',\n  \"You should create a new connection with `new Connection('http://localhost:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'You should import from `@solana/web3.js`'\n);\nconst connectionImportSpecifier = solanaWeb3ImportDeclaration.specifiers.find(\n  s => {\n    return s.imported.name === 'Connection';\n  }\n);\nassert.exists(\n  connectionImportSpecifier,\n  'You should import `Connection` from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-mint-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\ntouch create-mint-account.js\n```\n\n## 4\n\n### --description--\n\nYou will need the `@solana/spl-token` package, as well as the `@solana/web3.js` package to interact with the _Token Program_.\n\nInstall both packages using `npm`.\n\n### --tests--\n\nYou should have at least version `1.70.0` of `@solana/web3.js` added to the `package.json` dependencies.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    './learn-solanas-token-program-by-minting-a-fungible-token/package.json'\n  )\n);\nconst web3Version = packageJson.dependencies?.['@solana/web3.js'];\nassert.exists(\n  web3Version,\n  'You should have `@solana/web3.js` in your dependencies'\n);\n// Manually check SemVer is at least 1.70.0\nconst web3VersionParts = web3Version.split('.').map(p => p.replace(/\\D/g, ''));\nassert.isAtLeast(\n  parseInt(web3VersionParts[0]),\n  1,\n  '`@solana/web3.js` should have a major version of at least 1'\n);\nassert.isAtLeast(\n  parseInt(web3VersionParts[1]),\n  70,\n  '`@solana/web3.js` should have a minor version of at least 70'\n);\nassert.isAtLeast(\n  parseInt(web3VersionParts[2]),\n  0,\n  '`@solana/web3.js` should have a patch version of at least 0'\n);\n```\n\nYou should have at least version `0.3.6` of `@solana/spl-token` added to the `package.json` dependencies.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    './learn-solanas-token-program-by-minting-a-fungible-token/package.json'\n  )\n);\nconst splTokenVersion = packageJson.dependencies?.['@solana/spl-token'];\nassert.exists(\n  splTokenVersion,\n  'You should have `@solana/spl-token` in your dependencies'\n);\n// Manually check SemVer is at least 0.3.6\nconst splTokenVersionParts = splTokenVersion\n  .split('.')\n  .map(p => p.replace(/\\D/g, ''));\nassert.isAtLeast(\n  parseInt(splTokenVersionParts[0]),\n  0,\n  '`@solana/spl-token` should have a major version of at least 0'\n);\nassert.isAtLeast(\n  parseInt(splTokenVersionParts[1]),\n  3,\n  '`@solana/spl-token` should have a minor version of at least 3'\n);\nassert.isAtLeast(\n  parseInt(splTokenVersionParts[2]),\n  6,\n  '`@solana/spl-token` should have a patch version of at least 6'\n);\n```\n\n### --seed--\n\n#### --\"create-mint-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n```\n\n## 5\n\n### --description--\n\nCreating a Mint Account requires another account to pay the fees.\n\nImport the `payer` variable from `utils.js`.\n\n### --tests--\n\nYou should have `import { payer } from './utils.js';` in `create-mint-account.js`.\n\n```js\nconst utilsImportDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(utilsImportDeclaration, 'You should import from `./utils.js`');\nconst payerImportSpecifier = utilsImportDeclaration.specifiers.find(s => {\n  return s.imported.name === 'payer';\n});\nassert.exists(\n  payerImportSpecifier,\n  'You should import `payer` from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-mint-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\nnpm install @solana/web3.js@1 @solana/spl-token\n```\n\n## 6\n\n### --description--\n\nThe `payer` variable is a `Keypair` constructed from a `wallet.json` file.\n\nUse `solana-keygen` to create a new keypair, and save it to a file named `wallet.json`. You will be prompted to enter a passphrase. You can leave this blank.\n\n### --tests--\n\nYou should have a `wallet.json` file in the `learn-solanas-token-program-by-minting-a-fungible-token/` directory.\n\n```js\nconst walletJsonExists = __helpers.fileExists(\n  'learn-solanas-token-program-by-minting-a-fungible-token/wallet.json'\n);\nassert.isTrue(walletJsonExists, 'The `wallet.json` file should exist');\nconst walletJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-solanas-token-program-by-minting-a-fungible-token/wallet.json'\n  )\n);\nassert.isArray(\n  walletJson,\n  'The `wallet.json` file should be an array of numbers.\\nRun `solana-keygen new --outfile wallet.json` to create a new keypair.'\n);\n```\n\n### --seed--\n\n#### --\"create-mint-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { payer } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n```\n\n## 7\n\n### --description--\n\nA Mint Account also requires a _mint authority_ which is an account that controls the minting of the token. You will set the payer to be the mint authority.\n\nWithin `create-mint-account.js`, declare a variable `mintAuthority`, and set it equal to the `payer` public key.\n\n### --tests--\n\nYou should have `const mintAuthority = payer.publicKey;` in `create-mint-account.js`.\n\n```js\nconst mintAuthorityDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'mintAuthority';\n  });\nassert.exists(\n  mintAuthorityDeclaration,\n  'A variable named `mintAuthority` should exist'\n);\nconst mintAuthorityMemberExpression =\n  mintAuthorityDeclaration.declarations?.[0]?.init;\nassert.exists(\n  mintAuthorityMemberExpression,\n  'The `mintAuthority` variable should have an initialiser'\n);\nassert.equal(\n  mintAuthorityMemberExpression.object?.name,\n  'payer',\n  'The `mintAuthority` variable should be initialised with `payer.publicKey`'\n);\nassert.equal(\n  mintAuthorityMemberExpression.property?.name,\n  'publicKey',\n  'The `mintAuthority` variable should be initialised with `payer.publicKey`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-mint-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\nsolana-keygen new --no-bip39-passphrase --silent --outfile wallet.json\n```\n\n## 8\n\n### --description--\n\nA Mint Account requires a _freeze authority_ which is an account that controls the <dfn title=\"Freezing a token account prevents that account from receiving/transfering the associated token.\">freezing of token accounts</dfn>. You will set the payer to be the freeze authority.\n\nWithin `create-mint-account.js`, declare a variable `freezeAuthority`, and set it equal to the `payer` public key.\n\n### --tests--\n\nYou should have `const freezeAuthority = payer.publicKey;` in `create-mint-account.js`.\n\n```js\nconst freezeAuthorityDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.name === 'freezeAuthority';\n  });\nassert.exists(\n  freezeAuthorityDeclaration,\n  'A variable named `freezeAuthority` should exist'\n);\nconst freezeAuthorityMemberExpression =\n  freezeAuthorityDeclaration.declarations?.[0]?.init;\nassert.exists(\n  freezeAuthorityMemberExpression,\n  'The `freezeAuthority` variable should have an initialiser'\n);\nassert.equal(\n  freezeAuthorityMemberExpression.object?.name,\n  'payer',\n  'The `freezeAuthority` variable should be initialised with `payer.publicKey`'\n);\nassert.equal(\n  freezeAuthorityMemberExpression.property?.name,\n  'publicKey',\n  'The `freezeAuthority` variable should be initialised with `payer.publicKey`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-mint-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"create-mint-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { payer } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst mintAuthority = payer.publicKey;\n```\n\n## 9\n\n### --description--\n\nImport the `createMint` function from `@solana/spl-token`, to create and initialize a new Mint Account:\n\n```typescript\ncreateMint(\n  connection: Connection,\n  payer: Signer,\n  mintAuthority: PublicKey,\n  freezeAuthority: PublicKey | null,\n  decimals: number\n): Promise<PublicKey>\n```\n\nCall the `createMint` function, passing in logical arguments. Store the awaited result in a variable named `mint`.\n\nThe _decimals_ value is the number of decimal places the token will have. Set the decimals to `9` - the same as the native SOL token:\n\n<div style=\"margin:auto;display:table;\">\n\n| Decimals | Smallest Token Unit |\n| :------: | :-----------------: |\n|    0     |          1          |\n|    1     |         0.1         |\n|   ...    |         ...         |\n|    9     |     0.000000001     |\n\n</div>\n\n### --tests--\n\nYou should import `createMint` from `@solana/spl-token`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/spl-token';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'createMint',\n  '`createMint` should be imported from `@solana/spl-token`'\n);\n```\n\nYou should have `const mint = await createMint(connection, payer, mintAuthority, freezeAuthority, 9);` in `create-mint-account.js`.\n\n```js\nconst mintDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'mint';\n});\nassert.exists(mintDeclaration, 'A variable named `mint` should exist');\nconst mintAwaitExpression = mintDeclaration.declarations?.[0]?.init;\nassert.equal(\n  mintAwaitExpression.type,\n  'AwaitExpression',\n  'The `createMint` call should be awaited'\n);\nconst createMintCallExpression = mintAwaitExpression.argument;\nassert.equal(\n  createMintCallExpression.callee.name,\n  'createMint',\n  'The `mint` variable should be initialised with the `createMint` function'\n);\nconst createMintArguments = createMintCallExpression.arguments;\nassert.equal(\n  createMintArguments.length,\n  5,\n  'The `createMint` function should be called with 5 arguments'\n);\nconst [\n  connectionArgument,\n  payerArgument,\n  mintAuthorityArgument,\n  freezeAuthorityArgument,\n  decimalsArgument\n] = createMintArguments;\nassert.equal(\n  connectionArgument.name,\n  'connection',\n  'The first argument to `createMint` should be `connection`'\n);\nassert.equal(\n  payerArgument.name,\n  'payer',\n  'The second argument to `createMint` should be `payer`'\n);\nassert.equal(\n  mintAuthorityArgument.name,\n  'mintAuthority',\n  'The third argument to `createMint` should be `mintAuthority`'\n);\nassert.equal(\n  freezeAuthorityArgument.name,\n  'freezeAuthority',\n  'The fourth argument to `createMint` should be `freezeAuthority`'\n);\nassert.equal(\n  decimalsArgument.value,\n  9,\n  'The fifth argument to `createMint` should be `9`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-mint-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"create-mint-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { payer } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst mintAuthority = payer.publicKey;\nconst freezeAuthority = payer.publicKey;\n```\n\n## 10\n\n### --description--\n\nThe `createMint` function returns the public key of the newly-created Mint Account.\n\nLog the base-58 representation of `mint` to the console.\n\n### --tests--\n\nYou should use the `toBase58` method on `mint`.\n\n```js\nconst mintMemberExpression = babelisedCode\n  .getType('MemberExpression')\n  .find(m => {\n    return m.object?.name === 'mint' && m.property?.name === 'toBase58';\n  });\nassert.exists(\n  mintMemberExpression,\n  'The `mint` variable should have a `toBase58` method called on it'\n);\n```\n\nYou can have `console.log(mint.toBase58());` in `create-mint-account.js`.\n\n```js\nconst consoleLogCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return (\n      c.callee?.object?.name === 'console' && c.callee?.property?.name === 'log'\n    );\n  });\nassert.exists(consoleLogCallExpression, 'A `console.log` call should exist');\nconst consoleLogArguments = consoleLogCallExpression.arguments;\n// Assert one of the arguments is the `mint.toBase58()` call\nconst mintToBase58CallExpression = consoleLogArguments.find(a => {\n  return (\n    a.type === 'CallExpression' &&\n    a.callee.object.name === 'mint' &&\n    a.callee.property.name === 'toBase58'\n  );\n});\nassert.exists(\n  mintToBase58CallExpression,\n  'One of the arguments to `console.log` should be `mint.toBase58()`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-mint-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"create-mint-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { payer } from './utils.js';\nimport { createMint } from '@solana/spl-token';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst mintAuthority = payer.publicKey;\nconst freezeAuthority = payer.publicKey;\n\nconst mint = await createMint(\n  connection,\n  payer,\n  mintAuthority,\n  freezeAuthority,\n  9\n);\n```\n\n## 11\n\n### --description--\n\nStart a local Solana cluster. Ensure the RPC URL is set to `http://localhost:8899`.\n\n### --tests--\n\nYour Solana config RPC URL should be set to `http://localhost:8899`.\n\n```js\nconst command = `solana config get json_rpc_url`;\nawait new Promise(res => setTimeout(() => res(), 2000));\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\nassert.include(\n  stdout,\n  'http://localhost:8899',\n  'Try running `solana config set --url localhost`'\n);\n```\n\nYou should run `solana-test-validator` in a separate terminal.\n\n```js\nawait new Promise(res => setTimeout(() => res(), 2000));\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --\"create-mint-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { payer } from './utils.js';\nimport { createMint } from '@solana/spl-token';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst mintAuthority = payer.publicKey;\nconst freezeAuthority = payer.publicKey;\n\nconst mint = await createMint(\n  connection,\n  payer,\n  mintAuthority,\n  freezeAuthority,\n  9\n);\n\nconsole.log('Token Unique Identifier:', mint.toBase58());\n```\n\n## 12\n\n### --description--\n\nAirdrop some SOL to the payer account to pay for the transaction fees:\n\n```bash\nsolana airdrop <amount_of_sol> ./wallet.json\n```\n\n### --tests--\n\nThe `wallet.json` account should have at least 2 SOL.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ./learn-solanas-token-program-by-minting-a-fungible-token/wallet.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAtLeast(\n  parseInt(balance),\n  2,\n  'The `wallet.json` account should have at least 2 SOL'\n);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 13\n\n### --description--\n\nRun the `create-mint-account.js` script:\n\n```bash\nnode create-mint-account.js\n```\n\n<details>\n  <summary>NOTE: Error</summary>\n\nIf this command fails with the below error, wait a few seconds and run it again - transactions (e.g. Airdrops) take a few seconds to be finalised.\n\n```bash\n/workspace/solana-curriculum/learn-solanas-token-program-by-minting-a-fungible-token/node_modules/@solana/web3.js/lib/index.cjs.js:9754\n      throw new SendTransactionError('failed to send transaction: ' + res.error.message, logs);\n            ^\n\nSendTransactionError: failed to send transaction: Transaction simulation failed: Attempt to debit an account but found no record of a prior credit.\n```\n\n</details>\n\n### --tests--\n\nYou should run `node create-mint-account.js` in a terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(\n  lastCommand,\n  'node create-mint-account.js',\n  'You should run `node create-mint-account.js` in a terminal'\n);\n```\n\nThe output of `node create-mint-account.js` should include the base-58 representation of the Mint Account.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.match(terminalOutput, /[A-z0-9]{44}/);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 14\n\n### --description--\n\nThe output should include the base-58 representation of the Mint Account. In other words, the mint account's public key address.\n\nCopy this address, and paste it into the `MINT_ADDRESS_58` variable in `utils.js`. Then, uncomment the `mintAddress` export statement.\n\n### --tests--\n\nYou should have `const MINT_ADDRESS_58 = '...';` in `utils.js`.\n\n```js\n// TODO: Note that seed cannot add this...\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'MINT_ADDRESS_58');\nassert.exists(\n  variableDeclaration,\n  'The `MINT_ADDRESS_58` variable is should exist'\n);\nconst value = variableDeclaration.declarations?.[0]?.init?.value;\nassert.isString(value, 'The `MINT_ADDRESS_58` value should be a string');\nassert.isNotEmpty(value, 'The `MINT_ADDRESS_58` value should not be empty');\n```\n\nYou should uncomment `export const mintAddress = new PublicKey(MINT_ADDRESS_58);` in `utils.js`.\n\n```js\nconst exportStatement = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => e.declaration?.declarations?.[0]?.id?.name === 'mintAddress');\nassert.exists(\n  exportStatement,\n  'The `mintAddress` export statement should be UNCOMMENTED'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/utils.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['importAssertions']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 15\n\n### --description--\n\nNow that you have created a _Mint Account_, you need to create a _Token Account_.\n\nA _Token Account_ is owned by another account, and holds tokens of a specific mint.\n\nCreate a new file named `create-token-account.js`.\n\n### --tests--\n\nYou can use `touch` to create a file named `create-token-account.js`.\n\n```js\nconst fileExists = await __helpers.fileExists(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-token-account.js'\n);\nassert.isTrue(fileExists);\n```\n\n## 16\n\n### --description--\n\nWithin `create-token-account.js`, declare a variable `connection`, and assign it a new instance of the `Connection` class. Pass in your local Solana RPC URL as the first argument.\n\n### --tests--\n\nYou should have `const connection = new Connection('http://localhost:8899');` in `create-token-account.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'A `connection` variable should be declared'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee?.name,\n  'Connection',\n  '`connection` should be initialised with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments?.[0]?.value,\n  'http://localhost:8899',\n  \"A new connection should be created with `new Connection('http://localhost:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\nconst connectionImportSpecifier = solanaWeb3ImportDeclaration.specifiers.find(\n  s => {\n    return s.imported.name === 'Connection';\n  }\n);\nassert.exists(\n  connectionImportSpecifier,\n  '`Connection` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\ntouch create-token-account.js\n```\n\n## 17\n\n### --description--\n\nWithin `create-token-account.js`, import `payer` and `mintAddress` from `utils.js`.\n\n### --tests--\n\nYou should import `payer` from `utils.js`.\n\n```js\nconst utilsImportDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(\n  utilsImportDeclaration,\n  'An import from `./utils.js` should exist'\n);\nconst payerImportSpecifiers = utilsImportDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  payerImportSpecifiers,\n  'payer',\n  '`payer` should be imported from `./utils.js`'\n);\n```\n\nYou should import `mintAddress` from `utils.js`.\n\n```js\nconst utilsImportDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(\n  utilsImportDeclaration,\n  'An import from `./utils.js` should exist'\n);\nconst mintAddressImportSpecifiers = utilsImportDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  mintAddressImportSpecifiers,\n  'mintAddress',\n  '`mintAddress` should be imported from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"create-token-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n```\n\n## 18\n\n### --description--\n\nImport the `getOrCreateAssociatedTokenAccount` function from `@solana/spl-token`, to retrieve an associated token account, or create it if it does not exist:\n\n```typescript\ngetOrCreateAssociatedTokenAccount(\n  connection: Connection,\n  payer: Signer, // Payer for this transaction\n  mint: PublicKey, // Address of the associated token mint\n  owner: PublicKey // Address of the account to own the token account\n): Promise<Account>\n```\n\nCall the `getOrCreateAssociatedTokenAccount` function, passing in logical arguments. Store the awaited result in a variable named `tokenAccount`.\n\nUse the `payer` account as the owner of the token account.\n\n### --tests--\n\nYou should have `const tokenAccount = await getOrCreateAssociatedTokenAccount(...);` in `create-token-account.js`.\n\n```js\nconst tokenAccountVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'tokenAccount');\nassert.exists(\n  tokenAccountVariableDeclaration,\n  'A `tokenAccount` variable should be declared'\n);\nconst awaitExpression = tokenAccountVariableDeclaration.declarations[0].init;\nassert.exists(\n  awaitExpression,\n  'The `tokenAccount` variable should be initialised with an `await` expression'\n);\nconst callExpression = awaitExpression.argument;\nassert.exists(\n  callExpression,\n  'The `tokenAccount` variable should be initialised with a call expression'\n);\nconst callee = callExpression.callee;\nassert.equal(\n  callee.name,\n  'getOrCreateAssociatedTokenAccount',\n  'The `tokenAccount` variable should be initialised with a call expression to `getOrCreateAssociatedTokenAccount`'\n);\n```\n\nYou should import `getOrCreateAssociatedTokenAccount` from `@solana/spl-token`.\n\n```js\nconst splTokenImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/spl-token';\n  });\nassert.exists(\n  splTokenImportDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst getOrCreateAssociatedTokenAccountImportSpecifiers =\n  splTokenImportDeclaration.specifiers.map(s => {\n    return s.imported.name;\n  });\nassert.include(\n  getOrCreateAssociatedTokenAccountImportSpecifiers,\n  'getOrCreateAssociatedTokenAccount',\n  '`getOrCreateAssociatedTokenAccount` should be imported from `@solana/spl-token`'\n);\n```\n\nYou should pass in order: `connection`, `payer`, `mintAddress`, and `payer.publicKey` as arguments to `getOrCreateAssociatedTokenAccount`.\n\n```js\nconst getOrCreateAssociatedTokenAccountCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => c.callee.name === 'getOrCreateAssociatedTokenAccount');\nassert.exists(\n  getOrCreateAssociatedTokenAccountCallExpression,\n  'A call expression to `getOrCreateAssociatedTokenAccount` should exist'\n);\nconst [arg1, arg2, arg3, arg4] =\n  getOrCreateAssociatedTokenAccountCallExpression.arguments;\nassert.equal(\n  arg1.name,\n  'connection',\n  'The first argument to `getOrCreateAssociatedTokenAccount` should be `connection`'\n);\nassert.equal(\n  arg2.name,\n  'payer',\n  'The second argument to `getOrCreateAssociatedTokenAccount` should be `payer`'\n);\nassert.equal(\n  arg3.name,\n  'mintAddress',\n  'The third argument to `getOrCreateAssociatedTokenAccount` should be `mintAddress`'\n);\nassert.nestedPropertyVal(arg4, 'object.name', 'payer');\nassert.nestedPropertyVal(arg4, 'property.name', 'publicKey');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"create-token-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n```\n\n## 19\n\n### --description--\n\nWithin `create-token-account.js`, log the base-58 representation of the `tokenAccount` address to the console.\n\n### --tests--\n\nYou should have `console.log(tokenAccount.address.toBase58());` in `create-token-account.js`.\n\n```js\nconst consoleLogCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return (\n      c.callee.object?.name === 'console' && c.callee.property?.name === 'log'\n    );\n  });\nassert.exists(consoleLogCallExpression, 'A `console.log` call should exist');\nconst consoleLogArguments = consoleLogCallExpression.arguments;\n// Assert one of the arguments is the `mint.toBase58()` call\nconst mintToBase58CallExpression = consoleLogArguments.find(a => {\n  return (\n    a.type === 'CallExpression' &&\n    a.callee?.object?.object?.name === 'tokenAccount' &&\n    a.callee?.object?.property?.name === 'address' &&\n    a.callee?.property?.name === 'toBase58'\n  );\n});\nassert.exists(\n  mintToBase58CallExpression,\n  'One of the arguments to `console.log` should be `tokenAccount.publicKey.toBase58()`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/create-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"create-token-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { payer, mintAddress } from './utils.js';\nimport { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst tokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  payer.publicKey\n);\n```\n\n## 20\n\n### --description--\n\nRun the `create-token-account.js` script:\n\n```bash\nnode create-token-account.js\n```\n\n### --tests--\n\nYou should run `node create-token-account.js` in a terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(\n  lastCommand,\n  'node create-token-account.js',\n  'Try running `node create-token-account.js` in a terminal'\n);\n```\n\nThe output of `node create-token-account.js` should include the base-58 representation of the Token Account.\n\n```js\nconst terminalOutput = await __helpers.getTerminalOutput();\nassert.match(terminalOutput, /[A-z0-9]{44}/);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --\"create-token-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst tokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  payer.publicKey\n);\n\nconsole.log('Token Account Address:', tokenAccount.publicKey.toBase58());\n```\n\n## 21\n\n### --description--\n\nThe output should include the base-58 representation of the Token Account. In other words, the token account's public key address.\n\nCopy this address, and paste it into the `TOKEN_ACCOUNT_58` variable in `utils.js`. Then, uncomment the `tokenAccount` variable.\n\n### --tests--\n\nYou should have `const TOKEN_ACCOUNT_58 = '...';` in `utils.js`.\n\n```js\n// TODO: Note that seed cannot add this...\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'TOKEN_ACCOUNT_58');\nassert.exists(\n  variableDeclaration,\n  'The `TOKEN_ACCOUNT_58` variable is should exist'\n);\nconst value = variableDeclaration.declarations?.[0]?.init?.value;\nassert.isString(value, 'The `TOKEN_ACCOUNT_58` value should be a string');\nassert.isNotEmpty(value, 'The `TOKEN_ACCOUNT_58` value should not be empty');\n```\n\nYou should uncomment `export const tokenAccount = new PublicKey(TOKEN_ACCOUNT_58);` in `utils.js`.\n\n```js\nconst exportStatement = babelisedCode\n  .getType('ExportNamedDeclaration')\n  .find(e => e.declaration?.declarations?.[0]?.id?.name === 'tokenAccount');\nassert.exists(\n  exportStatement,\n  'The `tokenAccount` export statement should be UNCOMMENTED'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/utils.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString, {\n  plugins: ['importAssertions']\n});\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 22\n\n### --description--\n\nRun the `create-token-account.js` script again:\n\n```bash\nnode create-token-account.js\n```\n\n### --tests--\n\nYou should run `node create-token-account.js` in a terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(\n  lastCommand,\n  'node create-token-account.js',\n  'Try running `node create-token-account.js` in a terminal'\n);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 23\n\n### --description--\n\nSeeing as there is already an associated token account for the `payer` account, the `getOrCreateAssociatedTokenAccount` function will return the existing token account.\n\nTo see what a token account looks like, create a new file called `get-token-account.js`:\n\n### --tests--\n\nYou should have a `get-token-account.js` file.\n\n```js\nconst fileExists = await __helpers.fileExists(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-account.js'\n);\nassert.isTrue(fileExists);\n```\n\n## 24\n\n### --description--\n\nWithin `get-token-account.js`, declare a variable `connection`, and assign it a new instance of the `Connection` class. Pass in your local Solana RPC URL as the first argument.\n\n### --tests--\n\nYou should have `const connection = new Connection('http://localhost:8899');` in `get-token-account.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'A `connection` variable should be declared'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee.name,\n  'Connection',\n  '`connection` should be initialised with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments[0].value,\n  'http://localhost:8899',\n  \"A new connection should be created with `new Connection('http://localhost:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\nconst connectionImportSpecifier = solanaWeb3ImportDeclaration.specifiers.find(\n  s => {\n    return s.imported.name === 'Connection';\n  }\n);\nassert.exists(\n  connectionImportSpecifier,\n  '`Connection` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\ntouch get-token-account.js\n```\n\n## 25\n\n### --description--\n\nWithin `get-token-account.js`, create a new `PublicKey` from the first command-line argument, and assign it to a variable called `userPublicKey`.\n\n### --tests--\n\nYou should have `const userPublicKey = new PublicKey(process.argv[2]);` in `get-token-account.js`.\n\n```js\nconst userPublicKeyVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'userPublicKey');\nassert.exists(\n  userPublicKeyVariableDeclaration,\n  'A `userPublicKey` variable should be declared'\n);\nconst newExpression = userPublicKeyVariableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  newExpression?.callee?.name,\n  'PublicKey',\n  '`userPublicKey` should be initialised with a new `PublicKey`'\n);\nconst processArgvMemberExpression = newExpression?.arguments?.[0];\nassert.equal(\n  processArgvMemberExpression?.object?.object?.name,\n  'process',\n  '`PublicKey` should be initialised with `process...`'\n);\nassert.equal(\n  processArgvMemberExpression?.object?.property?.name,\n  'argv',\n  '`PublicKey` should be initialised with `process.argv...`'\n);\nassert.equal(\n  processArgvMemberExpression?.property?.value,\n  '2',\n  '`PublicKey` should be initialised with `process.argv[2]`'\n);\n```\n\nYou should import `PublicKey` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\nconst specifiers = solanaWeb3ImportDeclaration.specifiers.map(\n  s => s.imported.name\n);\nassert.include(\n  specifiers,\n  'PublicKey',\n  '`PublicKey` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"get-token-account.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n```\n\n## 26\n\n### --description--\n\nImport the `getAssociatedTokenAddress` function from `@solana/spl-token`, to get the address of the associated token account for a given mint and owner:\n\n```typescript\ngetAssociatedTokenAddress(\n  mint: PublicKey,\n  owner: PublicKey\n): Promise<PublicKey>;\n```\n\nCall the `getAssociatedTokenAddress` function, passing in the `mintAddress` (from `utils.js`) and `userPublicKey` variables as arguments. Assign the result to a variable called `tokenAddress`.\n\n### --tests--\n\nYou should have `const tokenAddress = await getAssociatedTokenAddress(mintAddress, userPublicKey);` in `get-token-account.js`.\n\n```js\nconst tokenAddressVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'tokenAddress');\nassert.exists(\n  tokenAddressVariableDeclaration,\n  'A `tokenAddress` variable should be declared'\n);\nconst awaitExpression = tokenAddressVariableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  awaitExpression?.type,\n  'AwaitExpression',\n  '`tokenAddress` should be initialised with an `await` expression'\n);\nconst getAssociatedTokenAddressCallExpression = awaitExpression?.argument;\nassert.equal(\n  getAssociatedTokenAddressCallExpression?.callee?.name,\n  'getAssociatedTokenAddress',\n  '`tokenAddress` should be initialised with `getAssociatedTokenAddress(...)`'\n);\nconst [mintAddressIdentifier, userPublicKeyIdentifier] =\n  getAssociatedTokenAddressCallExpression?.arguments;\nassert.equal(\n  mintAddressIdentifier?.name,\n  'mintAddress',\n  '`getAssociatedTokenAddress` should be called with `mintAddress` as the first argument'\n);\nassert.equal(\n  userPublicKeyIdentifier?.name,\n  'userPublicKey',\n  '`getAssociatedTokenAddress` should be called with `userPublicKey` as the second argument'\n);\n```\n\nYou should import `getAssociatedTokenAddress` from `@solana/spl-token`.\n\n```js\nconst splTokenImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/spl-token';\n  });\nassert.exists(\n  splTokenImportDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst specifiers = splTokenImportDeclaration.specifiers.map(\n  s => s.imported.name\n);\nassert.include(\n  specifiers,\n  'getAssociatedTokenAddress',\n  '`getAssociatedTokenAddress` should be imported from `@solana/spl-token`'\n);\n```\n\nYou should import `mintAddress` from `./utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'An import from `./utils.js` should exist');\nconst specifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  specifiers,\n  'mintAddress',\n  '`mintAddress` should be imported from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"get-token-account.js\"--\n\n```js\nimport { Connection, PublicKey } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst tokenAccountPublicKey = new PublicKey(process.argv[2]);\n```\n\n## 27\n\n### --description--\n\nImport the `getAccount` function from `@solana/spl-token`, to get the account information for a given token account:\n\n```typescript\ngetAccount(\n  connection: Connection,\n  address: PublicKey\n): Promise<Account>;\n```\n\nCall the `getAccount` function, passing in the `connection` and `tokenAddress` variables as arguments. Assign the result to a variable called `tokenAccount`.\n\n### --tests--\n\nYou should have `const tokenAccount = await getAccount(connection, tokenAddress);` in `get-token-account.js`.\n\n```js\nconst tokenAccountVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'tokenAccount');\nassert.exists(\n  tokenAccountVariableDeclaration,\n  'A `tokenAccount` variable should be declared'\n);\nconst awaitExpression = tokenAccountVariableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  awaitExpression?.type,\n  'AwaitExpression',\n  '`tokenAccount` should be initialised with an `await` expression'\n);\nconst getAccountCallExpression = awaitExpression?.argument;\nassert.equal(\n  getAccountCallExpression?.callee?.name,\n  'getAccount',\n  '`tokenAccount` should be initialised with `getAccount(...)`'\n);\nconst [connectionIdentifier, tokenAddressIdentifier] =\n  getAccountCallExpression?.arguments;\nassert.equal(\n  connectionIdentifier?.name,\n  'connection',\n  '`getAccount` should be called with `connection` as the first argument'\n);\nassert.equal(\n  tokenAddressIdentifier?.name,\n  'tokenAddress',\n  '`getAccount` should be called with `tokenAddress` as the second argument'\n);\n```\n\nYou should import `getAccount` from `@solana/spl-token`.\n\n```js\nconst splTokenImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/spl-token';\n  });\nassert.exists(\n  splTokenImportDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst specifiers = splTokenImportDeclaration.specifiers.map(\n  s => s.imported.name\n);\nassert.include(\n  specifiers,\n  'getAccount',\n  '`getAccount` should be imported from `@solana/spl-token`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"get-token-account.js\"--\n\n```js\nimport { Connection, PublicKey } from '@solana/web3.js';\nimport { getAssociatedTokenAddress } from '@solana/spl-token';\nimport { mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst userPublicKey = new PublicKey(process.argv[2]);\n\nconst tokenAddress = await getAssociatedTokenAddress(\n  mintAddress,\n  userPublicKey\n);\n```\n\n## 28\n\n### --description--\n\nPrint the `tokenAccount` variable to the console.\n\n### --tests--\n\nYou should have `console.log(tokenAccount);` in `get-token-account.js`.\n\n```js\nconst consoleLogCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return (\n      c.callee.object?.name === 'console' && c.callee.property?.name === 'log'\n    );\n  });\nassert.exists(consoleLogCallExpression, 'A `console.log` call should exist');\nconst consoleLogArguments = consoleLogCallExpression.arguments;\n// Assert one of the arguments is `tokenAccount`\nconst tokenAccountIdent = consoleLogArguments.find(a => {\n  return a.name === 'tokenAccount';\n});\nassert.exists(\n  tokenAccountIdent,\n  'One of the arguments to `console.log` should be `tokenAccount`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"get-token-account.js\"--\n\n```js\nimport { Connection, PublicKey } from '@solana/web3.js';\nimport { getAssociatedTokenAddress, getAccount } from '@solana/spl-token';\nimport { mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst userPublicKey = new PublicKey(process.argv[2]);\n\nconst tokenAddress = await getAssociatedTokenAddress(\n  mintAddress,\n  userPublicKey\n);\n\nconst tokenAccount = await getAccount(connection, tokenAddress);\n```\n\n## 29\n\n### --description--\n\nRun the `get-token-account.js` script, passing in the public key of the `payer` account as the first argument:\n\n```bash\nsolana address --keypair ./wallet.json # to get the public key\nnode get-token-account.js <public_key>\n```\n\n### --tests--\n\nYou should run `node get-token-account.js <public_key>` in the terminal.\n\n```js\nconst wallet = JSON.parse(\n  await __helpers.getFile(\n    './learn-solanas-token-program-by-minting-a-fungible-token/wallet.json'\n  )\n);\nconst secretKey = Uint8Array.from(wallet);\nconst { Keypair } = await import('@solana/web3.js');\nconst payer = Keypair.fromSecretKey(secretKey);\n\nconst command = `node get-token-account.js ${payer.publicKey.toString()}`;\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, command, `The last command should be '${command}'`);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --\"get-token-account.js\"--\n\n```js\nimport { Connection, PublicKey } from '@solana/web3.js';\nimport { getAssociatedTokenAddress, getAccount } from '@solana/spl-token';\nimport { mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst userPublicKey = new PublicKey(process.argv[2]);\n\nconst tokenAddress = await getAssociatedTokenAddress(\n  mintAddress,\n  userPublicKey\n);\n\nconst tokenAccount = await getAccount(connection, tokenAddress);\n\nconsole.log('Token Account:', tokenAccount);\n```\n\n## 30\n\n### --description--\n\nThe `tokenAccount` variable should have an `amount` property, which is the number of tokens held by the account.\n\nCurrently, the token account has no tokens, because none have been minted into the account.\n\nCreate a file named `mint.js`.\n\n### --tests--\n\nYou should have a file named `mint.js`.\n\n```js\nconst mintFileExists = await __helpers.fileExists(\n  'learn-solanas-token-program-by-minting-a-fungible-token/mint.js'\n);\nassert.isTrue(mintFileExists, 'A file named `mint.js` should exist');\n```\n\n## 31\n\n### --description--\n\nWithin `mint.js`, declare a variable `connection`, and assign it a new instance of the `Connection` class. Pass in your local Solana RPC URL as the first argument.\n\n### --tests--\n\nYou should have `const connection = new Connection('http://localhost:8899');` in `mint.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'A `connection` variable should be declared'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee.name,\n  'Connection',\n  '`connection` should be initialised with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments[0].value,\n  'http://localhost:8899',\n  \"A new connection should be created with `new Connection('http://localhost:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\nconst connectionImportSpecifier = solanaWeb3ImportDeclaration.specifiers.find(\n  s => {\n    return s.imported.name === 'Connection';\n  }\n);\nassert.exists(\n  connectionImportSpecifier,\n  '`Connection` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/mint.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\ntouch mint.js\n```\n\n## 32\n\n### --description--\n\nImport the `mintTo` function from `@solana/spl-token`, to mint tokens into a token account:\n\n```typescript\nmintTo(\n  connection: Connection,\n  payer: Signer,\n  mint: PublicKey,\n  destination: PublicKey,\n  authority: PublicKey | Signer,\n  amount: number | bigint\n): Promise\n```\n\nCall and await the `mintTo` function, passing in the `connection`, `payer`, `mintAddress`, `tokenAccount`, `mintAuthority`, and `1_000_000_000` variables as arguments.\n\n### --tests--\n\nYou should have `await mintTo(connection, payer, mintAddress, tokenAccount, mintAuthority, 1_000_000_000);` in `mint.js`.\n\n```js\nconst expressionStatement = babelisedCode\n  .getExpressionStatements()\n  .find(\n    e =>\n      e.expression.type === 'AwaitExpression' &&\n      e.expression.argument?.callee?.name === 'mintTo'\n  );\nassert.exists(\n  expressionStatement,\n  'An `await mintTo(...)` expression should exist'\n);\nconst mintToArguments = expressionStatement.expression.argument.arguments;\nconst [\n  connectionArgument,\n  payerArgument,\n  mintAddressArgument,\n  tokenAccountArgument,\n  mintAuthorityArgument,\n  amountArgument\n] = mintToArguments;\nassert.equal(\n  connectionArgument?.name,\n  'connection',\n  'The first argument to `mintTo` should be `connection`'\n);\nassert.equal(\n  payerArgument?.name,\n  'payer',\n  'The second argument to `mintTo` should be `payer`'\n);\nassert.equal(\n  mintAddressArgument?.name,\n  'mintAddress',\n  'The third argument to `mintTo` should be `mintAddress`'\n);\nassert.equal(\n  tokenAccountArgument?.name,\n  'tokenAccount',\n  'The fourth argument to `mintTo` should be `tokenAccount`'\n);\nassert.equal(\n  mintAuthorityArgument?.name,\n  'mintAuthority',\n  'The fifth argument to `mintTo` should be `mintAuthority`'\n);\nassert.equal(\n  amountArgument?.value,\n  1_000_000_000,\n  'The sixth argument to `mintTo` should be `1_000_000_000`'\n);\n```\n\nYou should import `mintTo` from `@solana/spl-token`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/spl-token';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'mintTo',\n  '`mintTo` should be imported from `@solana/spl-token`'\n);\n```\n\nYou should import `payer` from `./utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'An import from `./utils.js` should exist');\nconst specifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  specifiers,\n  'payer',\n  '`payer` should be imported from `./utils.js`'\n);\n```\n\nYou should import `mintAddress` from `./utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'An import from `./utils.js` should exist');\nconst specifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  specifiers,\n  'mintAddress',\n  '`mintAddress` should be imported from `./utils.js`'\n);\n```\n\nYou should import `tokenAccount` from `./utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'An import from `./utils.js` should exist');\nconst specifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  specifiers,\n  'tokenAccount',\n  '`tokenAccount` should be imported from `./utils.js`'\n);\n```\n\nYou should import `mintAuthority` from `./utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'An import from `./utils.js` should exist');\nconst specifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  specifiers,\n  'mintAuthority',\n  '`mintAuthority` should be imported from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/mint.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"mint.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n```\n\n## 33\n\n### --description--\n\nRun the `mint.js` script:\n\n```bash\nnode mint.js\n```\n\n### --tests--\n\nYou should run `node mint.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(\n  lastCommand,\n  'node mint.js',\n  'Try running `node mint.js` in the terminal'\n);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --\"mint.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { mintTo } from '@solana/spl-token';\nimport { payer, mintAddress, tokenAccount, mintAuthority } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nawait mintTo(\n  connection,\n  payer,\n  mintAddress,\n  tokenAccount,\n  mintAuthority,\n  1_000_000_000\n);\n```\n\n## 34\n\n### --description--\n\nRun the `get-token-account.js` script again, passing in the public key of the `payer` account as the first argument.\n\n### --tests--\n\nYou should run `node get-token-account.js <public_key>` in the terminal.\n\n```js\nconst wallet = JSON.parse(\n  await __helpers.getFile(\n    './learn-solanas-token-program-by-minting-a-fungible-token/wallet.json'\n  )\n);\nconst secretKey = Uint8Array.from(wallet);\nconst { Keypair } = await import('@solana/web3.js');\nconst payer = Keypair.fromSecretKey(secretKey);\n\nconst command = `node get-token-account.js ${payer.publicKey.toString()}`;\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, command, `The last command should be '${command}'`);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 35\n\n### --description--\n\nThe token account should have an `amount` of `1000000000n`. Note that this means the account has a total of 1 token, because the token has 9 decimals - the same way an account with `1_000_000_000 lamports` means it has `1 SOL`.\n\nTo see the total number of tokens minted, create a file named `get-token-info.js`.\n\n### --tests--\n\nYou should have a file named `get-token-info.js`.\n\n```js\nconst fileExists = await __helpers.fileExists(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-info.js'\n);\nassert.isTrue(fileExists, 'A file named `get-token-info.js` should exist');\n```\n\n## 36\n\n### --description--\n\nWithin `get-token-info.js`, declare a variable `connection`, and assign it a new instance of the `Connection` class. Pass in your local Solana RPC URL as the first argument.\n\n### --tests--\n\nYou should have `const connection = new Connection('http://localhost:8899');` in `get-token-info.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'A `connection` variable should be declared'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee.name,\n  'Connection',\n  '`connection` should be initialised with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments[0].value,\n  'http://localhost:8899',\n  \"A new connection should be created with `new Connection('http://localhost:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\nconst connectionImportSpecifier = solanaWeb3ImportDeclaration.specifiers.find(\n  s => {\n    return s.imported.name === 'Connection';\n  }\n);\nassert.exists(\n  connectionImportSpecifier,\n  '`Connection` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-info.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\ntouch get-token-info.js\n```\n\n## 37\n\n### --description--\n\nImport the `getMint` function from `@solana/spl-token`, to get the token information:\n\n```typescript\ngetMint(\n  connection: Connection,\n  address: PublicKey\n): Promise<Mint>\n```\n\nCall the `getMint` function, passing in the `connection` and `mintAddress` variables as arguments, and assign the awaited result to a variable `mint`.\n\n### --tests--\n\nYou should have `const mint = await getMint(connection, mintAddress);` in `get-token-info.js`.\n\n```js\nconst mintVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'mint');\nassert.exists(mintVariableDeclaration, 'A `mint` variable should be declared');\nconst awaitExpression = mintVariableDeclaration.declarations[0].init;\nassert.equal(\n  awaitExpression.type,\n  'AwaitExpression',\n  '`mint` should be initialised with an await expression'\n);\nconst callExpression = awaitExpression.argument;\nassert.equal(\n  callExpression.callee.name,\n  'getMint',\n  '`mint` should be initialised with a call to `getMint`'\n);\nassert.equal(\n  callExpression.arguments[0].name,\n  'connection',\n  '`getMint` should be called with `connection` as the first argument'\n);\nassert.equal(\n  callExpression.arguments[1].name,\n  'mintAddress',\n  '`getMint` should be called with `mintAddress` as the second argument'\n);\n```\n\nYou should import `getMint` from `@solana/spl-token`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/spl-token';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'getMint',\n  '`getMint` should be imported from `@solana/spl-token`'\n);\n```\n\nYou should import `mintAddress` from `./utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'An import from `./utils.js` should exist');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'mintAddress',\n  '`mintAddress` should be imported from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-info.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"get-token-info.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n```\n\n## 38\n\n### --description--\n\nLog the `mint` variable to the console.\n\n### --tests--\n\nYou should have `console.log(mint);` in `get-token-info.js`.\n\n```js\nconst consoleLogCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return (\n      c.callee.object?.name === 'console' && c.callee.property?.name === 'log'\n    );\n  });\nassert.exists(consoleLogCallExpression, 'A `console.log` call should exist');\nconst consoleLogArguments = consoleLogCallExpression.arguments;\n// Assert one of the arguments is `mint`\nconst mintIdent = consoleLogArguments.find(a => {\n  return a.name === 'mint';\n});\nassert.exists(\n  mintIdent,\n  'One of the arguments to `console.log` should be `mint`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/get-token-info.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"get-token-info.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { getMint } from '@solana/spl-token';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst mint = await getMint(connection, mintAddress);\n```\n\n## 39\n\n### --description--\n\nRun the `get-token-info.js` script:\n\n```bash\nnode get-token-info.js\n```\n\n### --tests--\n\nYou should run `node get-token-info.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(\n  lastCommand,\n  'node get-token-info.js',\n  'Try running `node get-token-info.js` in the terminal'\n);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --\"get-token-info.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\nimport { getMint } from '@solana/spl-token';\nimport { mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst mint = await getMint(connection, mintAddress);\n\nconsole.log(mint);\n```\n\n## 40\n\n### --description--\n\nThe output should show a `supply` property of `1000000000n`, which means the total number of tokens minted is `1_000_000_000 / 9 = 1`.\n\nMint at least 2 more tokens to the `payer` account.\n\n### --tests--\n\nYou should run `node mint.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand,\n  'node mint.js',\n  'Try running `node mint.js` in the terminal'\n);\n```\n\nThe `payer` account should have a balance of at least `3_000_000_000`.\n\n```js\nconst wallet = JSON.parse(\n  await __helpers.getFile(\n    './learn-solanas-token-program-by-minting-a-fungible-token/wallet.json'\n  )\n);\nconst secretKey = Uint8Array.from(wallet);\nconst { Keypair } = await import('@solana/web3.js');\nconst payer = Keypair.fromSecretKey(secretKey);\n\nconst { Connection } = await import('@solana/web3.js');\nconst { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');\n\nconst connection = new Connection('http://localhost:8899');\nconst tokenAccounts = await connection.getParsedTokenAccountsByOwner(\n  payer.publicKey,\n  {\n    programId: TOKEN_PROGRAM_ID\n  }\n);\n\nconst { tokenAmount } = tokenAccounts?.value?.[0]?.account?.data?.parsed?.info;\nassert.isAtLeast(tokenAmount?.uiAmount, 3);\n```\n\nThe total supply of tokens should be at least `3_000_000_000`.\n\n```js\nconst { mintAddress } = await __helpers.importSansCache(\n  './learn-solanas-token-program-by-minting-a-fungible-token/utils.js'\n);\nconst { getMint } = await import('@solana/spl-token');\nconst { Connection } = await import('@solana/web3.js');\nconst connection = new Connection('http://localhost:8899');\n\nconst mint = await getMint(connection, mintAddress);\nassert.isAtLeast(Number(mint.supply), 3_000_000_000);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 41\n\n### --description--\n\nNow that your token has been minted and is in circulation, you can transfer tokens from one account to another.\n\nCreate a file named `transfer.js`.\n\n### --tests--\n\nYou should have a file named `transfer.js`.\n\n```js\nconst transferFileExists = __helpers.fileExists(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nassert.isTrue(transferFileExists, 'A `transfer.js` file should exist');\n```\n\n## 42\n\n### --description--\n\nWithin `transfer.js`, declare a variable `connection`, and assign it a new instance of the `Connection` class. Pass in your local Solana RPC URL as the first argument.\n\n### --tests--\n\nYou should have `const connection = new Connection('http://localhost:8899');` in `transfer.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'A `connection` variable should be declared'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee.name,\n  'Connection',\n  '`connection` should be initialised with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments[0].value,\n  'http://localhost:8899',\n  \"A new connection should be created with `new Connection('http://localhost:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\nconst connectionImportSpecifier = solanaWeb3ImportDeclaration.specifiers.find(\n  s => {\n    return s.imported.name === 'Connection';\n  }\n);\nassert.exists(\n  connectionImportSpecifier,\n  '`Connection` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --cmd--\n\n```bash\ntouch transfer.js\n```\n\n## 43\n\n### --description--\n\nFrom the first command-line argument, create a new `PublicKey` instance, and assign it to a variable `fromTokenAccountPublicKey`.\n\n### --tests--\n\nYou should have `const fromTokenAccountPublicKey = new PublicKey(process.argv[2]);` in `transfer.js`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'fromTokenAccountPublicKey');\nassert.exists(\n  variableDeclaration,\n  'A `fromTokenAccountPublicKey` variable should be declared'\n);\nconst newExpression = variableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  newExpression?.callee?.name,\n  'PublicKey',\n  '`fromTokenAccountPublicKey` should be initialised with a new `PublicKey`'\n);\nconst processArgvMemberExpression = newExpression?.arguments?.[0];\nassert.equal(\n  processArgvMemberExpression?.object?.object?.name,\n  'process',\n  '`PublicKey` should be initialised with `process...`'\n);\nassert.equal(\n  processArgvMemberExpression?.object?.property?.name,\n  'argv',\n  '`PublicKey` should be initialised with `process.argv...`'\n);\nassert.equal(\n  processArgvMemberExpression?.property?.value,\n  '2',\n  '`PublicKey` should be initialised with `process.argv[2]`'\n);\n```\n\nYou should import `PublicKey` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\nconst importSpecifiers = solanaWeb3ImportDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'PublicKey',\n  '`PublicKey` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n```\n\n## 44\n\n### --description--\n\nGenerate a new keypair, and assign it to a variable `toWallet`.\n\n### --tests--\n\nYou should have `const toWallet = Keypair.generate();` in `transfer.js`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'toWallet');\nassert.exists(variableDeclaration, 'A `toWallet` variable should be declared');\nconst generateMemberExpression =\n  variableDeclaration.declarations?.[0]?.init?.callee;\nassert.equal(\n  generateMemberExpression?.object?.name,\n  'Keypair',\n  '`toWallet` should be initialised with `Keypair.generate()`'\n);\nassert.equal(\n  generateMemberExpression?.property?.name,\n  'generate',\n  '`toWallet` should be initialised with `Keypair.generate()`'\n);\n```\n\nYou should import `Keypair` from `@solana/web3.js`.\n\n```js\nconst solanaWeb3ImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/web3.js';\n  });\nassert.exists(\n  solanaWeb3ImportDeclaration,\n  'An import from `@solana/web3.js` should exist'\n);\nconst importSpecifiers = solanaWeb3ImportDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'Keypair',\n  '`Keypair` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection, PublicKey } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n```\n\n## 45\n\n### --description--\n\nImport the `getOrCreateAssociatedTokenAccount` function from `@solana/spl-token`, to get (create) the token account of the `toWallet` address:\n\n```typescript\ngetOrCreateAssociatedTokenAccount(\n  connection: Connection,\n  payer: Signer,\n  mint: PublicKey,\n  owner: PublicKey\n): Promise<Account>\n```\n\nCall the function, passing in order: `connection`, `payer`, `mintAddress`, and the public key of `toWallet` as arguments. Assign the awaited result to a variable `toTokenAccount`.\n\n### --tests--\n\nYou should have `const toTokenAccount = await getOrCreateAssociatedTokenAccount(connection, payer, mintAddress, toWallet.publicKey);` in `transfer.js`.\n\n```js\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'toTokenAccount');\nassert.exists(\n  variableDeclaration,\n  'A `toTokenAccount` variable should be declared'\n);\nconst awaitExpression = variableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  awaitExpression?.type,\n  'AwaitExpression',\n  '`toTokenAccount` should be initialised with an `await` expression'\n);\nconst callExpression = awaitExpression?.argument;\nassert.equal(\n  callExpression?.callee?.name,\n  'getOrCreateAssociatedTokenAccount',\n  '`toTokenAccount` should be initialised with `getOrCreateAssociatedTokenAccount()`'\n);\nconst [\n  connectionArgument,\n  payerArgument,\n  mintAddressArgument,\n  toWalletPublicKeyArgument\n] = callExpression?.arguments;\nassert.equal(\n  connectionArgument?.name,\n  'connection',\n  'The first argument of `getOrCreateAssociatedTokenAccount()` should be `connection`'\n);\nassert.equal(\n  payerArgument?.name,\n  'payer',\n  'The second argument of `getOrCreateAssociatedTokenAccount()` should be `payer`'\n);\nassert.equal(\n  mintAddressArgument?.name,\n  'mintAddress',\n  'The third argument of `getOrCreateAssociatedTokenAccount()` should be `mintAddress`'\n);\nassert.equal(\n  toWalletPublicKeyArgument?.object?.name,\n  'toWallet',\n  'The fourth argument of `getOrCreateAssociatedTokenAccount()` should be `toWallet.publicKey`'\n);\nassert.equal(\n  toWalletPublicKeyArgument?.property?.name,\n  'publicKey',\n  'The fourth argument of `getOrCreateAssociatedTokenAccount()` should be `toWallet.publicKey`'\n);\n```\n\nYou should import `getOrCreateAssociatedTokenAccount` from `@solana/spl-token`.\n\n```js\nconst splTokenImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/spl-token';\n  });\nassert.exists(\n  splTokenImportDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst importSpecifiers = splTokenImportDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'getOrCreateAssociatedTokenAccount',\n  '`getOrCreateAssociatedTokenAccount` should be imported from `@solana/spl-token`'\n);\n```\n\nYou should import `payer` from `./utils.js`.\n\n```js\nconst utilsImportDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(\n  utilsImportDeclaration,\n  'An import from `./utils.js` should exist'\n);\nconst importSpecifiers = utilsImportDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'payer',\n  '`payer` should be imported from `./utils.js`'\n);\n```\n\nYou should import `mintAddress` from `./utils.js`.\n\n```js\nconst utilsImportDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(\n  utilsImportDeclaration,\n  'An import from `./utils.js` should exist'\n);\nconst importSpecifiers = utilsImportDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'mintAddress',\n  '`mintAddress` should be imported from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection, PublicKey, Keypair } from '@solana/web3.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n\nconst toWallet = Keypair.generate();\n```\n\n## 46\n\n### --description--\n\nImport the `getAccount` function from `@solana/spl-token`, to get the owner of the `fromTokenAccountPublicKey` account:\n\n```typescript\ngetAccount(\n  connection: Connection,\n  address: PublicKey\n): Promise<Account>\n```\n\nCall the function, passing in order: `connection`, and `fromTokenAccountPublicKey` as arguments. Assign the awaited result to a variable `fromWallet`.\n\n### --tests--\n\nYou should have `const fromWallet = await getAccount(connection, fromTokenAccountPublicKey);` in `transfer.js`.\n\n```js\nconst variableDeclarations = babelisedCode.getVariableDeclarations();\nconst fromWalletVariableDeclaration = variableDeclarations.find(v => {\n  return v.declarations?.[0]?.id?.name === 'fromWallet';\n});\nassert.exists(fromWalletVariableDeclaration, '`fromWallet` should be declared');\nconst awaitExpression = fromWalletVariableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  awaitExpression?.type,\n  'AwaitExpression',\n  '`fromWallet` should be initialised with `await`'\n);\nconst callExpression = awaitExpression?.argument;\nassert.equal(\n  callExpression?.callee?.name,\n  'getAccount',\n  '`fromWallet` should be initialised with `await getAccount()`'\n);\nconst [connectionArgument, fromTokenAccountPublicKeyArgument] =\n  callExpression?.arguments;\nassert.equal(\n  connectionArgument?.name,\n  'connection',\n  'The first argument of `getAccount()` should be `connection`'\n);\nassert.equal(\n  fromTokenAccountPublicKeyArgument?.name,\n  'fromTokenAccountPublicKey',\n  'The second argument of `getAccount()` should be `fromTokenAccountPublicKey`'\n);\n```\n\nYou should import `getAccount` from `@solana/spl-token`.\n\n```js\nconst splTokenImportDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => {\n    return i.source.value === '@solana/spl-token';\n  });\nassert.exists(\n  splTokenImportDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst importSpecifiers = splTokenImportDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'getAccount',\n  '`getAccount` should be imported from `@solana/spl-token`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n\nconst toWallet = Keypair.generate();\n\nconst toTokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  toWallet.publicKey\n);\n```\n\n## 47\n\n### --description--\n\nAssign the `owner` property of `fromWallet` to a variable `owner`.\n\n### --tests--\n\nYou should have `const owner = fromWallet.owner;` in `transfer.js`.\n\n```js\nconst variableDeclarations = babelisedCode.getVariableDeclarations();\nconst ownerVariableDeclaration = variableDeclarations.find(v => {\n  return v.declarations?.[0]?.id?.name === 'owner';\n});\nassert.exists(ownerVariableDeclaration, '`owner` should be declared');\nconst memberExpression = ownerVariableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  memberExpression?.object?.name,\n  'fromWallet',\n  '`owner` should be initialised with `fromWallet`'\n);\nassert.equal(\n  memberExpression?.property?.name,\n  'owner',\n  '`owner` should be initialised with `fromWallet.owner`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport {\n  getOrCreateAssociatedTokenAccount,\n  getAccount\n} from '@solana/spl-token';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n\nconst toWallet = Keypair.generate();\n\nconst toTokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  toWallet.publicKey\n);\n\nconst fromWallet = await getAccount(connection, fromTokenAccountPublicKey);\n```\n\n## 48\n\n### --description--\n\nCast the second command-line argument to a number, and assign it to a variable `amount`. This will be used as the amount of tokens to transfer.\n\n### --tests--\n\nYou should have `const amount = Number(process.argv[3]);` in `transfer.js`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'amount';\n});\nassert.exists(variableDeclaration, '`amount` should be declared');\nconst callExpression = variableDeclaration.declarations?.[0]?.init;\nassert.equal(\n  callExpression?.callee?.name,\n  'Number',\n  '`amount` should be initialised with `Number()`'\n);\nconst processArgvArgument = callExpression?.arguments?.[0];\nassert.equal(\n  processArgvArgument?.object?.object?.name,\n  'process',\n  '`amount` should be initialised with `Number(process.argv[3])`'\n);\nassert.equal(\n  processArgvArgument?.object?.property?.name,\n  'argv',\n  '`amount` should be initialised with `Number(process.argv[3])`'\n);\nassert.equal(\n  processArgvArgument?.property?.value,\n  3,\n  '`amount` should be initialised with `Number(process.argv[3])`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport {\n  getOrCreateAssociatedTokenAccount,\n  getAccount\n} from '@solana/spl-token';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n\nconst toWallet = Keypair.generate();\n\nconst toTokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  toWallet.publicKey\n);\n\nconst fromWallet = await getAccount(connection, fromTokenAccountPublicKey);\n\nconst owner = fromWallet.owner;\n```\n\n## 49\n\n### --description--\n\nImport the `transfer` function from `@solana/spl-token`, to transfer tokens from the `fromTokenAccountPublicKey` account to the `toTokenAccount` account:\n\n```typescript\ntransfer(\n  connection: Connection,\n  payer: Signer,\n  source: PublicKey,\n  destination: PublicKey,\n  owner: PublicKey | Signer,\n  amount: number | bigint\n): Promise\n```\n\nCall the function, passing in order: `connection`, `payer`, `fromTokenAccountPublicKey`, the `address` of `toTokenAccount`, `owner`, and `amount` as arguments.\n\n### --tests--\n\nYou should have `await transfer(connection, payer, fromTokenAccountPublicKey, toTokenAccount.address, owner, amount);` in `transfer.js`.\n\n```js\nconst expressionStatement = babelisedCode\n  .getExpressionStatements()\n  .find(\n    e =>\n      e.expression.type === 'AwaitExpression' &&\n      e.expression.argument.callee.name === 'transfer'\n  );\nassert.exists(\n  expressionStatement,\n  'An `await transfer(...)` expression should exist'\n);\nconst transferArgs = expressionStatement.expression.argument.arguments;\nconst [\n  connectionArgument,\n  payerArgument,\n  fromTokenAccountPublicKeyArgument,\n  toTokenAccountMemberExpression,\n  ownerArgument,\n  amountArgument\n] = transferArgs;\n\nassert.equal(\n  connectionArgument.name,\n  'connection',\n  'The first argument to `transfer` should be `connection`'\n);\nassert.equal(\n  payerArgument.name,\n  'payer',\n  'The second argument to `transfer` should be `payer`'\n);\nassert.equal(\n  fromTokenAccountPublicKeyArgument.name,\n  'fromTokenAccountPublicKey',\n  'The third argument to `transfer` should be `fromTokenAccountPublicKey`'\n);\nassert.equal(\n  toTokenAccountMemberExpression.object.name,\n  'toTokenAccount',\n  'The fourth argument to `transfer` should be `toTokenAccount.address`'\n);\nassert.equal(\n  toTokenAccountMemberExpression.property.name,\n  'address',\n  'The fourth argument to `transfer` should be `toTokenAccount.address`'\n);\nassert.equal(\n  ownerArgument.name,\n  'owner',\n  'The fifth argument to `transfer` should be `owner`'\n);\nassert.equal(\n  amountArgument.name,\n  'amount',\n  'The sixth argument to `transfer` should be `amount`'\n);\n```\n\nYou should import `transfer` from `@solana/spl-token`.\n\n```js\nconst importDeclaration = babelisedCode\n  .getImportDeclarations()\n  .find(i => i.source.value === '@solana/spl-token');\nassert.exists(\n  importDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'transfer',\n  'An import from `@solana/spl-token` should import `transfer`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport {\n  getOrCreateAssociatedTokenAccount,\n  getAccount\n} from '@solana/spl-token';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n\nconst toWallet = Keypair.generate();\n\nconst toTokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  toWallet.publicKey\n);\n\nconst fromWallet = await getAccount(connection, fromTokenAccountPublicKey);\n\nconst owner = fromWallet.owner;\n\nconst amount = Number(process.argv[3]);\n```\n\n## 50\n\n### --description--\n\nAdd the following to the end of the file, to log the result of the transfer:\n\n```js\nconsole.log(\n  `Transferred ${amount} tokens from ${fromTokenAccountPublicKey.toBase58()} to ${toTokenAccount.address.toBase58()}`\n);\n```\n\n### --tests--\n\nYou should add the above code to the end of `transfer.js`.\n\n```js\nconst transferFile = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nassert.match(\n  transferFile,\n  /console\\.log\\s*\\(\\s*`Transferred \\${\\s*amount\\s*} tokens from \\${\\s*fromTokenAccountPublicKey\\.toBase58\\s*\\(\\s*\\)\\s*} to \\${\\s*toTokenAccount\\.address\\.toBase58\\s*\\(\\s*\\)\\s*}`\\s*\\)\\s*;?/,\n  'The code to log the result of the transfer should exist'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-solanas-token-program-by-minting-a-fungible-token/transfer.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport {\n  getOrCreateAssociatedTokenAccount,\n  getAccount,\n  transfer\n} from '@solana/spl-token';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n\nconst toWallet = Keypair.generate();\n\nconst toTokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  toWallet.publicKey\n);\n\nconst fromWallet = await getAccount(connection, fromTokenAccountPublicKey);\n\nconst owner = fromWallet.owner;\n\nconst amount = Number(process.argv[3]);\n\nawait transfer(\n  connection,\n  payer,\n  fromTokenAccountPublicKey,\n  toTokenAccount.address,\n  owner,\n  amount\n);\n```\n\n## 51\n\n### --description--\n\nRun the `transfer.js` script passing in the token account public key for `./wallet.json`, and any amount of tokens to transfer:\n\n```bash\n$ node get-token-account.js <wallet.json_public_key> # Get wallet.json token account address\n$ node transfer.js <token_account_public_key> <amount>\n```\n\n### --tests--\n\nYou should run the `transfer.js` script.\n\n```js\nconst { tokenAccount } = await __helpers.importSansCache(\n  './learn-solanas-token-program-by-minting-a-fungible-token/utils.js'\n);\nconst commandRe = new RegExp(`node transfer.js ${tokenAccount.toBase58()} \\\\d+`);\nconst lastCommand = await __helpers.getLastCommand();\nassert.match(\n  lastCommand,\n  commandRe,\n  `The last command should match '${commandRe}'`\n);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n### --seed--\n\n#### --\"transfer.js\"--\n\n```js\nimport { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport {\n  getOrCreateAssociatedTokenAccount,\n  getAccount,\n  transfer\n} from '@solana/spl-token';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n\nconst toWallet = Keypair.generate();\n\nconst toTokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  toWallet.publicKey\n);\n\nconst fromWallet = await getAccount(connection, fromTokenAccountPublicKey);\n\nconst owner = fromWallet.owner;\n\nconst amount = Number(process.argv[3]);\n\nawait transfer(\n  connection,\n  payer,\n  fromTokenAccountPublicKey,\n  toTokenAccount.address,\n  owner,\n  amount\n);\n\nconsole.log(\n  `Transferred ${amount} tokens from ${fromTokenAccountPublicKey.toBase58()} to ${toTokenAccount.address.toBase58()}`\n);\n```\n\n## 52\n\n### --description--\n\nContratulations on finishing this project! Feel free to play with your code.\n\n**Summary**\n\n- A _Mint Account_ is an account typically owned by an organisation who commissions the token, and keeps track of the supply of tokens\n- A _Token Account_ is an account typically owned by an individual, and keeps track of the tokens held by that individual\n- Minting tokens involves the _minting authority_ authorising a payer to create tokens and transfer them to a _Token Account_\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "curriculum/locales/english/learn-the-metaplex-sdk-by-minting-an-nft.md",
    "content": "# Solana - Learn the Metaplex SDK by Minting an NFT\n\n## 1\n\n### --description--\n\nIn this project, you will learn how to use the Metaplex JavaScript SDK to mint a <dfn title=\"A digitally transferable asset whose supply is 1\">Non-Fungible Token (NFT)</dfn> on the Solana blockchain. You will learn how to add metadata to your NFT, and how to upload your NFT somewhere it can be viewed by others.\n\nFor the duration of this project, you will be working in the `learn-the-metaplex-sdk-by-minting-an-nft/` directory.\n\nChange into the above directory in a new bash terminal.\n\n### --tests--\n\nYou can use `cd` to change into the `learn-the-metaplex-sdk-by-minting-an-nft/` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-the-metaplex-sdk-by-minting-an-nft');\n```\n\n## 2\n\n### --description--\n\nSo far, you have interacted with the _System Program_, created and deployed your own program, and interacted with the _Token Program_. Now, you will interact with the _Metaplex Token Program_ to mint your NFT.\n\nStarting from the same code as the previous project, you will change the the way the mint account is created, and the way the token is minted such that it is an NFT.\n\nCreate a new keypair in a file called `wallet.json`.\n\n### --tests--\n\nYou should have a `wallet.json` file in the `learn-the-metaplex-sdk-my-minting-an-nft/` directory.\n\n```js\nconst walletJsonExists = __helpers.fileExists(\n  'learn-the-metaplex-sdk-by-minting-an-nft/wallet.json'\n);\nassert.isTrue(walletJsonExists, 'The `wallet.json` file should exist');\nconst walletJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/wallet.json'\n  )\n);\nassert.isArray(\n  walletJson,\n  'The `wallet.json` file should be an array of numbers.\\nRun `solana-keygen new --outfile wallet.json` to create a new keypair.'\n);\n```\n\n## 3\n\n### --description--\n\nStart a local Solana cluster. Ensure the RPC URL is set to `http://localhost:8899`.\n\n### --tests--\n\nYour Solana config RPC URL should be set to `http://localhost:8899`.\n\n```js\nconst command = `solana config get json_rpc_url`;\nawait new Promise(res => setTimeout(() => res(), 2000));\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\nassert.include(\n  stdout,\n  'http://localhost:8899',\n  'Try running `solana config set --url localhost`'\n);\n```\n\nYou should run `solana-test-validator` in a separate terminal.\n\n```js\nawait new Promise(res => setTimeout(() => res(), 2000));\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 4\n\n### --description--\n\nUse the Solana CLI to get the public key of the wallet you created. Put this public key in the `env.WALLET_ADDRESS` property of the `package.json` file.\n\n### --tests--\n\nThe `package.json` file should have a `env.WALLET_ADDRESS` property that is not empty.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson,\n  'env',\n  'The `package.json` file should have an `env` property.'\n);\nassert.property(\n  packageJson.env,\n  'WALLET_ADDRESS',\n  'The `package.json` file should have an `env.WALLET_ADDRESS` property.'\n);\nassert.notEmpty(\n  packageJson.env.WALLET_ADDRESS,\n  'The `env.WALLET_ADDRESS` property should have a value.'\n);\n```\n\nThe `env.WALLET_ADDRESS` property should match the public key of `wallet.json`.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nconst command =\n  'solana address -k learn-the-metaplex-sdk-by-minting-an-nft/wallet.json';\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\nconst walletAddress = stdout.trim();\nconsole.debug('walletAddress', walletAddress, packageJson.env.WALLET_ADDRESS);\nassert.notEmpty(\n  walletAddress,\n  'The `wallet.json` file should have a public key.'\n);\nassert.notEmpty(\n  packageJson.env.WALLET_ADDRESS,\n  'The `env.WALLET_ADDRESS` property should have a value.'\n);\nassert.equal(\n  packageJson.env.WALLET_ADDRESS,\n  walletAddress,\n  'The `env.WALLET_ADDRESS` property should match the public key of `wallet.json`.'\n);\n```\n\n## 5\n\n### --description--\n\n<!-- 1. Create mint account -->\n\nFollowing the same steps to create a fungible token, now a mint account for your NFT needs to be created.\n\nSeeing as NFT's are not divisible - you cannot own 0.1 of an NFT - the decimal argument of the `createMint` call needs to be changed. Within `spl-program/create-mint-account.js`, change the decimal place argument to `0`.\n\n### --tests--\n\nThe `createMint` call within `spl-program/create-mint-account.js` should have a decimal place argument of `0`.\n\n```js\nconst mintDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'mint';\n});\nassert.exists(mintDeclaration, 'A variable named `mint` should exist');\nconst mintAwaitExpression = mintDeclaration.declarations?.[0]?.init;\nassert.equal(\n  mintAwaitExpression.type,\n  'AwaitExpression',\n  'The `createMint` call should be awaited'\n);\nconst createMintCallExpression = mintAwaitExpression.argument;\nassert.equal(\n  createMintCallExpression.callee.name,\n  'createMint',\n  'The `mint` variable should be initialised with the `createMint` function'\n);\nconst createMintArguments = createMintCallExpression.arguments;\nassert.equal(\n  createMintArguments.length,\n  5,\n  'The `createMint` function should be called with 5 arguments'\n);\nconst [\n  connectionArgument,\n  payerArgument,\n  mintAuthorityArgument,\n  freezeAuthorityArgument,\n  decimalsArgument\n] = createMintArguments;\nassert.equal(\n  connectionArgument.name,\n  'connection',\n  'The first argument to `createMint` should be `connection`'\n);\nassert.equal(\n  payerArgument.name,\n  'payer',\n  'The second argument to `createMint` should be `payer`'\n);\nassert.equal(\n  mintAuthorityArgument.name,\n  'mintAuthority',\n  'The third argument to `createMint` should be `mintAuthority`'\n);\nassert.equal(\n  freezeAuthorityArgument.name,\n  'freezeAuthority',\n  'The fourth argument to `createMint` should be `freezeAuthority`'\n);\nassert.equal(\n  decimalsArgument.value,\n  0,\n  'The fifth argument to `createMint` should be `0`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/spl-program/create-mint-account.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 6\n\n### --description--\n\nAnother properties of NFT's is that there is only one of each NFT. This means that the `mintTo` call needs to be changed to mint only one token.\n\nWithin `spl-program/mint.js`, change the `amount` argument of the `mintTo` call to `1`.\n\n### --tests--\n\nThe `mintTo` call within `spl-program/mint.js` should have an `amount` argument of `1`.\n\n```js\nconst expressionStatement = babelisedCode\n  .getExpressionStatements()\n  .find(\n    e =>\n      e.expression.type === 'AwaitExpression' &&\n      e.expression.argument?.callee?.name === 'mintTo'\n  );\nassert.exists(\n  expressionStatement,\n  'An `await mintTo(...)` expression should exist'\n);\nconst mintToArguments = expressionStatement.expression.argument.arguments;\nconst [\n  connectionArgument,\n  payerArgument,\n  mintAddressArgument,\n  tokenAccountArgument,\n  mintAuthorityArgument,\n  amountArgument\n] = mintToArguments;\nassert.equal(\n  connectionArgument?.name,\n  'connection',\n  'The first argument to `mintTo` should be `connection`'\n);\nassert.equal(\n  payerArgument?.name,\n  'payer',\n  'The second argument to `mintTo` should be `payer`'\n);\nassert.equal(\n  mintAddressArgument?.name,\n  'mintAddress',\n  'The third argument to `mintTo` should be `mintAddress`'\n);\nassert.equal(\n  tokenAccountArgument?.name,\n  'tokenAccount',\n  'The fourth argument to `mintTo` should be `tokenAccount`'\n);\nassert.equal(\n  mintAuthorityArgument?.name,\n  'mintAuthority',\n  'The fifth argument to `mintTo` should be `mintAuthority`'\n);\nassert.equal(\n  amountArgument?.value,\n  1,\n  'The sixth argument to `mintTo` should be `1`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/spl-program/mint.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 7\n\n### --description--\n\nOnce the NFT has been minted to an account, the mint authority needs to be removed from the mint account. The mint authority is the only account that can mint more tokens. If the mint authority is removed, then no more tokens can be minted.\n\nWithin `spl-program/mint.js`, import the `setAuthority` function from `@solana/spl-token`, to change the mint authority.\n\n```typescript\nsetAuthority(\n  connection: Connection,\n  payer: Signer,\n  account: PublicKey,\n  currentAuthority: Signer | PublicKey,\n  authorityType: AuthorityType, // Import this enum from @solana/spl-token!\n  newAuthority: PublicKey | null\n): Promise\n```\n\nCall and await the `setAuthority` function, passing in the `connection`, `payer`, `mintAddress`, `mintAuthority`, `AuthorityType.MintTokens`, and `null` arguments.\n\n### --tests--\n\nYou should have `await setAuthority(connection, payer, mintAddress, mintAuthority, AuthorityType.MintTokens, null);` in `spl-program/mint.js`.\n\n```js\nconst expressionStatement = babelisedCode\n  .getExpressionStatements()\n  .find(\n    e =>\n      e.expression.type === 'AwaitExpression' &&\n      e.expression.argument?.callee?.name === 'setAuthority'\n  );\nassert.exists(\n  expressionStatement,\n  'An `await setAuthority(...)` expression should exist'\n);\nconst setAuthorityArguments = expressionStatement.expression.argument.arguments;\nconst [\n  connectionArgument,\n  payerArgument,\n  mintAddressArgument,\n  mintAuthorityArgument,\n  authorityTypeMemberExpressionArgument,\n  nullArgument\n] = setAuthorityArguments;\nassert.equal(\n  connectionArgument?.name,\n  'connection',\n  'The first argument to `setAuthority` should be `connection`'\n);\nassert.equal(\n  payerArgument?.name,\n  'payer',\n  'The second argument to `setAuthority` should be `payer`'\n);\nassert.equal(\n  mintAddressArgument?.name,\n  'mintAddress',\n  'The third argument to `setAuthority` should be `mintAddress`'\n);\nassert.equal(\n  mintAuthorityArgument?.name,\n  'mintAuthority',\n  'The fourth argument to `setAuthority` should be `mintAuthority`'\n);\nassert.equal(\n  authorityTypeMemberExpressionArgument?.object?.name,\n  'AuthorityType',\n  'The fifth argument to `setAuthority` should be `AuthorityType.MintTokens`'\n);\nassert.equal(\n  authorityTypeMemberExpressionArgument?.property?.name,\n  'MintTokens',\n  'The fifth argument to `setAuthority` should be `AuthorityType.MintTokens`'\n);\nassert.equal(\n  nullArgument?.type,\n  'NullLiteral',\n  'The sixth argument to `setAuthority` should be `null`'\n);\n```\n\nYou should import `setAuthority` from `@solana/spl-token`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/spl-token';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'setAuthority',\n  '`setAuthority` should be imported from `@solana/spl-token`'\n);\n```\n\nYou should import `AuthorityType` from `@solana/spl-token`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/spl-token';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@solana/spl-token` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => {\n  return s.imported.name;\n});\nassert.include(\n  importSpecifiers,\n  'AuthorityType',\n  '`AuthorityType` should be imported from `@solana/spl-token`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/spl-program/mint.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 8\n\n### --description--\n\nWith those small changes, you can now mint an NFT!\n\nRun the following to create the mint account:\n\n```bash\nnode spl-program/create-mint-account.js\n```\n\n**Note:** You should see an error (similar to below) in the terminal.\n\n<details>\n  <summary>Error</summary>\n\n```bash\nFailedToSendTransactionError: The transaction could not be sent successfully to the network. Please check the underlying error below for more details.\n\nSource: RPC\n\nCaused By: Error: failed to send transaction: Transaction simulation failed: Attempt to debit an account but found no record of a prior credit.\n```\n\n</details>\n\n### --tests--\n\nYou should run `node spl-program/create-mint-account.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node spl-program/create-mint-account.js',\n  'Try running `node spl-program/create-mint-account.js` in the terminal'\n);\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 9\n\n### --description--\n\nRemember to airdrop some SOL to the wallet 🤦‍♂️\n\n### --tests--\n\nThe `wallet.json` account should have at least 2 SOL.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ./learn-the-metaplex-sdk-by-minting-an-nft/wallet.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAtLeast(\n  parseInt(balance),\n  2,\n  'Try running `solana airdrop 2 ./wallet.json`'\n);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 10\n\n### --description--\n\nYou can now mint an NFT!\n\nRun the following to create the mint account:\n\n```bash\nnode spl-program/create-mint-account.js\n```\n\n### --tests--\n\nYou should run `node spl-program/create-mint-account.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node spl-program/create-mint-account.js',\n  'Try running `node spl-program/create-mint-account.js` in the terminal'\n);\n```\n\nThe command should succeed.\n\n```js\n// Test terminal-out includes value that can be parsed as a PublicKey.\nconst terminalOut = await __helpers.getTerminalOutput();\nconst base58String = terminalOut.match(/[\\w\\d]{32,44}/)?.[0];\nassert.exists(\n  base58String,\n  'The terminal output should include a base58 string'\n);\ntry {\n  console.log('TEST: ', terminalOut.match(/[\\w\\d]{32,44}/));\n  const { PublicKey } = await import('@solana/web3.js');\n  new PublicKey(base58String);\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 11\n\n### --description--\n\nPut the mint address in `env.MINT_ACCOUNT_ADDRESS` in the `package.json` file.\n\n### --tests--\n\nThe `package.json` file should have a `env.MINT_ACCOUNT_ADDRESS` property.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson,\n  'env',\n  'The `package.json` file should have an `env` property.'\n);\nassert.property(\n  packageJson.env,\n  'MINT_ACCOUNT_ADDRESS',\n  'The `package.json` file should have an `env.MINT_ACCOUNT_ADDRESS` property.'\n);\n```\n\nThe `env.MINT_ACCOUNT_ADDRESS` property should match an NFT owned by `wallet.json`.\n\n```js\nconst command =\n  'solana address -k learn-the-metaplex-sdk-by-minting-an-nft/wallet.json';\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\nconst walletAddress = stdout.trim();\n\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson,\n  'env',\n  'The `package.json` file should have an `env` property.'\n);\n\nassert.equal(\n  packageJson.env.WALLET_ADDRESS,\n  walletAddress,\n  'The `env.WALLET_ADDRESS` property should match the public key of `wallet.json`.'\n);\n\ntry {\n  const { Connection } = await import('@solana/web3.js');\n  const { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');\n  const connection = new Connection('http://127.0.0.1:8899');\n\n  const mintAccounts = await connection.getParsedProgramAccounts(\n    TOKEN_PROGRAM_ID,\n    {\n      filters: [\n        {\n          dataSize: 82\n        },\n        {\n          memcmp: {\n            offset: 4,\n            bytes: packageJson.env.WALLET_ADDRESS\n          }\n        }\n      ]\n    }\n  );\n\n  const mintAccount = mintAccounts.find(\n    ({ pubkey }) => pubkey?.toBase58() === packageJson.env.MINT_ACCOUNT_ADDRESS\n  );\n  assert.exists(\n    mintAccount,\n    'The `env.MINT_ACCOUNT_ADDRESS` property should match an NFT owned by `wallet.json`.'\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 12\n\n### --description--\n\nRun the following to create the token account:\n\n```bash\nnode spl-program/create-token-account.js\n```\n\n### --tests--\n\nYou should run `node spl-program/create-token-account.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node spl-program/create-token-account.js',\n  'Try running `node spl-program/create-token-account.js` in the terminal'\n);\n```\n\nThe command should succeed.\n\n```js\n// Test terminal-out includes value that can be parsed as a PublicKey.\nconst terminalOut = await __helpers.getTerminalOutput();\nconst base58String = terminalOut.match(/[\\w\\d]{32,44}/)?.[0];\nconst error = terminalOut.match('Error')?.[0];\nassert.notExists(error);\nassert.exists(\n  base58String,\n  'The terminal output should include a base58 string'\n);\ntry {\n  const { PublicKey } = await import('@solana/web3.js');\n  new PublicKey(base58String);\n} catch (e) {\n  assert.fail(e, 'Public key should be printed to the terminal');\n}\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 13\n\n### --description--\n\nPut the token account address in `env.TOKEN_ACCOUNT_ADDRESS` in the `package.json` file.\n\n### --tests--\n\nThe `package.json` file should have a `env` property.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson,\n  'env',\n  'The `package.json` file should have an `env` property.'\n);\n```\n\nThe `package.json` file should have a `env.TOKEN_ACCOUNT_ADDRESS` property.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson.env,\n  'TOKEN_ACCOUNT_ADDRESS',\n  'The `package.json` file should have an `env` property.'\n);\nassert.notEmpty(packageJson.env.TOKEN_ACCOUNT_ADDRESS);\n```\n\nThe `env.TOKEN_ACCOUNT_ADDRESS` property should match an NFT owned by `wallet.json`.\n\n```js\nconst command =\n  'solana address -k learn-the-metaplex-sdk-by-minting-an-nft/wallet.json';\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\nconst walletAddress = stdout.trim();\n\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson,\n  'env',\n  'The `package.json` file should have an `env` property.'\n);\n\nassert.equal(\n  packageJson.env.WALLET_ADDRESS,\n  walletAddress,\n  'The `env.WALLET_ADDRESS` property should match the public key of `wallet.json`.'\n);\n\ntry {\n  const { Connection } = await import('@solana/web3.js');\n  const { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');\n  const connection = new Connection('http://127.0.0.1:8899');\n\n  const tokenAccounts = await connection.getParsedProgramAccounts(\n    TOKEN_PROGRAM_ID,\n    {\n      filters: [\n        {\n          dataSize: 165\n        },\n        {\n          memcmp: {\n            offset: 32,\n            bytes: packageJson.env.WALLET_ADDRESS\n          }\n        }\n      ]\n    }\n  );\n\n  const tokenAccount = tokenAccounts.find(\n    ({ pubkey }) => pubkey.toBase58() === packageJson.env.TOKEN_ACCOUNT_ADDRESS\n  );\n  assert.exists(\n    tokenAccount,\n    'The `env.TOKEN_ACCOUNT_ADDRESS` property should match an NFT owned by `wallet.json`.'\n  );\n  assert.equal(\n    tokenAccount.account.data.parsed.info.mint,\n    packageJson.env.MINT_ACCOUNT_ADDRESS,\n    'The `env.TOKEN_ACCOUNT_ADDRESS` should be associated with the `env.MINT_ACCOUNT_ADDRESS`.'\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 14\n\n### --description--\n\nRun the following to mint the NFT to the token account:\n\n```bash\nnode spl-program/mint.js\n```\n\n### --tests--\n\nYou should run `node spl-program/mint.js` in the terminal.\n\n```js\n// Test command is run in correct directory\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node spl-program/mint.js',\n  'Try running `node spl-program/mint.js` in the terminal'\n);\n```\n\nThe command should succeed.\n\n```js\n// Test authority is null\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson,\n  'env',\n  'The `package.json` file should have an `env` property.'\n);\n\nassert.notEmpty(\n  packageJson.env.MINT_ACCOUNT_ADDRESS,\n  'The `env.MINT_ACCOUNT_ADDRESS` property should be set.'\n);\n\ntry {\n  const { Connection, PublicKey } = await import('@solana/web3.js');\n  const { getMint } = await import('@solana/spl-token');\n  const connection = new Connection('http://127.0.0.1:8899');\n\n  const mint = await getMint(\n    connection,\n    new PublicKey(packageJson.env.MINT_ACCOUNT_ADDRESS)\n  );\n  assert.equal(mint.mintAuthority, null, 'The mint authority should be null.');\n} catch (e) {\n  assert.fail(e);\n}\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 15\n\n### --description--\n\nRun the following to confirm the NFT has no mint authority, and has a total supply of 1:\n\n```bash\nnode spl-program/get-token-info.js\n```\n\n### --tests--\n\nYou should run `node spl-program/get-token-info.js` in the terminal.\n\n```js\n// Test command is run in correct directory\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node spl-program/get-token-info.js',\n  'Try running `node spl-program/get-token-info.js` in the terminal'\n);\n```\n\nThe command should succeed.\n\n```js\n// Script should be idempotent\nconst command = `node spl-program/get-token-info.js`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(\n  command,\n  'learn-the-metaplex-sdk-by-minting-an-nft'\n);\nassert.include(stdout, 'mintAuthority: null');\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 16\n\n### --description--\n\nYou often hear about NFTs being odd pictures of a cat, but what if you wanted to associate more information with the NFT? For example, you could associate a name, description, and image with the NFT. This is where metadata comes in.\n\nThe metadata file is stored on IPFS, and the NFT is associated with the metadata file by storing the IPFS hash in the NFT's data field.\n\nThere are many accounts and files involved in the process of creating an NFT with metadata. Metaplex has a JavaScript SDK which provides common functionality for creating NFTs with metadata.\n\nInstall version `0.18.1` of `@metaplex-foundation/js`.\n\n### --tests--\n\nYou should install `@metaplex-foundation/js` version `0.18.1`.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson.dependencies,\n  '@metaplex-foundation/js',\n  'The `package.json` file should have a `@metaplex-foundation/js` dependency.'\n);\nassert.equal(\n  packageJson.dependencies['@metaplex-foundation/js'],\n  '0.18.1',\n  'Try running `npm install --save-exact @metaplex-foundation/js@0.18.1` in the terminal.'\n);\n```\n\n## 17\n\n### --description--\n\nCreate a file called `create-nft.js`.\n\n### --tests--\n\nYou should create a file called `create-nft.js`.\n\n```js\nconst fileExists = __helpers.fileExists(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nassert.isTrue(\n  fileExists,\n  'The `learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js` file should exist.'\n);\n```\n\n## 18\n\n### --description--\n\nWithin `create-nft.js`, create a `connection` variable set to a new `Connection` of your local Solana validator.\n\n### --tests--\n\nYou should have `const connection = new Connection('http://127.0.0.1:8899');` in `create-nft.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'You should declare a variable named `connection`'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee.name,\n  'Connection',\n  'You should initialise `connection` with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments[0].value,\n  'http://127.0.0.1:8899',\n  \"You should create a new connection with `new Connection('http://127.0.0.1:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `@solana/web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'Connection',\n  '`Connection` should be imported from `@solana/spl-token`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 19\n\n### --description--\n\nImport the `Metaplex` class from the Metaplex SDK, create a `metaplex` variable, and set it to:\n\n```js\nMetaplex.make();\n```\n\n### --tests--\n\nYou should have `const metaplex = Metaplex.make();` in `create-nft.js`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst variableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'metaplex');\nassert.exists(\n  variableDeclaration,\n  'You should declare a variable named `metaplex`'\n);\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(minifiedCode, /metaplex=Metaplex\\.make\\(\\)/);\n```\n\nYou should import `Metaplex` from `@metaplex-foundation/js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@metaplex-foundation/js';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@metaplex-foundation/js` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'Metaplex',\n  '`Metaplex` should be imported from `@metaplex-foundation/js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 20\n\n### --description--\n\nThe `make` method on the `Metaplex` class expects a `Connection` that will be used to communicate with the cluster.\n\nPass the `connection` variable to the `make` method.\n\n### --tests--\n\nYou should have `const metaplex = Metaplex.make(connection);` in `create-nft.js`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'metaplex';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(minifiedCode, /metaplex=Metaplex\\.make\\(connection\\)/);\n```\n\n## 21\n\n### --description--\n\nThe `Metaplex` class has chainable `use` methods that allow you to configure the `Metaplex` instance with `MetaplexPlugin` implementations.\n\nConfigure the `Metaplex` instance to use the wallet keypair as the primary identity for transactions:\n\n```js\nMetaplex.make(connection).use(keypairIdentity(WALLET_KEYPAIR));\n```\n\nThe `keypairIdentity` function takes a `Keypair` and returns a `MetaplexPlugin`, and is exported from `@metaplex-foundation/js`.\n\nThe `WALLET_KEYPAIR` variable is a `Keypair` created from the `wallet.json` file, and is already exported from `utils.js`.\n\n### --tests--\n\nYou should have `const metaplex = Metaplex.make(connection).use(keypairIdentity(WALLET_KEYPAIR));` in `create-nft.js`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'metaplex';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /metaplex=Metaplex\\.make\\(connection\\)\\.use\\(keypairIdentity\\(WALLET_KEYPAIR\\)\\)/\n);\n```\n\nYou should import `keypairIdentity` from `@metaplex-foundation/js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@metaplex-foundation/js';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@metaplex-foundation/js` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'keypairIdentity',\n  '`keypairIdentity` should be imported from `@metaplex-foundation/js`'\n);\n```\n\nYou should import `WALLET_KEYPAIR` from `utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'An import from `./utils.js` should exist');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'WALLET_KEYPAIR',\n  '`WALLET_KEYPAIR` should be imported from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 22\n\n### --description--\n\nThe final configuration required is the _storage driver_ that will be used to store the NFT's metadata. This is because the NFT's metadata is not stored on-chain, but rather in a separate, often cheaper, storage system.\n\nSome common metadata storage locations are:\n\n- IPFS\n- Arweave\n- AWS\n\nFor the purpose of testing, and to avoid having to pay for storage, you can use the `localStorage` driver which will generate random URLs and keep track of their content in a local server.\n\nConfigure the `Metaplex` instance to use the `localStorage` driver.\n\n### --tests--\n\nYou should have `const metaplex = Metaplex.make(connection).use(keypairIdentity(WALLET_KEYPAIR)).use(localStorage());` in `create-nft.js`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'metaplex';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /metaplex=Metaplex\\.make\\(connection\\)\\.use\\(keypairIdentity\\(WALLET_KEYPAIR\\)\\)\\.use\\(localStorage\\(\\)\\)/\n);\n```\n\nYou should import `localStorage` from `utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'An import from `./utils.js` should exist');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'localStorage',\n  '`localStorage` should be imported from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 23\n\n### --description--\n\nThe storage driver takes an object with a `baseUrl` property, which is the base URL that will be used to generate the metadata URLs.\n\nSet the `baseUrl` to `http://127.0.0.1:3001/`.\n\n### --tests--\n\nYou should have `const metaplex = Metaplex.make(connection).use(keypairIdentity(WALLET_KEYPAIR)).use(localStorage({ baseUrl: 'http://127.0.0.1:3001/' }));` in `create-nft.js`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'metaplex';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /metaplex=Metaplex\\.make\\(connection\\)\\.use\\(keypairIdentity\\(WALLET_KEYPAIR\\)\\)\\.use\\(localStorage\\(\\{('|\"|`)?baseUrl\\1:('|\"|`)http:\\/\\/127\\.0\\.0\\.1:3001\\/\\2\\}\\)\\)/\n);\n```\n\n## 24\n\n### --description--\n\nYou can use any image you want for your NFT, but one is provided for you in the `assets` folder.\n\nDeclare a variable `imageBuffer`, and set it to the contents of the `assets/pic.png` file. You can use the `fs/promises` module to read the file.\n\n### --tests--\n\nYou can use `const imageBuffer = await readFile('assets/pic.png');` in `create-nft.js`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'imageBuffer';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /imageBuffer=(await readFile|readFileSync|read)\\(('|\"|`)(\\.\\/)?assets\\/pic\\.png\\2\\)/\n);\n```\n\nYou should import one of the file-reading API.\n\n```js\n// one of fs, fs/promises\n// one of readFileSync | readFile | read\nconst importDeclarations = babelisedCode.getImportDeclarations().filter(i => {\n  return i.source.value === 'fs' || i.source.value === 'fs/promises';\n});\nassert.notEmpty(\n  importDeclarations,\n  'An import from `fs` or `fs/promises` should exist'\n);\nconst importSpecifiers = importDeclarations.flatMap(i =>\n  i.specifiers.map(s => s.imported.name)\n);\nconst fileReadingApis = ['readFile', 'readFileSync', 'read'];\nlet found = false;\nout: {\n  for (const i of importSpecifiers) {\n    for (const f of fileReadingApis) {\n      if (i === f) {\n        found = true;\n        break out;\n      }\n    }\n  }\n}\nassert.isTrue(found, 'One of the file-reading APIs should be imported');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 25\n\n### --description--\n\nThe `@metaplex-foundation/js` module exports a `toMetaplexFile` function:\n\n```typescript\ntoMetaplexFile(\n  content: MetaplexFileContent,\n  fileName: string\n): MetaplexFile\n```\n\nThe `MetaplexFile` type contains useful metadata about the file which is useful for your NFT's metadata.\n\nDeclare a `file` variable, and set it to the result of calling `toMetaplexFile` with the `imageBuffer` and the filename `pic.png`.\n\n### --tests--\n\nYou should have `const file = toMetaplexFile(imageBuffer, 'pic.png');` in `create-nft.js`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'file';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /file=toMetaplexFile\\(imageBuffer,('|\"|`)?pic\\.png\\1\\)/\n);\n```\n\nYou should import `toMetaplexFile` from `@metaplex-foundation/js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@metaplex-foundation/js';\n});\nassert.exists(\n  importDeclaration,\n  'An import from `@metaplex-foundation/js` should exist'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'toMetaplexFile',\n  'The `toMetaplexFile` function should be imported'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 26\n\n### --description--\n\nThe `Metaplex` class has a `storage` method that returns the storage driver configured earlier. The storage driver has an `upload` method that takes a `MetaplexFile` and returns a `Promise` that resolves to a `string` containing the URL of the uploaded file.\n\nDeclare an `image` variable, and set it to the result of awaiting `metaplex.storage().upload(file)`.\n\n### --tests--\n\nYou should have `const image = await metaplex.storage().upload(file);` in `create-nft.js`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'image';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /\\s+image=await metaplex\\.storage\\(\\)\\.upload\\(file\\)/\n);\n```\n\n## 27\n\n### --description--\n\nWith the URL to the uploaded image, you can upload the NFT's metadata. The `Metaplex` class has an `nfts` method that returns many useful methods for working with NFT's. One of these methods is `uploadMetadata`:\n\n<details>\n  <summary><code>uploadMetadata</code> Signature</summary>\n\n```typescript\nuploadMetadata(\n  input: {\n    name?: string;\n    symbol?: string;\n    description?: string;\n    seller_fee_basis_points?: number;\n    image?: MetaplexFile;\n    external_url?: MetaplexFile;\n    attributes?: Array<{\n      trait_type?: string;\n      value?: string;\n      [key: string]: unknown;\n    }>;\n    properties?: {\n      creators?: Array<{\n        address?: string;\n        share?: number;\n        [key: string]: unknown;\n      }>;\n      files?: Array<{\n        type?: string;\n        uri?: MetaplexFile;\n        [key: string]: unknown;\n      }>;\n      [key: string]: unknown;\n    };\n    collection?: {\n      name?: string;\n      family?: string;\n      [key: string]: unknown;\n    };\n    [key: string]: unknown;\n  }\n): Promise\n```\n\n</details>\n\nThat is a lot of metadata! For now, you can just set the `name`, `description`, and `image` properties.\n\nDestructure the `uri` property from the result of awaiting `uploadMetadata`. Pass the `image` variable as the `image` property, and give the `name` and `description` properties some sane values.\n\n### --tests--\n\nYou should have `const { uri } = await metaplex.nfts().uploadMetadata({ name: 'any string', description: 'any string', image });` in `create-nft.js`.\n\n```js\nconst uriVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => {\n    return v.declarations?.[0]?.id?.properties?.find(p => p.key.name === 'uri');\n  });\nassert.exists(\n  uriVariableDeclaration,\n  'A variable declaration with a `uri` property should exist'\n);\nconst codeString = babelisedCode.generateCode(uriVariableDeclaration);\n\nconst metaplex = `\nconst metaplex = {\n  nfts: () => ({\n    uploadMetadata: ({name, description, image}) => {\n        if (typeof name !== 'string') {\n            throw new Error('name must be a string');\n        }\n        if (typeof description !== 'string') {\n            throw new Error('description must be a string');\n        }\n        return Promise.resolve({ uri: 'any string' });\n      }\n  })\n};\nlet image, name, description;\n`;\nconst testString = `${metaplex}\\n${codeString}`;\n\ntry {\n  await eval(`(async () => {${testString}})()`);\n} catch (e) {\n  assert.fail(e.message);\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 28\n\n### --description--\n\nAnother method on the `nfts` object is `create`:\n\n<details>\n  <summary><code>create</code> Signature</summary>\n\n````typescript\ncreate(\n  input: {\n    /**\n     * The authority that will be able to make changes\n     * to the created NFT.\n     *\n     * This is required as a Signer because creating the master\n     * edition account requires the update authority to sign\n     * the transaction.\n     *\n     * @defaultValue `metaplex.identity()`\n     */\n    updateAuthority?: Signer;\n    /**\n     * The authority that is currently allowed to mint new tokens\n     * for the provided mint account.\n     *\n     * Note that this is only relevant if the `useExistingMint` parameter\n     * if provided.\n     *\n     * @defaultValue `metaplex.identity()`\n     */\n    mintAuthority?: Signer;\n    /**\n     * The address of the new mint account as a Signer.\n     * This is useful if you already have a generated Keypair\n     * for the mint account of the NFT to create.\n     *\n     * @defaultValue `Keypair.generateCode()`\n     */\n    useNewMint?: Signer;\n    /**\n     * The address of the existing mint account that should be converted\n     * into an NFT. The account at this address should have the right\n     * requirements to become an NFT, e.g. its supply should contains\n     * exactly 1 token.\n     *\n     * @defaultValue Defaults to creating a new mint account with the\n     * right requirements.\n     */\n    useExistingMint?: PublicKey;\n    /**\n     * Whether or not we should mint one token for the new NFT.\n     *\n     * @defaultValue `true`\n     */\n    mintTokens?: boolean;\n    /**\n     * The owner of the NFT to create.\n     *\n     * @defaultValue `metaplex.identity().publicKey`\n     */\n    tokenOwner?: PublicKey;\n    /**\n     * The token account linking the mint account and the token owner\n     * together. By default, the associated token account will be used.\n     *\n     * If the provided token account does not exist, it must be passed as\n     * a Signer as we will need to create it before creating the NFT.\n     *\n     * @defaultValue Defaults to creating a new associated token account\n     * using the `mintAddress` and `tokenOwner` parameters.\n     */\n    tokenAddress?: PublicKey | Signer;\n    /**\n     * Describes the asset class of the token.\n     * It can be one of the following:\n     * - `TokenStandard.NonFungible`: A traditional NFT (master edition).\n     * - `TokenStandard.FungibleAsset`: A fungible token with metadata that can also have attrributes.\n     * - `TokenStandard.Fungible`: A fungible token with simple metadata.\n     * - `TokenStandard.NonFungibleEdition`: A limited edition NFT \"printed\" from a master edition.\n     * - `TokenStandard.ProgrammableNonFungible`: A master edition NFT with programmable configuration.\n     *\n     * @defaultValue `TokenStandard.NonFungible`\n     */\n    tokenStandard?: TokenStandard;\n    /** The URI that points to the JSON metadata of the asset. */\n    uri: string;\n    /** The on-chain name of the asset, e.g. \"My NFT #123\". */\n    name: string;\n    /**\n     * The royalties in percent basis point (i.e. 250 is 2.5%) that\n     * should be paid to the creators on each secondary sale.\n     */\n    sellerFeeBasisPoints: number;\n    /**\n     * The on-chain symbol of the asset, stored in the Metadata account.\n     * E.g. \"MYNFT\".\n     *\n     * @defaultValue `\"\"`\n     */\n    symbol?: string;\n    /**\n     * {@inheritDoc CreatorInput}\n     * @defaultValue\n     * Defaults to using the provided `updateAuthority` as the only verified creator.\n     * ```ts\n     * [{\n     *   address: updateAuthority.publicKey,\n     *   authority: updateAuthority,\n     *   share: 100,\n     * }]\n     * ```\n     */\n    creators?: CreatorInput[];\n    /**\n     * Whether or not the NFT's metadata is mutable.\n     * When set to `false` no one can update the Metadata account,\n     * not even the update authority.\n     *\n     * @defaultValue `true`\n     */\n    isMutable?: boolean;\n    /**\n     * Whether or not selling this asset is considered a primary sale.\n     * Once flipped from `false` to `true`, this field is immutable and\n     * all subsequent sales of this asset will be considered secondary.\n     *\n     * @defaultValue `false`\n     */\n    primarySaleHappened?: boolean;\n    /**\n     * The maximum supply of printed editions.\n     * When this is `null`, an unlimited amount of editions\n     * can be printed from the original edition.\n     *\n     * @defaultValue `toBigNumber(0)`\n     */\n    maxSupply?: Option<BigNumber>;\n    /**\n     * When this field is not `null`, it indicates that the NFT\n     * can be \"used\" by its owner or any approved \"use authorities\".\n     *\n     * @defaultValue `null`\n     */\n    uses?: Option<Uses>;\n    /**\n     * Whether the created NFT is a Collection NFT.\n     * When set to `true`, the NFT will be created as a\n     * Sized Collection NFT with an initial size of 0.\n     *\n     * @defaultValue `false`\n     */\n    isCollection?: boolean;\n    /**\n     * The Collection NFT that this new NFT belongs to.\n     * When `null`, the created NFT will not be part of a collection.\n     *\n     * @defaultValue `null`\n     */\n    collection?: Option<PublicKey>;\n    /**\n     * The collection authority that should sign the created NFT\n     * to prove that it is part of the provided collection.\n     * When `null`, the provided `collection` will not be verified.\n     *\n     * @defaultValue `null`\n     */\n    collectionAuthority?: Option<Signer>;\n    /**\n     * Whether or not the provided `collectionAuthority` is a delegated\n     * collection authority, i.e. it was approved by the update authority\n     * using `metaplex.nfts().approveCollectionAuthority()`.\n     *\n     * @defaultValue `false`\n     */\n    collectionAuthorityIsDelegated?: boolean;\n    /**\n     * Whether or not the provided `collection` is a sized collection\n     * and not a legacy collection.\n     *\n     * @defaultValue `true`\n     */\n    collectionIsSized?: boolean;\n    /**\n     * The ruleset account that should be used to configure the\n     * programmable NFT.\n     *\n     * This is only relevant for programmable NFTs, i.e. if the\n     * `tokenStandard` is set to `TokenStandard.ProgrammableNonFungible`.\n     *\n     * @defaultValue `null`\n     */\n    ruleSet?: Option<PublicKey>;\n  }\n): Promise\n````\n\n</details>\n\nThe only required properties are `name`, `uri`, and `sellerFeeBasisPoints`. On top of those, you should also provide a `maxSupply` of `1` if you want to create a limited edition NFT.\n\nThe `create` method will take care of creating the mint account, the associated token account, the metadata PDA and the original edition PDA (a.k.a. the master edition) for you.\n\nDeclare a variable `createResponse`, and assign to it the result of awaiting the `create` method. Pass in the previously mentioned properties with sane values.\n\n### --tests--\n\nYou should have `const createResponse = await metaplex.nfts().create({ name: \"any string\", uri, sellerFeeBasisPoints: <any_int>, maxSupply: 1 });` in `create-nft.js`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id.name === 'createResponse';\n});\nassert.exists(variableDeclaration, 'A variable `createResponse` should exist');\n\nconst codeString = babelisedCode.generateCode(variableDeclaration);\n\nconst metaplex = `\nconst metaplex = {\n  nfts: () => ({\n    create: ({name, sellerFeeBasisPoints, maxSupply}) => {\n        if (typeof name !== 'string') {\n            throw new Error('name must be a string');\n        }\n        if (typeof sellerFeeBasisPoints !== 'number') {\n            throw new Error('sellerFeeBasisPoints must be a number');\n        }\n        if (typeof maxSupply !== 'number') {\n            throw new Error('maxSupply must be a number');\n        } else {\n          if (maxSupply !== 1) {\n            throw new Error('maxSupply must be 1');\n          }\n        }\n        return Promise.resolve('createResponse');\n      }\n  })\n};\nlet name, sellerFeeBasisPoints, maxSupply, uri;\n`;\nconst testString = `${metaplex}\\n${codeString}`;\n\ntry {\n  await eval(`(async () => {${testString}})()`);\n} catch (e) {\n  assert.fail(e.message);\n}\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 29\n\n### --description--\n\nLog the `createResponse` variable to the console.\n\n### --tests--\n\nYou should have `console.log(createResponse);` in `create-nft.js`.\n\n```js\nconst consoleLogCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return (\n      c.callee.object?.name === 'console' && c.callee.property?.name === 'log'\n    );\n  });\nassert.exists(consoleLogCallExpression, 'A `console.log` call should exist');\nconst consoleLogArguments = consoleLogCallExpression.arguments;\nconst ident = consoleLogArguments.find(a => {\n  return a.name === 'createResponse';\n});\nassert.exists(\n  ident,\n  'One of the arguments to `console.log` should be `createResponse`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/create-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 30\n\n### --description--\n\nThe local storage driver points to a simple REST API that stores the metadata on your local machine. It is useful for testing purposes.\n\nStart the local storage driver in a new terminal:\n\n```bash\nnpm run start:server\n```\n\n### --tests--\n\nThe local storage driver should be running at `http://127.0.0.1:3001`.\n\n```js\ntry {\n  const res = await fetch('http://127.0.0.1:3001/status/ping');\n  // Response should be 200 with text \"pong\"\n  if (res.status === 200) {\n    const text = await res.text();\n    if (text !== 'pong') {\n      throw new Error(`Expected response text \"pong\", got ${text}`);\n    }\n  } else {\n    throw new Error(`Expected status code 200, got ${res.status}`);\n  }\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 31\n\n### --description--\n\nRun the script in the terminal:\n\n```bash\nnode create-nft.js\n```\n\n**Note:** You should see an error (similar to below) in the terminal.\n\n<details>\n  <summary>Error</summary>\n\n```bash\nFailedToSendTransactionError: The transaction could not be sent successfully to the network. Please check the underlying error below for more details.\n\nSource: RPC\n\nCaused By: Error: failed to send transaction: Transaction simulation failed: Attempt to load a program that does not exist\n```\n\n</details>\n\n### --tests--\n\nYou should run `node create-nft.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node create-nft.js',\n  'Run `node create-nft.js` in the terminal'\n);\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe local storage driver should be running at `http://127.0.0.1:3001`.\n\n```js\ntry {\n  const res = await fetch('http://127.0.0.1:3001/status/ping');\n  // Response should be 200 with text \"pong\"\n  if (res.status === 200) {\n    const text = await res.text();\n    if (text !== 'pong') {\n      throw new Error(`Expected response text \"pong\", got ${text}`);\n    }\n  } else {\n    throw new Error(`Expected status code 200, got ${res.status}`);\n  }\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 32\n\n### --description--\n\nThe error comes about because the default Solana test validator does not come with the Metaplex Token program deployed.\n\nSo, the program needs to be manually deployed to the local cluster. To do this, first a dump of the program from mainnet needs to be created:\n\n```bash\nsolana program dump --url mainnet-beta metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./mlp_token.so\n```\n\n<details>\n  <summary>About the Command</summary>\n\nThe above command:\n\n- Takes a dump of the program at the provided address\n- Uses the mainnet-beta cluster (the cluster that the Metaplex Token program is deployed to)\n- Outputs the dump to `mlp_token.so`\n\nThe address of the Metaplex Token program is `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s`. You can find the address of any program you want by searching for it on the Solana Explorer:\n\n`https://explorer.solana.com/address/metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s`\n\n</details>\n\n### --tests--\n\nYou should have a file called `mlp_token.so` in the root of the project.\n\n```js\nconst fileExists = __helpers.fileExists(\n  'learn-the-metaplex-sdk-by-minting-an-nft/mlp_token.so'\n);\nassert.isTrue(fileExists, 'A file called `mlp_token.so` should exist.');\n```\n\n## 33\n\n### --description--\n\nDeploy the Metaplex Token program to your local cluster:\n\n```bash\nsolana program deploy --keypair wallet.json ./mlp_token.so\n```\n\n### --tests--\n\nYou should run `solana program deploy ./mlp_token.so` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'solana program deploy --keypair wallet.json ./mlp_token.so',\n  'Run `solana program deploy ./mlp_token.so` in the terminal'\n);\n```\n\n## 34\n\n### --description--\n\nRe-run the script to create the NFT.\n\n**Note:** You should see an error in the terminal.\n\n### --tests--\n\nYou should run `node create-nft.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node create-nft.js',\n  'Run `node create-nft.js` in the terminal'\n);\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\n\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\n\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe local storage driver should be running at `http://127.0.0.1:3001`.\n\n```js\ntry {\n  const res = await fetch('http://127.0.0.1:3001/status/ping');\n  // Response should be 200 with text \"pong\"\n  if (res.status === 200) {\n    const text = await res.text();\n    if (text !== 'pong') {\n      throw new Error(`Expected response text \"pong\", got ${text}`);\n    }\n  } else {\n    throw new Error(`Expected status code 200, got ${res.status}`);\n  }\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 35\n\n### --description--\n\nThe error comes about because, internally, the Metaplex SDK is expecting a program address of `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s`. However, when you deployed the `.so` file, it was deployed at a random public address.\n\nInstead, you can start the local cluster with the program pre-deployed at a specific address:\n\n```bash\nsolana-test-validator --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./mlp_token.so --reset\n```\n\nStop your existing local cluster, and start a new one with the above command.\n\n<details>\n  <summary>About the Command</summary>\n\nThe `--bpf-program` flag is used to specify the address of the program. This is the same address that the program is deployed to on mainnet-beta. Internally, the Metaplex SDK uses this address in the transaction instructions to tell the cluster which program to use.\n\nThe `--reset` flag is used to clear the `test-ledger` directory. This is where the local cluster stores its data. If you don't clear the directory, the cluster will use the old data.\n\n</details>\n\n### --tests--\n\nYou should be in the `learn-the-metaplex-sdk-by-minting-an-nft` directory.\n\n```js\nconst cwdFile = await __helpers.getCWD();\nconst cwd = cwdFile.split('\\n').filter(Boolean).pop();\nassert.include(cwd, 'learn-the-metaplex-sdk-by-minting-an-nft');\n```\n\nYou should run `solana-test-validator --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./mlp_token.so --reset` in the terminal.\n\n```js\nawait new Promise(res => setTimeout(() => res(), 2000));\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '\n  {\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"getProgramAccounts\",\n    \"params\": [\n      \"BPFLoader2111111111111111111111111111111111\", {\n        \"encoding\": \"base64\",\n        \"dataSlice\": {\n          \"length\": 0,\n          \"offset\": 0\n        }\n      }\n    ]\n}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.exists(\n    jsonOut.result.find(\n      r => r.pubkey === 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'\n    )\n  );\n} catch (e) {\n  assert.fail(\n    e,\n    'Try running `solana-test-validator --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./mlp_token.so --reset`'\n  );\n}\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nawait new Promise(res => setTimeout(() => res(), 2000));\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 36\n\n### --description--\n\nFor ease, this command has been added to the `package.json` file as a script. Stop your existing local cluster, and start a new one with the following command:\n\n```bash\nnpm run start:validator\n```\n\n**Note:** You might need to manually run the tests.\n\n### --tests--\n\nYou should run `npm run start:validator` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getTemp();\nassert.include(\n  lastCommand,\n  'npm run start:validator',\n  'Run `npm run start:validator` in the terminal'\n);\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 37\n\n### --description--\n\nRestarting the validator with the `--reset` flag deletes any previous ledger data. This means you will need to airdrop some SOL into the account associated with `wallet.json` again.\n\nDo so.\n\n### --tests--\n\nThe `wallet.json` account should have at least 2 SOL.\n\n```js\nconst { stdout } = await __helpers.getCommandOutput(\n  `solana balance ./learn-the-metaplex-sdk-by-minting-an-nft/wallet.json`\n);\nconst balance = stdout.trim()?.match(/\\d+/)?.[0];\nassert.isAtLeast(\n  parseInt(balance),\n  2,\n  'Try running `solana airdrop 2 ./wallet.json`'\n);\n```\n\nThe validator should be running at `http://localhost:8899`.\n\n```js\nconst command = `curl http://localhost:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\n## 38\n\n### --description--\n\nCreate a new NFT again. Pay attention to the output in the terminal.\n\n### --tests--\n\nYou should run `node create-nft.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(\n  lastCommand?.trim(),\n  'node create-nft.js',\n  'Run `node create-nft.js` in the terminal'\n);\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe local storage driver should be running at `http://127.0.0.1:3001`.\n\n```js\ntry {\n  const res = await fetch('http://127.0.0.1:3001/status/ping');\n  // Response should be 200 with text \"pong\"\n  if (res.status === 200) {\n    const text = await res.text();\n    if (text !== 'pong') {\n      throw new Error(`Expected response text \"pong\", got ${text}`);\n    }\n  } else {\n    throw new Error(`Expected status code 200, got ${res.status}`);\n  }\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 39\n\n### --description--\n\nPart of the output should be the mint account's public key. Copy the base58 string and paste it into the `env.MINT_ACCOUNT_ADDRESS` property of the `package.json` file.\n\n_As this is a new NFT, the `env.MINT_ACCOUNT_ADDRESS` property will be different to the previous one._\n\n### --tests--\n\nThe `package.json` file should have a `env.MINT_ACCOUNT_ADDRESS` property.\n\n```js\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson,\n  'env',\n  'The `package.json` file should have an `env` property.'\n);\nassert.property(\n  packageJson.env,\n  'MINT_ACCOUNT_ADDRESS',\n  'The `package.json` file should have an `env.MINT_ACCOUNT_ADDRESS` property.'\n);\n```\n\nThe `env.MINT_ACCOUNT_ADDRESS` property should match an NFT owned by `wallet.json`.\n\n```js\nconst command =\n  'solana address -k learn-the-metaplex-sdk-by-minting-an-nft/wallet.json';\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\nconst walletAddress = stdout.trim();\n\nconst packageJson = JSON.parse(\n  await __helpers.getFile(\n    'learn-the-metaplex-sdk-by-minting-an-nft/package.json'\n  )\n);\nassert.property(\n  packageJson,\n  'env',\n  'The `package.json` file should have an `env` property.'\n);\n\nassert.equal(\n  packageJson.env.WALLET_ADDRESS,\n  walletAddress,\n  'The `env.WALLET_ADDRESS` property should match the public key of `wallet.json`.'\n);\n\ntry {\n  const { Connection } = await import('@solana/web3.js');\n  const { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');\n  const connection = new Connection('http://127.0.0.1:8899');\n\n  const mintAccounts = await connection.getParsedProgramAccounts(\n    TOKEN_PROGRAM_ID,\n    {\n      filters: [\n        {\n          dataSize: 165\n        },\n        {\n          memcmp: {\n            offset: 32,\n            bytes: packageJson.env.WALLET_ADDRESS\n          }\n        }\n      ]\n    }\n  );\n\n  const mintAccount = mintAccounts.find(\n    ({ account }) =>\n      account?.data?.parsed?.info?.mint === packageJson.env.MINT_ACCOUNT_ADDRESS\n  );\n  assert.exists(\n    mintAccount,\n    'The `env.MINT_ACCOUNT_ADDRESS` property should match an NFT owned by `wallet.json`.'\n  );\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 40\n\n### --description--\n\nNow that your NFT is deployed, and you have the mint account's public key, you can always find it using the public key.\n\nCreate a new file called `get-nft.js`.\n\n### --tests--\n\nYou should have a `get-nft.js` file.\n\n```js\nconst fileExists = __helpers.fileExists(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nassert.isTrue(fileExists, 'You should have a `get-nft.js` file.');\n```\n\n## 41\n\n### --description--\n\nWithin `get-nft.js`, declare a `connection` variable with a configuration pointing to your local cluster.\n\n### --tests--\n\nYou should have `const connection = new Connection('http://127.0.0.1:8899');` in `get-nft.js`.\n\n```js\nconst connectionVariableDeclaration = babelisedCode\n  .getVariableDeclarations()\n  .find(v => v.declarations?.[0]?.id?.name === 'connection');\nassert.exists(\n  connectionVariableDeclaration,\n  'You should declare a variable named `connection`'\n);\nconst newExpression = connectionVariableDeclaration.declarations[0].init;\nassert.equal(\n  newExpression.callee.name,\n  'Connection',\n  'You should initialise `connection` with a new `Connection`'\n);\nassert.equal(\n  newExpression.arguments[0].value,\n  'http://127.0.0.1:8899',\n  \"You should create a new connection with `new Connection('http://127.0.0.1:8899')`\"\n);\n```\n\nYou should import `Connection` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `@solana/web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'Connection',\n  '`Connection` should be imported from `@solana/spl-token`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 42\n\n### --description--\n\nWithin `get-nft.js`, declare a `metaplex` variable with the same configuration as the `metaplex` variable in `create-nft.js`.\n\n### --tests--\n\nYou should have `const metaplex = Metaplex.make(connection).use(keypairIdentity(WALLET_KEYPAIR)).use(localStorage({ baseUrl: 'http://127.0.0.1:3001/' }));` in `get-nft.js`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'metaplex';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /metaplex=Metaplex\\.make\\(connection\\)\\.use\\(keypairIdentity\\(WALLET_KEYPAIR\\)\\)\\.use\\(localStorage\\(\\{('|\"|`)?baseUrl\\1:('|\"|`)http:\\/\\/127\\.0\\.0\\.1:3001\\/\\2\\}\\)\\)/\n);\n```\n\nYou should import `Metaplex` from `@metaplex-foundation/js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@metaplex-foundation/js';\n});\nassert.exists(\n  importDeclaration,\n  'You should import from `@metaplex-foundation/js`'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'Metaplex',\n  '`Metaplex` should be imported from `@metaplex-foundation/js`'\n);\n```\n\nYou should import `keypairIdentity` from `@metaplex-foundation/js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@metaplex-foundation/js';\n});\nassert.exists(\n  importDeclaration,\n  'You should import from `@metaplex-foundation/js`'\n);\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'keypairIdentity',\n  '`keypairIdentity` should be imported from `@metaplex-foundation/js`'\n);\n```\n\nYou should import `WALLET_KEYPAIR` from `utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'You should import from `./utils.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'WALLET_KEYPAIR',\n  '`WALLET_KEYPAIR` should be imported from `./utils.js`'\n);\n```\n\nYou should import `localStorage` from `utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'You should import from `./utils.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'localStorage',\n  '`localStorage` should be imported from `./utils.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 43\n\n### --description--\n\nWithin `get-nft.js`, declare a `mintAddress` variable, and assign it the value of an instance of `PublicKey` constructed from the `MINT_ACCOUNT_ADDRESS` property from `pkg.env`.\n\n**Hint:** The `pkg` object is exported from `utils.js`.\n\n### --tests--\n\nYou should have `const mintAddress = new PublicKey(pkg.env.MINT_ACCOUNT_ADDRESS);` in `get-nft.js`.\n\n```js\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'mintAddress';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /mintAddress=new PublicKey\\(pkg\\.env\\.MINT_ACCOUNT_ADDRESS\\)/\n);\n```\n\nYou should import `pkg` from `utils.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === './utils.js';\n});\nassert.exists(importDeclaration, 'You should import from `./utils.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'pkg',\n  '`pkg` should be imported from `./utils.js`'\n);\n```\n\nYou should import `PublicKey` from `@solana/web3.js`.\n\n```js\nconst importDeclaration = babelisedCode.getImportDeclarations().find(i => {\n  return i.source.value === '@solana/web3.js';\n});\nassert.exists(importDeclaration, 'You should import from `@solana/web3.js`');\nconst importSpecifiers = importDeclaration.specifiers.map(s => s.imported.name);\nassert.include(\n  importSpecifiers,\n  'PublicKey',\n  '`PublicKey` should be imported from `@solana/web3.js`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 44\n\n### --description--\n\nTo get the NFT, use the `findByMint` method on the `Metaplex.nfts()` class:\n\n```typescript\nfindByMint(\n  {\n    mintAddress: PublicKey;\n    tokenAddress?: PublicKey;\n    tokenOwner?: PublicKey;\n    loadJsonMetadata?: boolean;\n  }\n): Promise\n```\n\nPass in the required `mintAddress` property, and assign the awaited result to an `nft` variable.\n\n### --tests--\n\nYou should have `const nft = await metaplex.nfts().findByMint({ mintAddress });` in `get-nft.js`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'nft';\n});\nassert.exists(variableDeclaration, 'An `nft` variable should exist');\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /nft=await metaplex\\.nfts\\(\\)\\.findByMint\\(\\{mintAddress\\}\\)/\n);\n```\n\n## 45\n\n### --description--\n\nLog the `nft` variable to the console.\n\n### --tests--\n\nYou should have `console.log(nft);` in `get-nft.js`.\n\n```js\nconst consoleLogCallExpression = babelisedCode\n  .getType('CallExpression')\n  .find(c => {\n    return (\n      c.callee.object?.name === 'console' && c.callee.property?.name === 'log'\n    );\n  });\nassert.exists(consoleLogCallExpression, 'A `console.log` call should exist');\nconst consoleLogArguments = consoleLogCallExpression.arguments;\nconst ident = consoleLogArguments.find(a => {\n  return a.name === 'nft';\n});\nassert.exists(ident, 'One of the arguments to `console.log` should be `nft`');\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 46\n\n### --description--\n\nRun the `get-nft.js` script.\n\n```bash\nnode get-nft.js\n```\n\n### --tests--\n\nYou should run `node get-nft.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(lastCommand?.trim(), 'node get-nft.js');\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe local storage driver should be running at `http://127.0.0.1:3001`.\n\n```js\ntry {\n  const res = await fetch('http://127.0.0.1:3001/status/ping');\n  // Response should be 200 with text \"pong\"\n  if (res.status === 200) {\n    const text = await res.text();\n    if (text !== 'pong') {\n      throw new Error(`Expected response text \"pong\", got ${text}`);\n    }\n  } else {\n    throw new Error(`Expected status code 200, got ${res.status}`);\n  }\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 47\n\n### --description--\n\nNotice the output includes a `json` property. This is the metadata for the NFT.\n\nThe metadata includes the image URL, but not the image data itself. To get the image data, you can use the `download` method on the `Metaplex.storage()` class:\n\n```typescript\ndownload(\n  uri: string\n): Promise<MetaplexFile>\n```\n\nDeclare a variable `imageData`, and assign it the awaited result of calling the `download` method, passing in the `image` property of the NFT metadata.\n\n### --tests--\n\nYou should have `const imageData = await metaplex.storage().download(nft.json.image);` in `get-nft.js`.\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nconst variableDeclaration = babelisedCode.getVariableDeclarations().find(v => {\n  return v.declarations?.[0]?.id?.name === 'imageData';\n});\nconst minifiedCode = babelisedCode.generateCode(variableDeclaration, {\n  minified: true\n});\nassert.match(\n  minifiedCode,\n  /imageData=await metaplex\\.storage\\(\\)\\.download\\(nft\\.json\\.image\\)/\n);\n```\n\n## 48\n\n### --description--\n\nLog the `imageData` variable to the console.\n\n### --tests--\n\nYou should have `console.log(imageData);` in `get-nft.js`.\n\n```js\nconst consoleLogCallExpressions = babelisedCode\n  .getType('CallExpression')\n  .filter(c => {\n    return (\n      c.callee.object?.name === 'console' && c.callee.property?.name === 'log'\n    );\n  });\nassert.exists(consoleLogCallExpressions, 'A `console.log` call should exist');\nconst is_ident = consoleLogCallExpressions.some(c => {\n  const consoleLogArguments = c.arguments;\n  const ident = consoleLogArguments.find(a => {\n    return a.name === 'imageData';\n  });\n  return ident;\n});\nassert.exists(\n  is_ident,\n  'One of the arguments to `console.log` should be `imageData`'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 49\n\n### --description--\n\nRun the `get-nft.js` script.\n\n### --tests--\n\nYou should run `node get-nft.js` in the terminal.\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.equal(lastCommand?.trim(), 'node get-nft.js');\n```\n\nThe validator should be running at `http://127.0.0.1:8899`.\n\n```js\nconst command = `curl http://127.0.0.1:8899 -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"id\":1, \"method\":\"getHealth\"}'`;\nconst { stdout, stderr } = await __helpers.getCommandOutput(command);\ntry {\n  const jsonOut = JSON.parse(stdout);\n  assert.deepInclude(jsonOut, { result: 'ok' });\n} catch (e) {\n  assert.fail(e, 'Try running `solana-test-validator` in a separate terminal');\n}\n```\n\nThe local storage driver should be running at `http://127.0.0.1:3001`.\n\n```js\ntry {\n  const res = await fetch('http://127.0.0.1:3001/status/ping');\n  // Response should be 200 with text \"pong\"\n  if (res.status === 200) {\n    const text = await res.text();\n    if (text !== 'pong') {\n      throw new Error(`Expected response text \"pong\", got ${text}`);\n    }\n  } else {\n    throw new Error(`Expected status code 200, got ${res.status}`);\n  }\n} catch (e) {\n  assert.fail(e);\n}\n```\n\n## 50\n\n### --description--\n\nNow, with the buffer data, use the `writeFile` function from the `fs/promises` module to reconstruct the image file. Re-run the script to generate the image file.\n\n### --tests--\n\nYou should have `await writeFile(<file_name>, imageData.buffer);` in `get-nft.js`.\n\n```js\nconst expressionStatement = babelisedCode.getExpressionStatements().find(e => {\n  return e.expression?.argument?.callee?.name === 'writeFile';\n});\nassert.exists(expressionStatement, 'An `await writeFile()` call should exist');\nconst [fileName, imageBufferMemberExpression] =\n  expressionStatement.expression.argument.arguments;\nassert.isString(fileName?.value, 'The first argument should be a string');\nassert.equal(\n  imageBufferMemberExpression?.object?.name,\n  'imageData',\n  'The second argument should be `imageData.buffer`'\n);\nassert.equal(\n  imageBufferMemberExpression?.property?.name,\n  'buffer',\n  'The second argument should be `imageData.buffer`'\n);\n```\n\nYou should have a `.png` file in the project root.\n\n```js\nconst dir = await __helpers.getDirectory(\n  'learn-the-metaplex-sdk-by-minting-an-nft'\n);\nassert.exists(\n  dir.find(f => f.endsWith('.png')),\n  'A .png file should exist in the project root'\n);\n```\n\n### --before-all--\n\n```js\nconst codeString = await __helpers.getFile(\n  'learn-the-metaplex-sdk-by-minting-an-nft/get-nft.js'\n);\nconst babelisedCode = new __helpers.Babeliser(codeString);\nglobal.babelisedCode = babelisedCode;\n```\n\n### --after-all--\n\n```js\ndelete global.babelisedCode;\n```\n\n## 51\n\n### --description--\n\nContratulations on finishing this project! Feel free to play with your code.\n\n**Summary**\n\nThe main differences between NFT's and fungible tokens are:\n\n- NFT's are not divisible\n- Only one NFT of a mint can exist\n- No more NFT's can be minted once the mint authority is removed\n\nThe Metaplex SDK provides a simple interface for minting NFT's and managing the metadata associated with them:\n\n```javascript\n// Create a new Metaplex instance\nconst metaplex = Metaplex.make(connection);\n// Attach a wallet to use for transactions\nmetaplex.use(keypairIdentity(wallet_keypair));\n// Attach a storage driver to use for uploading and downloading metadata\nmetaplex.use(storage_driver);\n\n// Create a MetaplexFile\nconst file = toMetaplexFile(image_buffer);\n// Upload the file to the storage driver\nconst image_uri = await metaplex.storage().upload(file);\n// Upload the metadata to the storage driver\nconst { uri } = await metaplex.nfts().uploadMetadata({\n  name: 'fCC',\n  description: 'An image of the freeCodeCamp logo',\n  image: image_uri\n});\n// Create a new NFT mint\nconst nftInfo = await metaplex.nfts().create({\n  name: 'fCC',\n  uri,\n  sellerFeeBasisPoints: 1000,\n  maxSupply: 1\n});\n\n// Get nft data\nconst nft = await metaplex.nfts().findByMint({\n  mintAddress: nftInfo.mintAddress\n});\n// Download image from storage driver\nconst image = await metaplex.storage().download(nft.json.image);\n```\n\n🎆\n\nOnce you are done, enter `done` in the terminal.\n\n### --tests--\n\nYou should enter `done` in the terminal\n\n```js\nconst lastCommand = await __helpers.getLastCommand();\nassert.include(lastCommand, 'done');\n```\n\n## --fcc-end--\n"
  },
  {
    "path": "freecodecamp.conf.json",
    "content": "{\n  \"path\": \".\",\n  \"version\": \"1.0.3\",\n  \"scripts\": {\n    \"develop-course\": \"NODE_ENV=development node ./node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/server.js\",\n    \"run-course\": \"NODE_ENV=production node ./node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/server.js\",\n    \"test\": {\n      \"functionName\": \"handleMessage\",\n      \"arguments\": [\n        {\n          \"message\": \"Hello World!\",\n          \"type\": \"info\"\n        }\n      ]\n    }\n  },\n  \"workspace\": {\n    \"previews\": [\n      {\n        \"open\": true,\n        \"url\": \"http://localhost:8080\",\n        \"showLoader\": true,\n        \"timeout\": 4000\n      }\n    ]\n  },\n  \"bash\": {\n    \".bashrc\": \"./bash/.bashrc\",\n    \"sourcerer.sh\": \"./bash/sourcerer.sh\"\n  },\n  \"client\": {\n    \"assets\": {\n      \"header\": \"./client/assets/fcc_primary_large.svg\",\n      \"favicon\": \"./client/assets/fcc_primary_small.svg\"\n    },\n    \"landing\": {\n      \"description\": \"Learn the Solana toolchain, and test your new-found skills.\",\n      \"faq-link\": \"#\",\n      \"faq-text\": \"https://github.com/freeCodeCamp/solana-curriculum/issues\"\n    },\n    \"static\": {\n      \"/images/phantom\": \"./curriculum/images/phantom\",\n      \"/images/devnet\": \"./curriculum/images/devnet\"\n    }\n  },\n  \"config\": {\n    \"projects.json\": \"./config/projects.json\",\n    \"state.json\": \"./config/state.json\"\n  },\n  \"curriculum\": {\n    \"locales\": {\n      \"english\": \"./curriculum/locales/english\"\n    }\n  },\n  \"tooling\": {\n    \"helpers\": \"./tooling/helpers.js\"\n  }\n}\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-1/.gitkeep",
    "content": ""
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/.prettierignore",
    "content": "\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",
    "content": "[features]\nseeds = false\nskip-lint = false\n[programs.localnet]\ntic_tac_toe = \"BUfb6FXLkiSpMnJnMR4Q5uGZYZkaNGytjhLwiiJQsE8F\"\n\n[registry]\nurl = \"https://api.apr.dev\"\n\n[provider]\ncluster = \"Localnet\"\nwallet = \"~/.config/solana/id.json\"\n\n[scripts]\ntest = \"yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts\"\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[profile.release.build-override]\nopt-level = 3\nincremental = false\ncodegen-units = 1\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/migrations/deploy.ts",
    "content": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked from the CLI, injecting a provider\n// configured from the workspace's Anchor.toml.\n\nconst anchor = require(\"@coral-xyz/anchor\");\n\nmodule.exports = async function (provider) {\n  // Configure client to use the provider.\n  anchor.setProvider(provider);\n\n  // Add your deploy script here.\n};\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/package.json",
    "content": "{\n    \"scripts\": {\n        \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n        \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" --check\"\n    },\n    \"dependencies\": {\n        \"@coral-xyz/anchor\": \"^0.27.0\"\n    },\n    \"devDependencies\": {\n        \"chai\": \"^4.3.4\",\n        \"mocha\": \"^9.0.3\",\n        \"ts-mocha\": \"^10.0.0\",\n        \"@types/bn.js\": \"^5.1.0\",\n        \"@types/chai\": \"^4.3.0\",\n        \"@types/mocha\": \"^9.0.0\",\n        \"typescript\": \"^4.3.5\",\n        \"prettier\": \"^2.6.2\"\n    }\n}\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/programs/tic-tac-toe/Cargo.toml",
    "content": "[package]\nname = \"tic-tac-toe\"\nversion = \"0.1.0\"\ndescription = \"Created with Anchor\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\nname = \"tic_tac_toe\"\n\n[features]\nno-entrypoint = []\nno-idl = []\nno-log-ix-name = []\ncpi = [\"no-entrypoint\"]\ndefault = []\n\n[dependencies]\nanchor-lang = \"0.27.0\"\nnum-traits = \"0.2\"\nnum-derive = \"0.3\"\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/programs/tic-tac-toe/Xargo.toml",
    "content": "[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",
    "content": "use anchor_lang::prelude::*;\nuse num_derive;\nuse num_traits::FromPrimitive;\n\ndeclare_id!(\"BUfb6FXLkiSpMnJnMR4Q5uGZYZkaNGytjhLwiiJQsE8F\");\n\n#[program]\npub mod tic_tac_toe {\n    use super::*;\n\n    pub fn setup_game(\n        ctx: Context<SetupGame>,\n        player_two_pubkey: Pubkey,\n        _game_id: String,\n    ) -> Result<()> {\n        let player_one = &ctx.accounts.player_one;\n        let player_one_pubkey = player_one.key();\n\n        let game = &mut ctx.accounts.game;\n\n        game.start([player_one_pubkey, player_two_pubkey])\n    }\n\n    pub fn play(ctx: Context<Play>, tile: Tile) -> Result<()> {\n        let game = &mut ctx.accounts.game;\n\n        require_keys_eq!(\n            game.current_player(),\n            ctx.accounts.player.key(),\n            TicTacToeError::NotPlayersTurn\n        );\n\n        game.play(&tile)\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(player_two_pubkey: Pubkey, _game_id: String)]\npub struct SetupGame<'info> {\n    #[account(\n        init,\n        payer = player_one,\n        space = 8 + Game::MAXIMUM_SIZE,\n        seeds = [b\"game\", player_one.key().as_ref(), _game_id.as_bytes()],\n        bump\n    )]\n    pub game: Account<'info, Game>,\n    #[account(mut)]\n    pub player_one: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct Game {\n    players: [Pubkey; 2],          // (32 * 2)\n    turn: u8,                      // 1\n    board: [[Option<Sign>; 3]; 3], // 9 * (1 + 1) = 18\n    state: GameState,              // 32 + 1\n}\n\n#[derive(Accounts)]\npub struct Play<'info> {\n    #[account(mut)]\n    pub game: Account<'info, Game>,\n    pub player: Signer<'info>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]\npub enum GameState {\n    Active,\n    Tie,\n    Won { winner: Pubkey },\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, num_derive::FromPrimitive, Copy, Clone, PartialEq)]\npub enum Sign {\n    X,\n    O,\n}\n\nimpl Game {\n    pub const MAXIMUM_SIZE: usize = (32 * 2) + 1 + (9 * (1 + 1)) + (32 + 1);\n\n    pub fn start(&mut self, players: [Pubkey; 2]) -> Result<()> {\n        require_eq!(self.turn, 0, TicTacToeError::GameAlreadyStarted);\n        self.players = players;\n        self.turn = 1;\n        Ok(())\n    }\n\n    pub fn is_active(&self) -> bool {\n        self.state == GameState::Active\n    }\n\n    fn current_player_index(&self) -> usize {\n        ((self.turn - 1) % 2) as usize\n    }\n\n    pub fn current_player(&self) -> Pubkey {\n        self.players[self.current_player_index()]\n    }\n\n    pub fn play(&mut self, tile: &Tile) -> Result<()> {\n        require!(self.is_active(), TicTacToeError::GameAlreadyOver);\n\n        match tile {\n            tile @ Tile {\n                row: 0..=2,\n                column: 0..=2,\n            } => match self.board[tile.row as usize][tile.column as usize] {\n                Some(_) => return Err(TicTacToeError::TileAlreadySet.into()),\n                None => {\n                    self.board[tile.row as usize][tile.column as usize] =\n                        Some(Sign::from_usize(self.current_player_index()).unwrap());\n                }\n            },\n            _ => return Err(TicTacToeError::TileOutOfBounds.into()),\n        }\n\n        self.update_state();\n\n        if GameState::Active == self.state {\n            self.turn += 1;\n        }\n\n        Ok(())\n    }\n\n    fn is_winning_trio(&self, trio: [(usize, usize); 3]) -> bool {\n        let [first, second, third] = trio;\n        self.board[first.0][first.1].is_some()\n            && self.board[first.0][first.1] == self.board[second.0][second.1]\n            && self.board[first.0][first.1] == self.board[third.0][third.1]\n    }\n\n    fn update_state(&mut self) {\n        for i in 0..=2 {\n            // three of the same in one row\n            if self.is_winning_trio([(i, 0), (i, 1), (i, 2)]) {\n                self.state = GameState::Won {\n                    winner: self.current_player(),\n                };\n                return;\n            }\n            // three of the same in one column\n            if self.is_winning_trio([(0, i), (1, i), (2, i)]) {\n                self.state = GameState::Won {\n                    winner: self.current_player(),\n                };\n                return;\n            }\n        }\n\n        // three of the same in one diagonal\n        if self.is_winning_trio([(0, 0), (1, 1), (2, 2)])\n            || self.is_winning_trio([(0, 2), (1, 1), (2, 0)])\n        {\n            self.state = GameState::Won {\n                winner: self.current_player(),\n            };\n            return;\n        }\n\n        // reaching this code means the game has not been won,\n        // so if there are unfilled tiles left, it's still active\n        for row in 0..=2 {\n            for column in 0..=2 {\n                if self.board[row][column].is_none() {\n                    return;\n                }\n            }\n        }\n\n        // game has not been won\n        // game has no more free tiles\n        // -> game ends in a tie\n        self.state = GameState::Tie;\n    }\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize)]\npub struct Tile {\n    row: u8,\n    column: u8,\n}\n\n#[error_code]\npub enum TicTacToeError {\n    TileOutOfBounds,\n    TileAlreadySet,\n    GameAlreadyOver,\n    NotPlayersTurn,\n    GameAlreadyStarted,\n}\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/tests/tic-tac-toe.ts",
    "content": "import {\n  AnchorProvider,\n  workspace,\n  setProvider,\n  Program\n} from '@coral-xyz/anchor';\nimport { TicTacToe } from '../target/types/tic_tac_toe';\n\ndescribe('TicTacToe', () => {\n  // Configure the client to use the local cluster.\n  setProvider(AnchorProvider.env());\n\n  const program = workspace.TicTacToe as Program<TicTacToe>;\n\n  it('Is initialized!', async () => {\n    // Add your test here.\n    const tx = await program.methods.initialize().rpc();\n    console.log('Your transaction signature', tx);\n  });\n});\n"
  },
  {
    "path": "learn-anchor-by-building-tic-tac-toe-part-2/tic-tac-toe/tsconfig.json",
    "content": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules/@types\"],\n              \"lib\": [\"es2015\"],\n              \"module\": \"commonjs\",\n              \"target\": \"es6\",\n              \"esModuleInterop\": true\n            }\n          }\n          "
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/.gitkeep",
    "content": ""
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/.gitignore",
    "content": "\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",
    "content": "[features]\nseeds = false\nskip-lint = false\n[programs.localnet]\ntic_tac_toe = \"5xGwZASoE5ZgxKgaisJNaGTGzMKzjyyBGv9FCUtu2m1c\"\n\n[registry]\nurl = \"https://api.apr.dev\"\n\n[provider]\ncluster = \"Localnet\"\nwallet = \"~/.config/solana/id.json\"\n\n[scripts]\ntest = \"yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts\"\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[profile.release.build-override]\nopt-level = 3\nincremental = false\ncodegen-units = 1\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/index.css",
    "content": "body {\n  background: black;\n  width: 100%;\n  height: 100vh;\n  margin: 0;\n  padding: 0;\n}\n\nform,\ntable {\n  color: whitesmoke;\n  margin: 1em auto;\n  text-align: center;\n}\n\nform > fieldset > button:hover {\n  cursor: pointer;\n  background-color: #cd3737;\n}\n\ntbody > tr {\n  border: solid 1px whitesmoke;\n}\n\ntd {\n  width: 100px;\n  height: 100px;\n  text-align: center;\n  vertical-align: middle;\n  font-size: 50px;\n  border: solid 1px whitesmoke;\n}\n\ntd:hover {\n  cursor: pointer;\n  background-color: #4b8270;\n}\n\n#errors {\n  color: red;\n  font-size: 1.5em;\n  margin: 0 auto;\n  text-align: center;\n}\n\n#keypairs {\n  color: greenyellow;\n}\n\n/* Wrap text in #keypairs > li */\n#keypairs > li > pre {\n  text-wrap: wrap;\n  word-wrap: break-word;\n  color: #2ca08d;\n  background-color: #2e3136;\n  margin: 1rem;\n  padding: 0.5rem;\n}\n\n#spinner {\n  margin: 0 auto;\n  text-align: center;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}\n\n.lds-ripple {\n  display: inline-block;\n  position: relative;\n  width: 80px;\n  height: 80px;\n}\n\n.lds-ripple div {\n  position: absolute;\n  border: 4px solid #fff;\n  opacity: 1;\n  border-radius: 50%;\n  animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;\n}\n\n.lds-ripple div:nth-child(2) {\n  animation-delay: -0.5s;\n}\n\n@keyframes lds-ripple {\n  0% {\n    top: 36px;\n    left: 36px;\n    width: 0;\n    height: 0;\n    opacity: 1;\n  }\n  100% {\n    top: 0px;\n    left: 0px;\n    width: 72px;\n    height: 72px;\n    opacity: 0;\n  }\n}\n\n.hidden {\n  display: none;\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/index.html",
    "content": "<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link href=\"./index.css\" type=\"text/css\" rel=\"stylesheet\" />\n    <title>Tic-Tac-Toe</title>\n  </head>\n  <body>\n    <form>\n      <label> Keypair:<input type=\"text\" id=\"keypair\" /> </label>\n      <button id=\"connect-wallet\" formaction=\"#\">Connect Wallet</button>\n    </form>\n    <form>\n      <label>New Game ID: <input type=\"text\" id=\"game-id\" /></label>\n      <label>\n        Player One Public Key:\n        <input\n          type=\"text\"\n          required\n          placeholder=\"e.g. AxfbN...\"\n          id=\"player-one-public-key\"\n        />\n      </label>\n      <label>\n        Player Two Public Key:\n        <input\n          type=\"text\"\n          required\n          placeholder=\"e.g. AxfbN...\"\n          id=\"player-two-public-key\"\n        />\n      </label>\n      <label>\n        Game Public Key: <input type=\"text\" id=\"game-public-key\" />\n      </label>\n      <fieldset>\n        <button id=\"start-game\" formaction=\"#\">Start Game</button>\n        <p>OR</p>\n        <button id=\"join-game\" formaction=\"#\">Join Game</button>\n      </fieldset>\n    </form>\n    <table>\n      <thead>\n        <tr>\n          <th>Turn:</th>\n          <th id=\"turn\"></th>\n        </tr>\n        <tr>\n          <th>Player:</th>\n          <th id=\"player-turn\"></th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <td id=\"0\"></td>\n          <td id=\"1\"></td>\n          <td id=\"2\"></td>\n        </tr>\n        <tr>\n          <td id=\"3\"></td>\n          <td id=\"4\"></td>\n          <td id=\"5\"></td>\n        </tr>\n        <tr>\n          <td id=\"6\"></td>\n          <td id=\"7\"></td>\n          <td id=\"8\"></td>\n        </tr>\n      </tbody>\n    </table>\n    <section>\n      <p id=\"errors\"></p>\n      <ul id=\"keypairs\"></ul>\n    </section>\n    <div id=\"spinner\" class=\"hidden\">\n      <div class=\"lds-ripple\">\n        <div></div>\n        <div></div>\n      </div>\n    </div>\n  </body>\n  <script src=\"./index.js\" type=\"module\"></script>\n</html>\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/index.js",
    "content": "import { Keypair, PublicKey } from '@solana/web3.js';\nimport player_one_keypair from '../player-one.json';\nimport player_two_keypair from '../player-two.json';\nimport { displayError, removeLoader, showLoader } from './utils';\nimport { PROGRAM_ID, deriveGamePublicKey } from './web3';\n\nconst gameIdEl = document.getElementById('game-id');\nconst startGameBtnEl = document.getElementById('start-game');\nconst tableBodyEl = document.querySelector('tbody');\nconst tdEls = tableBodyEl.querySelectorAll('td');\nconst joinGameBtnEl = document.getElementById('join-game');\nconst playerOnePublicKeyEl = document.getElementById('player-one-public-key');\nconst playerTwoPublicKeyEl = document.getElementById('player-two-public-key');\nconst connectWalletBtnEl = document.getElementById('connect-wallet');\nconst keypairEl = document.getElementById('keypair');\nconst keypairsEl = document.getElementById('keypairs');\n\nconnectWalletBtnEl.addEventListener('click', ev => {\n  ev.preventDefault();\n  displayError();\n  showLoader();\n  try {\n    const keypair = keypairEl.value;\n    sessionStorage.setItem('keypair', keypair);\n\n    // TODO: Connect to wallet\n\n    connectWalletBtnEl.style.backgroundColor = 'green';\n  } catch (e) {\n    displayError(e);\n  } finally {\n    removeLoader();\n  }\n});\n\nstartGameBtnEl.addEventListener('click', async event => {\n  event.preventDefault();\n  displayError();\n  showLoader();\n  try {\n    // TODO: Create a new game\n  } catch (e) {\n    displayError(e);\n  } finally {\n    removeLoader();\n  }\n});\n\njoinGameBtnEl.addEventListener('click', async event => {\n  event.preventDefault();\n  displayError();\n  showLoader();\n  try {\n    // TODO: Join an existing game\n  } catch (e) {\n    displayError(e);\n  } finally {\n    removeLoader();\n  }\n});\n\ntdEls.forEach(tdEl => {\n  tdEl.addEventListener('click', async event => {\n    event.preventDefault();\n    displayError();\n    showLoader();\n    try {\n      // TODO: Play tile\n    } catch (e) {\n      displayError(e);\n    } finally {\n      removeLoader();\n    }\n  });\n});\n\ndocument.addEventListener('DOMContentLoaded', async _event => {\n  startWithPossibleValues();\n\n  const interval = setInterval(async () => {\n    showLoader();\n    try {\n      // TODO: If program and gamePublicKey exist, update board\n    } catch (e) {\n      console.debug(e);\n    } finally {\n      removeLoader();\n    }\n  }, 3000);\n\n  // A game of tic-tac-toe should not last long,\n  // but for development this is commented out\n  // setTimeout(() => {\n  //   clearInterval(interval);\n  // }, 300_000);\n  return () => {\n    clearInterval(interval);\n  };\n});\n\n// ---------------------\n// CONVENIENCE FUNCTIONS\n// ---------------------\n\nfunction startWithPossibleValues() {\n  const player_one_publicKey = Keypair.fromSecretKey(\n    new Uint8Array(player_one_keypair)\n  ).publicKey.toBase58();\n  const player_two_publicKey = Keypair.fromSecretKey(\n    new Uint8Array(player_two_keypair)\n  ).publicKey.toBase58();\n\n  playerOnePublicKeyEl.value = player_one_publicKey;\n  playerTwoPublicKeyEl.value = player_two_publicKey;\n\n  const keypairs = [player_one_keypair, player_two_keypair];\n  keypairs.forEach((keypair, i) => {\n    const li = document.createElement('li');\n    const code = document.createElement('code');\n    const pre = document.createElement('pre');\n    pre.appendChild(code);\n    code.textContent = JSON.stringify(keypair);\n    li.textContent = `Player ${i + 1} Public Key: `;\n\n    li.appendChild(pre);\n\n    keypairsEl.appendChild(li);\n  });\n}\n\nplayerOnePublicKeyEl.addEventListener('change', e => {\n  const playerOnePublicKey = e.target.value;\n  sessionStorage.setItem('playerOnePublicKey', playerOnePublicKey);\n});\n\nplayerTwoPublicKeyEl.addEventListener('change', e => {\n  const playerTwoPublicKey = e.target.value;\n  sessionStorage.setItem('playerTwoPublicKey', playerTwoPublicKey);\n});\n\ngameIdEl.addEventListener('change', e => {\n  const gameId = e.target.value;\n  sessionStorage.setItem('gameId', gameId);\n  const gamePublicKey = deriveGamePublicKey(\n    new PublicKey(playerOnePublicKeyEl.value),\n    gameId,\n    PROGRAM_ID\n  );\n  sessionStorage.setItem('gamePublicKey', gamePublicKey.toBase58());\n});\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/package.json",
    "content": "{\n  \"name\": \"app\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"4.5.6\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/utils.js",
    "content": "const errorsEl = document.getElementById('errors');\nconst spinnerEl = document.getElementById('spinner');\n\nconst tableBodyEl = document.querySelector('tbody');\nconst tdEls = tableBodyEl.querySelectorAll('td');\n\n/**\n * @param {{ x: {} } | { o: {} } | null} tile\n * @returns 'X' | 'O' | ''\n */\nexport function tileToString(tile) {\n  if (tile?.x) {\n    return 'X';\n  }\n  if (tile?.o) {\n    return 'O';\n  }\n  return '';\n}\n\n/**\n * @param {({ x: {} } | { o: {} } | null)[][]} board\n */\nexport function setTiles(board) {\n  for (let i = 0; i < 3; i++) {\n    for (let j = 0; j < 3; j++) {\n      const tile = board[i][j];\n      const tileEl = tdEls[i * 3 + j];\n      tileEl.textContent = tileToString(tile);\n    }\n  }\n}\n\n/**\n * @param {Error | undefined} error\n */\nexport function displayError(error) {\n  if (error) {\n    console.error(error);\n    errorsEl.innerText = error.message;\n  } else {\n    errorsEl.innerText = '';\n  }\n}\n\n/**\n * @param {number} id\n * @returns {{ row: number; column: number }}\n */\nexport function idToTile(id) {\n  for (let i = 0; i < 3; i++) {\n    for (let j = 0; j < 3; j++) {\n      const tile = tdEls[i * 3 + j];\n      if (tile.id === id) {\n        return { row: i, column: j };\n      }\n    }\n  }\n}\n\nexport function showLoader() {\n  spinnerEl.classList.remove('hidden');\n}\n\nexport function removeLoader() {\n  spinnerEl.classList.add('hidden');\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/app/wallet.js",
    "content": "export class Wallet {\n  constructor(keypair) {\n    this.keypair = keypair;\n    this._publicKey = keypair.publicKey;\n  }\n  async signTransaction(tx) {\n    tx.sign(this.keypair);\n    return tx;\n  }\n  async signAllTransactions(txs) {\n    txs.map(tx => tx.sign(this.keypair));\n    return txs;\n  }\n  get publicKey() {\n    return this._publicKey;\n  }\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/migrations/deploy.ts",
    "content": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked from the CLI, injecting a provider\n// configured from the workspace's Anchor.toml.\n\nconst anchor = require(\"@coral-xyz/anchor\");\n\nmodule.exports = async function (provider) {\n  // Configure client to use the provider.\n  anchor.setProvider(provider);\n\n  // Add your deploy script here.\n};\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/package.json",
    "content": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" --check\"\n  },\n  \"dependencies\": {\n    \"@coral-xyz/anchor\": \"^0.28.0\"\n  },\n  \"devDependencies\": {\n    \"@types/bn.js\": \"^5.1.0\",\n    \"@types/chai\": \"^4.3.0\",\n    \"@types/mocha\": \"^9.0.0\",\n    \"chai\": \"^4.3.4\",\n    \"mocha\": \"^9.0.3\",\n    \"ts-mocha\": \"^10.0.0\",\n    \"typescript\": \"^4.3.5\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/programs/tic-tac-toe/Cargo.toml",
    "content": "[package]\nname = \"tic-tac-toe\"\nversion = \"0.1.0\"\ndescription = \"Created with Anchor\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\nname = \"tic_tac_toe\"\n\n[features]\nno-entrypoint = []\nno-idl = []\nno-log-ix-name = []\ncpi = [\"no-entrypoint\"]\ndefault = []\n\n[dependencies]\nanchor-lang = \"0.28.0\"\nnum-traits = \"0.2\"\nnum-derive = \"0.3\"\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/programs/tic-tac-toe/Xargo.toml",
    "content": "[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",
    "content": "use anchor_lang::prelude::*;\nuse num_derive;\nuse num_traits::FromPrimitive;\n\ndeclare_id!(\"5xGwZASoE5ZgxKgaisJNaGTGzMKzjyyBGv9FCUtu2m1c\");\n\n#[program]\npub mod tic_tac_toe {\n    use super::*;\n\n    pub fn setup_game(\n        ctx: Context<SetupGame>,\n        player_two_pubkey: Pubkey,\n        _game_id: String,\n    ) -> Result<()> {\n        let player_one = &ctx.accounts.player_one;\n        let player_one_pubkey = player_one.key();\n\n        let game = &mut ctx.accounts.game;\n\n        game.start([player_one_pubkey, player_two_pubkey])\n    }\n\n    pub fn play(ctx: Context<Play>, tile: Tile) -> Result<()> {\n        let game = &mut ctx.accounts.game;\n\n        require_keys_eq!(\n            game.current_player(),\n            ctx.accounts.player.key(),\n            TicTacToeError::NotPlayersTurn\n        );\n\n        game.play(&tile)\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(player_two_pubkey: Pubkey, _game_id: String)]\npub struct SetupGame<'info> {\n    #[account(\n        init,\n        payer = player_one,\n        space = 8 + Game::MAXIMUM_SIZE,\n        seeds = [b\"game\", player_one.key().as_ref(), _game_id.as_bytes()],\n        bump\n    )]\n    pub game: Account<'info, Game>,\n    #[account(mut)]\n    pub player_one: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\npub struct Game {\n    players: [Pubkey; 2],          // (32 * 2)\n    turn: u8,                      // 1\n    board: [[Option<Sign>; 3]; 3], // 9 * (1 + 1) = 18\n    state: GameState,              // 32 + 1\n}\n\n#[derive(Accounts)]\npub struct Play<'info> {\n    #[account(mut)]\n    pub game: Account<'info, Game>,\n    pub player: Signer<'info>,\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]\npub enum GameState {\n    Active,\n    Tie,\n    Won { winner: Pubkey },\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize, num_derive::FromPrimitive, Copy, Clone, PartialEq)]\npub enum Sign {\n    X,\n    O,\n}\n\nimpl Game {\n    pub const MAXIMUM_SIZE: usize = (32 * 2) + 1 + (9 * (1 + 1)) + (32 + 1);\n\n    pub fn start(&mut self, players: [Pubkey; 2]) -> Result<()> {\n        require_eq!(self.turn, 0, TicTacToeError::GameAlreadyStarted);\n        self.players = players;\n        self.turn = 1;\n        Ok(())\n    }\n\n    pub fn is_active(&self) -> bool {\n        self.state == GameState::Active\n    }\n\n    fn current_player_index(&self) -> usize {\n        ((self.turn - 1) % 2) as usize\n    }\n\n    pub fn current_player(&self) -> Pubkey {\n        self.players[self.current_player_index()]\n    }\n\n    pub fn play(&mut self, tile: &Tile) -> Result<()> {\n        require!(self.is_active(), TicTacToeError::GameAlreadyOver);\n\n        match tile {\n            tile @ Tile {\n                row: 0..=2,\n                column: 0..=2,\n            } => match self.board[tile.row as usize][tile.column as usize] {\n                Some(_) => return Err(TicTacToeError::TileAlreadySet.into()),\n                None => {\n                    self.board[tile.row as usize][tile.column as usize] =\n                        Some(Sign::from_usize(self.current_player_index()).unwrap());\n                }\n            },\n            _ => return Err(TicTacToeError::TileOutOfBounds.into()),\n        }\n\n        self.update_state();\n\n        if GameState::Active == self.state {\n            self.turn += 1;\n        }\n\n        Ok(())\n    }\n\n    fn is_winning_trio(&self, trio: [(usize, usize); 3]) -> bool {\n        let [first, second, third] = trio;\n        self.board[first.0][first.1].is_some()\n            && self.board[first.0][first.1] == self.board[second.0][second.1]\n            && self.board[first.0][first.1] == self.board[third.0][third.1]\n    }\n\n    fn update_state(&mut self) {\n        for i in 0..=2 {\n            // three of the same in one row\n            if self.is_winning_trio([(i, 0), (i, 1), (i, 2)]) {\n                self.state = GameState::Won {\n                    winner: self.current_player(),\n                };\n                return;\n            }\n            // three of the same in one column\n            if self.is_winning_trio([(0, i), (1, i), (2, i)]) {\n                self.state = GameState::Won {\n                    winner: self.current_player(),\n                };\n                return;\n            }\n        }\n\n        // three of the same in one diagonal\n        if self.is_winning_trio([(0, 0), (1, 1), (2, 2)])\n            || self.is_winning_trio([(0, 2), (1, 1), (2, 0)])\n        {\n            self.state = GameState::Won {\n                winner: self.current_player(),\n            };\n            return;\n        }\n\n        // reaching this code means the game has not been won,\n        // so if there are unfilled tiles left, it's still active\n        for row in 0..=2 {\n            for column in 0..=2 {\n                if self.board[row][column].is_none() {\n                    return;\n                }\n            }\n        }\n\n        // game has not been won\n        // game has no more free tiles\n        // -> game ends in a tie\n        self.state = GameState::Tie;\n    }\n}\n\n#[derive(AnchorSerialize, AnchorDeserialize)]\npub struct Tile {\n    row: u8,\n    column: u8,\n}\n\n#[error_code]\npub enum TicTacToeError {\n    TileOutOfBounds,\n    TileAlreadySet,\n    GameAlreadyOver,\n    NotPlayersTurn,\n    GameAlreadyStarted,\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/tests/tic-tac-toe.ts",
    "content": "import * as anchor from \"@coral-xyz/anchor\";\nimport { Program } from \"@coral-xyz/anchor\";\nimport { TicTacToe } from \"../target/types/tic_tac_toe\";\n\ndescribe(\"tic-tac-toe\", () => {\n  // Configure the client to use the local cluster.\n  anchor.setProvider(anchor.AnchorProvider.env());\n\n  const program = anchor.workspace.TicTacToe as Program<TicTacToe>;\n\n  it(\"Is initialized!\", async () => {\n    // Add your test here.\n    const tx = await program.methods.initialize().rpc();\n    console.log(\"Your transaction signature\", tx);\n  });\n});\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-1/tic-tac-toe/tsconfig.json",
    "content": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules/@types\"],\n              \"lib\": [\"es2015\"],\n              \"module\": \"commonjs\",\n              \"target\": \"es6\",\n              \"esModuleInterop\": true\n            }\n          }\n          "
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/index.css",
    "content": "body {\n  background: black;\n  width: 100%;\n  height: 100vh;\n  margin: 0;\n  padding: 0;\n}\n\nform,\ntable {\n  color: whitesmoke;\n  margin: 1em auto;\n  text-align: center;\n}\n\nform > fieldset > button:hover {\n  cursor: pointer;\n  background-color: #cd3737;\n}\n\ntbody > tr {\n  border: solid 1px whitesmoke;\n}\n\ntd {\n  width: 100px;\n  height: 100px;\n  text-align: center;\n  vertical-align: middle;\n  font-size: 50px;\n  border: solid 1px whitesmoke;\n}\n\ntd:hover {\n  cursor: pointer;\n  background-color: #4b8270;\n}\n\n#errors {\n  color: red;\n  font-size: 1.5em;\n  margin: 0 auto;\n  text-align: center;\n}\n\n#keypairs {\n  color: greenyellow;\n}\n\n/* Wrap text in #keypairs > li */\n#keypairs > li > pre {\n  text-wrap: wrap;\n  word-wrap: break-word;\n  color: #2ca08d;\n  background-color: #2e3136;\n  margin: 1rem;\n  padding: 0.5rem;\n}\n\n#spinner {\n  margin: 0 auto;\n  text-align: center;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}\n\n.lds-ripple {\n  display: inline-block;\n  position: relative;\n  width: 80px;\n  height: 80px;\n}\n\n.lds-ripple div {\n  position: absolute;\n  border: 4px solid #fff;\n  opacity: 1;\n  border-radius: 50%;\n  animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;\n}\n\n.lds-ripple div:nth-child(2) {\n  animation-delay: -0.5s;\n}\n\n@keyframes lds-ripple {\n  0% {\n    top: 36px;\n    left: 36px;\n    width: 0;\n    height: 0;\n    opacity: 1;\n  }\n  100% {\n    top: 0px;\n    left: 0px;\n    width: 72px;\n    height: 72px;\n    opacity: 0;\n  }\n}\n\n.hidden {\n  display: none;\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/index.html",
    "content": "<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link href=\"./index.css\" type=\"text/css\" rel=\"stylesheet\" />\n    <title>Tic-Tac-Toe</title>\n  </head>\n  <body>\n    <form>\n      <label> Keypair:<input type=\"text\" id=\"keypair\" /> </label>\n      <button id=\"connect-wallet\" formaction=\"#\">Connect Wallet</button>\n    </form>\n    <form>\n      <label>New Game ID: <input type=\"text\" id=\"game-id\" /></label>\n      <label>\n        Player One Public Key:\n        <input\n          type=\"text\"\n          required\n          placeholder=\"e.g. AxfbN...\"\n          id=\"player-one-public-key\"\n        />\n      </label>\n      <label>\n        Player Two Public Key:\n        <input\n          type=\"text\"\n          required\n          placeholder=\"e.g. AxfbN...\"\n          id=\"player-two-public-key\"\n        />\n      </label>\n      <label>\n        Game Public Key: <input type=\"text\" id=\"game-public-key\" />\n      </label>\n      <fieldset>\n        <button id=\"start-game\" formaction=\"#\">Start Game</button>\n        <p>OR</p>\n        <button id=\"join-game\" formaction=\"#\">Join Game</button>\n      </fieldset>\n    </form>\n    <table>\n      <thead>\n        <tr>\n          <th>Turn:</th>\n          <th id=\"turn\"></th>\n        </tr>\n        <tr>\n          <th>Player:</th>\n          <th id=\"player-turn\"></th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <td id=\"0\"></td>\n          <td id=\"1\"></td>\n          <td id=\"2\"></td>\n        </tr>\n        <tr>\n          <td id=\"3\"></td>\n          <td id=\"4\"></td>\n          <td id=\"5\"></td>\n        </tr>\n        <tr>\n          <td id=\"6\"></td>\n          <td id=\"7\"></td>\n          <td id=\"8\"></td>\n        </tr>\n      </tbody>\n    </table>\n    <section>\n      <p id=\"errors\"></p>\n      <ul id=\"keypairs\"></ul>\n    </section>\n    <div id=\"spinner\" class=\"hidden\">\n      <div class=\"lds-ripple\">\n        <div></div>\n        <div></div>\n      </div>\n    </div>\n  </body>\n  <script src=\"./index.js\" type=\"module\"></script>\n</html>\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/index.js",
    "content": "import { Keypair, PublicKey } from '@solana/web3.js';\nimport player_one_keypair from '../player-one.json';\nimport player_two_keypair from '../player-two.json';\nimport { displayError, removeLoader, showLoader } from './utils';\nimport {\n  PROGRAM_ID,\n  connectWallet,\n  deriveGamePublicKey,\n  handlePlay,\n  startGame,\n  updateBoard\n} from './web3';\n\nconst gameIdEl = document.getElementById('game-id');\nconst startGameBtnEl = document.getElementById('start-game');\nconst tableBodyEl = document.querySelector('tbody');\nconst tdEls = tableBodyEl.querySelectorAll('td');\nconst joinGameBtnEl = document.getElementById('join-game');\nconst playerOnePublicKeyEl = document.getElementById('player-one-public-key');\nconst playerTwoPublicKeyEl = document.getElementById('player-two-public-key');\nconst connectWalletBtnEl = document.getElementById('connect-wallet');\nconst keypairEl = document.getElementById('keypair');\nconst keypairsEl = document.getElementById('keypairs');\n\nconnectWalletBtnEl.addEventListener('click', ev => {\n  ev.preventDefault();\n  displayError();\n  showLoader();\n  try {\n    const keypair = keypairEl.value;\n    sessionStorage.setItem('keypair', keypair);\n\n    // TODO: Connect to wallet\n    connectWallet();\n\n    connectWalletBtnEl.style.backgroundColor = 'green';\n  } catch (e) {\n    displayError(e);\n  } finally {\n    removeLoader();\n  }\n});\n\nstartGameBtnEl.addEventListener('click', async event => {\n  event.preventDefault();\n  displayError();\n  showLoader();\n  try {\n    // TODO: Create a new game\n    await startGame();\n  } catch (e) {\n    displayError(e);\n  } finally {\n    removeLoader();\n  }\n});\n\njoinGameBtnEl.addEventListener('click', async event => {\n  event.preventDefault();\n  displayError();\n  showLoader();\n  try {\n    // TODO: Join an existing game\n    await updateBoard();\n  } catch (e) {\n    displayError(e);\n  } finally {\n    removeLoader();\n  }\n});\n\ntdEls.forEach(tdEl => {\n  tdEl.addEventListener('click', async event => {\n    event.preventDefault();\n    displayError();\n    showLoader();\n    try {\n      // TODO: Play tile\n      await handlePlay(event.target.id);\n    } catch (e) {\n      displayError(e);\n    } finally {\n      removeLoader();\n    }\n  });\n});\n\ndocument.addEventListener('DOMContentLoaded', async _event => {\n  startWithPossibleValues();\n\n  const interval = setInterval(async () => {\n    showLoader();\n    try {\n      // TODO: If program and gamePublicKey exist, update board\n      const gamePublicKey = sessionStorage.getItem('gamePublicKey');\n      if (program && gamePublicKey) {\n        await updateBoard();\n      }\n    } catch (e) {\n      console.debug(e);\n    } finally {\n      removeLoader();\n    }\n  }, 3000);\n\n  // A game of tic-tac-toe should not last long,\n  // but for development this is commented out\n  // setTimeout(() => {\n  //   clearInterval(interval);\n  // }, 300_000);\n  return () => {\n    clearInterval(interval);\n  };\n});\n\n// ---------------------\n// CONVENIENCE FUNCTIONS\n// ---------------------\n\nfunction startWithPossibleValues() {\n  const player_one_publicKey = Keypair.fromSecretKey(\n    new Uint8Array(player_one_keypair)\n  ).publicKey.toBase58();\n  const player_two_publicKey = Keypair.fromSecretKey(\n    new Uint8Array(player_two_keypair)\n  ).publicKey.toBase58();\n\n  playerOnePublicKeyEl.value = player_one_publicKey;\n  playerTwoPublicKeyEl.value = player_two_publicKey;\n\n  const keypairs = [player_one_keypair, player_two_keypair];\n  keypairs.forEach((keypair, i) => {\n    const li = document.createElement('li');\n    const code = document.createElement('code');\n    const pre = document.createElement('pre');\n    pre.appendChild(code);\n    code.textContent = JSON.stringify(keypair);\n    li.textContent = `Player ${i + 1} Public Key: `;\n\n    li.appendChild(pre);\n\n    keypairsEl.appendChild(li);\n  });\n}\n\nplayerOnePublicKeyEl.addEventListener('change', e => {\n  const playerOnePublicKey = e.target.value;\n  sessionStorage.setItem('playerOnePublicKey', playerOnePublicKey);\n});\n\nplayerTwoPublicKeyEl.addEventListener('change', e => {\n  const playerTwoPublicKey = e.target.value;\n  sessionStorage.setItem('playerTwoPublicKey', playerTwoPublicKey);\n});\n\ngameIdEl.addEventListener('change', e => {\n  const gameId = e.target.value;\n  sessionStorage.setItem('gameId', gameId);\n  const gamePublicKey = deriveGamePublicKey(\n    new PublicKey(playerOnePublicKeyEl.value),\n    gameId,\n    PROGRAM_ID\n  );\n  sessionStorage.setItem('gamePublicKey', gamePublicKey.toBase58());\n});\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/package.json",
    "content": "{\n  \"name\": \"app\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"4.5.6\"\n  },\n  \"dependencies\": {\n    \"@solana/web3.js\": \"1.87.7\",\n    \"@coral-xyz/anchor\": \"0.28.1-beta.1\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/utils.js",
    "content": "const errorsEl = document.getElementById('errors');\nconst spinnerEl = document.getElementById('spinner');\n\nconst tableBodyEl = document.querySelector('tbody');\nconst tdEls = tableBodyEl.querySelectorAll('td');\n\n/**\n * @param {{ x: {} } | { o: {} } | null} tile\n * @returns 'X' | 'O' | ''\n */\nexport function tileToString(tile) {\n  if (tile?.x) {\n    return 'X';\n  }\n  if (tile?.o) {\n    return 'O';\n  }\n  return '';\n}\n\n/**\n * @param {({ x: {} } | { o: {} } | null)[][]} board\n */\nexport function setTiles(board) {\n  for (let i = 0; i < 3; i++) {\n    for (let j = 0; j < 3; j++) {\n      const tile = board[i][j];\n      const tileEl = tdEls[i * 3 + j];\n      tileEl.textContent = tileToString(tile);\n    }\n  }\n}\n\n/**\n * @param {Error | undefined} error\n */\nexport function displayError(error) {\n  if (error) {\n    console.error(error);\n    errorsEl.innerText = error.message;\n  } else {\n    errorsEl.innerText = '';\n  }\n}\n\n/**\n * @param {number} id\n * @returns {{ row: number; column: number }}\n */\nexport function idToTile(id) {\n  for (let i = 0; i < 3; i++) {\n    for (let j = 0; j < 3; j++) {\n      const tile = tdEls[i * 3 + j];\n      if (tile.id === id) {\n        return { row: i, column: j };\n      }\n    }\n  }\n}\n\nexport function showLoader() {\n  spinnerEl.classList.remove('hidden');\n}\n\nexport function removeLoader() {\n  spinnerEl.classList.add('hidden');\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/wallet.js",
    "content": "export class Wallet {\n  constructor(keypair) {\n    this.keypair = keypair;\n    this._publicKey = keypair.publicKey;\n  }\n  async signTransaction(tx) {\n    tx.sign(this.keypair);\n    return tx;\n  }\n  async signAllTransactions(txs) {\n    txs.map(tx => tx.sign(this.keypair));\n    return txs;\n  }\n  get publicKey() {\n    return this._publicKey;\n  }\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/app/web3.js",
    "content": "import { AnchorProvider, Program, setProvider } from '@coral-xyz/anchor';\nimport { Wallet } from './wallet.js';\nimport { IDL } from '../tic_tac_toe';\nimport { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport { Buffer } from 'buffer';\nimport { idToTile, setTiles } from './utils.js';\n\nconst turnEl = document.getElementById('turn');\nconst playerTurnEl = document.getElementById('player-turn');\n\nwindow.Buffer = Buffer;\nwindow.program = null;\n\nexport const PROGRAM_ID = new PublicKey(\n  '5xGwZASoE5ZgxKgaisJNaGTGzMKzjyyBGv9FCUtu2m1c'\n);\n\nexport const connection = new Connection('http://localhost:8899', 'processed');\n\nexport function connectWallet() {\n  const keypairArr = sessionStorage.getItem('keypair');\n  const uint = new Uint8Array(JSON.parse(keypairArr));\n  const keypair = Keypair.fromSecretKey(uint);\n  const wallet = new Wallet(keypair);\n  const provider = new AnchorProvider(connection, wallet, {});\n  setProvider(provider);\n  const program = new Program(IDL, PROGRAM_ID, provider);\n  window.program = program;\n}\n\nexport async function startGame() {\n  const gameId = sessionStorage.getItem('gameId');\n  const playerOnePublicKey = new PublicKey(\n    sessionStorage.getItem('playerOnePublicKey')\n  );\n  const playerTwoPublicKey = new PublicKey(\n    sessionStorage.getItem('playerTwoPublicKey')\n  );\n  const gamePublicKey = deriveGamePublicKey(\n    playerOnePublicKey,\n    gameId,\n    PROGRAM_ID\n  );\n  sessionStorage.setItem('gamePublicKey', gamePublicKey.toString());\n\n  const keypairStr = sessionStorage.getItem('keypair');\n  const keypairArr = JSON.parse(keypairStr);\n  const uint8Arr = new Uint8Array(keypairArr);\n  const keypair = Keypair.fromSecretKey(uint8Arr);\n  await program.methods\n    .setupGame(playerTwoPublicKey, gameId)\n    .accounts({\n      playerOne: keypair.publicKey,\n      game: gamePublicKey\n    })\n    .signers([keypair])\n    .rpc();\n  await updateBoard();\n}\n\nexport async function handlePlay(id) {\n  const tile = idToTile(id);\n\n  await updateBoard();\n\n  const keypairStr = sessionStorage.getItem('keypair');\n  const keypairArr = JSON.parse(keypairStr);\n  const uint8Arr = new Uint8Array(keypairArr);\n  const keypair = Keypair.fromSecretKey(uint8Arr);\n  const gamePublicKey = new PublicKey(sessionStorage.getItem('gamePublicKey'));\n  await program.methods\n    .play(tile)\n    .accounts({\n      player: keypair.publicKey,\n      game: gamePublicKey\n    })\n    .signers([keypair])\n    .rpc();\n  await updateBoard();\n}\n\nexport function deriveGamePublicKey(playerOnePublicKey, gameId, programId) {\n  const [gamePublicKey, _] = PublicKey.findProgramAddressSync(\n    [Buffer.from('game'), playerOnePublicKey.toBuffer(), Buffer.from(gameId)],\n    programId\n  );\n  return gamePublicKey;\n}\n\nexport async function getGameAccount() {\n  const gamePublicKey = new PublicKey(sessionStorage.getItem('gamePublicKey'));\n  const gameData = await program.account.game.fetch(gamePublicKey);\n  turnEl.textContent = gameData.turn;\n  playerTurnEl.textContent = gameData.turn % 2 === 0 ? 'O' : 'X';\n  return gameData;\n}\n\nexport async function updateBoard() {\n  const gameData = await getGameAccount();\n  const board = gameData.board;\n\n  setTiles(board);\n}\n"
  },
  {
    "path": "learn-how-to-build-a-client-side-app-part-2/tic_tac_toe.ts",
    "content": "export type TicTacToe = {\n  \"version\": \"0.1.0\",\n  \"name\": \"tic_tac_toe\",\n  \"instructions\": [\n    {\n      \"name\": \"setupGame\",\n      \"accounts\": [\n        {\n          \"name\": \"game\",\n          \"isMut\": true,\n          \"isSigner\": false\n        },\n        {\n          \"name\": \"playerOne\",\n          \"isMut\": true,\n          \"isSigner\": true\n        },\n        {\n          \"name\": \"systemProgram\",\n          \"isMut\": false,\n          \"isSigner\": false\n        }\n      ],\n      \"args\": [\n        {\n          \"name\": \"playerTwoPubkey\",\n          \"type\": \"publicKey\"\n        },\n        {\n          \"name\": \"gameId\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    {\n      \"name\": \"play\",\n      \"accounts\": [\n        {\n          \"name\": \"game\",\n          \"isMut\": true,\n          \"isSigner\": false\n        },\n        {\n          \"name\": \"player\",\n          \"isMut\": false,\n          \"isSigner\": true\n        }\n      ],\n      \"args\": [\n        {\n          \"name\": \"tile\",\n          \"type\": {\n            \"defined\": \"Tile\"\n          }\n        }\n      ]\n    }\n  ],\n  \"accounts\": [\n    {\n      \"name\": \"game\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"players\",\n            \"type\": {\n              \"array\": [\n                \"publicKey\",\n                2\n              ]\n            }\n          },\n          {\n            \"name\": \"turn\",\n            \"type\": \"u8\"\n          },\n          {\n            \"name\": \"board\",\n            \"type\": {\n              \"array\": [\n                {\n                  \"array\": [\n                    {\n                      \"option\": {\n                        \"defined\": \"Sign\"\n                      }\n                    },\n                    3\n                  ]\n                },\n                3\n              ]\n            }\n          },\n          {\n            \"name\": \"state\",\n            \"type\": {\n              \"defined\": \"GameState\"\n            }\n          }\n        ]\n      }\n    }\n  ],\n  \"types\": [\n    {\n      \"name\": \"Tile\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"row\",\n            \"type\": \"u8\"\n          },\n          {\n            \"name\": \"column\",\n            \"type\": \"u8\"\n          }\n        ]\n      }\n    },\n    {\n      \"name\": \"GameState\",\n      \"type\": {\n        \"kind\": \"enum\",\n        \"variants\": [\n          {\n            \"name\": \"Active\"\n          },\n          {\n            \"name\": \"Tie\"\n          },\n          {\n            \"name\": \"Won\",\n            \"fields\": [\n              {\n                \"name\": \"winner\",\n                \"type\": \"publicKey\"\n              }\n            ]\n          }\n        ]\n      }\n    },\n    {\n      \"name\": \"Sign\",\n      \"type\": {\n        \"kind\": \"enum\",\n        \"variants\": [\n          {\n            \"name\": \"X\"\n          },\n          {\n            \"name\": \"O\"\n          }\n        ]\n      }\n    }\n  ],\n  \"errors\": [\n    {\n      \"code\": 6000,\n      \"name\": \"TileOutOfBounds\"\n    },\n    {\n      \"code\": 6001,\n      \"name\": \"TileAlreadySet\"\n    },\n    {\n      \"code\": 6002,\n      \"name\": \"GameAlreadyOver\"\n    },\n    {\n      \"code\": 6003,\n      \"name\": \"NotPlayersTurn\"\n    },\n    {\n      \"code\": 6004,\n      \"name\": \"GameAlreadyStarted\"\n    }\n  ]\n};\n\nexport const IDL: TicTacToe = {\n  \"version\": \"0.1.0\",\n  \"name\": \"tic_tac_toe\",\n  \"instructions\": [\n    {\n      \"name\": \"setupGame\",\n      \"accounts\": [\n        {\n          \"name\": \"game\",\n          \"isMut\": true,\n          \"isSigner\": false\n        },\n        {\n          \"name\": \"playerOne\",\n          \"isMut\": true,\n          \"isSigner\": true\n        },\n        {\n          \"name\": \"systemProgram\",\n          \"isMut\": false,\n          \"isSigner\": false\n        }\n      ],\n      \"args\": [\n        {\n          \"name\": \"playerTwoPubkey\",\n          \"type\": \"publicKey\"\n        },\n        {\n          \"name\": \"gameId\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    {\n      \"name\": \"play\",\n      \"accounts\": [\n        {\n          \"name\": \"game\",\n          \"isMut\": true,\n          \"isSigner\": false\n        },\n        {\n          \"name\": \"player\",\n          \"isMut\": false,\n          \"isSigner\": true\n        }\n      ],\n      \"args\": [\n        {\n          \"name\": \"tile\",\n          \"type\": {\n            \"defined\": \"Tile\"\n          }\n        }\n      ]\n    }\n  ],\n  \"accounts\": [\n    {\n      \"name\": \"game\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"players\",\n            \"type\": {\n              \"array\": [\n                \"publicKey\",\n                2\n              ]\n            }\n          },\n          {\n            \"name\": \"turn\",\n            \"type\": \"u8\"\n          },\n          {\n            \"name\": \"board\",\n            \"type\": {\n              \"array\": [\n                {\n                  \"array\": [\n                    {\n                      \"option\": {\n                        \"defined\": \"Sign\"\n                      }\n                    },\n                    3\n                  ]\n                },\n                3\n              ]\n            }\n          },\n          {\n            \"name\": \"state\",\n            \"type\": {\n              \"defined\": \"GameState\"\n            }\n          }\n        ]\n      }\n    }\n  ],\n  \"types\": [\n    {\n      \"name\": \"Tile\",\n      \"type\": {\n        \"kind\": \"struct\",\n        \"fields\": [\n          {\n            \"name\": \"row\",\n            \"type\": \"u8\"\n          },\n          {\n            \"name\": \"column\",\n            \"type\": \"u8\"\n          }\n        ]\n      }\n    },\n    {\n      \"name\": \"GameState\",\n      \"type\": {\n        \"kind\": \"enum\",\n        \"variants\": [\n          {\n            \"name\": \"Active\"\n          },\n          {\n            \"name\": \"Tie\"\n          },\n          {\n            \"name\": \"Won\",\n            \"fields\": [\n              {\n                \"name\": \"winner\",\n                \"type\": \"publicKey\"\n              }\n            ]\n          }\n        ]\n      }\n    },\n    {\n      \"name\": \"Sign\",\n      \"type\": {\n        \"kind\": \"enum\",\n        \"variants\": [\n          {\n            \"name\": \"X\"\n          },\n          {\n            \"name\": \"O\"\n          }\n        ]\n      }\n    }\n  ],\n  \"errors\": [\n    {\n      \"code\": 6000,\n      \"name\": \"TileOutOfBounds\"\n    },\n    {\n      \"code\": 6001,\n      \"name\": \"TileAlreadySet\"\n    },\n    {\n      \"code\": 6002,\n      \"name\": \"GameAlreadyOver\"\n    },\n    {\n      \"code\": 6003,\n      \"name\": \"NotPlayersTurn\"\n    },\n    {\n      \"code\": 6004,\n      \"name\": \"GameAlreadyStarted\"\n    }\n  ]\n};\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/Anchor.toml",
    "content": "[features]\nseeds = false\nskip-lint = false\nskip-preflight = true\n[programs.localnet]\ntodo = \"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\"\n\n[registry]\nurl = \"https://api.apr.dev\"\n\n[provider]\ncluster = \"Localnet\"\nwallet = \"~/.config/solana/id.json\"\n\n[scripts]\ntest = \"yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts\"\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[profile.release.build-override]\nopt-level = 3\nincremental = false\ncodegen-units = 1\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>TODO</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/package.json",
    "content": "{\n  \"name\": \"todo\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"lint\": \"eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@coral-xyz/anchor\": \"0.28.1-beta.1\",\n    \"@solana/wallet-adapter-phantom\": \"0.9.24\",\n    \"@solana/web3.js\": \"1.87.7\",\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"18.2.31\",\n    \"@types/react-dom\": \"18.2.14\",\n    \"@typescript-eslint/eslint-plugin\": \"6.8.0\",\n    \"@typescript-eslint/parser\": \"6.8.0\",\n    \"@vitejs/plugin-react\": \"4.0.4\",\n    \"eslint\": \"8.52.0\",\n    \"eslint-plugin-react-hooks\": \"4.6.0\",\n    \"eslint-plugin-react-refresh\": \"0.4.3\",\n    \"typescript\": \"5.1.6\",\n    \"vite\": \"4.5.6\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/app.css",
    "content": "/* RESETS */\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n*:focus {\n  outline: 3px dashed #228bec;\n  outline-offset: 0;\n}\nhtml {\n  font: 62.5% / 1.15 sans-serif;\n}\nh1,\nh2 {\n  margin-bottom: 0;\n}\nul {\n  list-style: none;\n  padding: 0;\n}\nbutton {\n  border: none;\n  margin: 0;\n  padding: 0;\n  width: auto;\n  overflow: visible;\n  background: transparent;\n  color: inherit;\n  font: inherit;\n  line-height: normal;\n  -webkit-font-smoothing: inherit;\n  -moz-osx-font-smoothing: inherit;\n  appearance: none;\n}\nbutton::-moz-focus-inner {\n  border: 0;\n}\nbutton:hover {\n  cursor: pointer;\n}\nbutton,\ninput {\n  font-family: inherit;\n  font-size: 100%;\n  line-height: 1.15;\n  margin: 0;\n}\nbutton,\ninput {\n  overflow: visible;\n}\ninput[type='text'] {\n  border-radius: 0;\n}\nbody {\n  width: 100%;\n  max-width: 68rem;\n  margin: 0 auto;\n  font:\n    1.6rem/1.25 Arial,\n    sans-serif;\n  background-color: #f5f5f5;\n  color: #4d4d4d;\n}\n@media screen and (min-width: 620px) {\n  body {\n    font-size: 1.9rem;\n    line-height: 1.31579;\n  }\n}\n/*END RESETS*/\n/* GLOBAL STYLES */\n.btn {\n  padding: 0.8rem 1rem 0.7rem;\n  border: 0.2rem solid #4d4d4d;\n  cursor: pointer;\n  text-transform: capitalize;\n}\n.btn.toggle-btn {\n  border-width: 1px;\n  border-color: #d3d3d3;\n}\n.btn.toggle-btn[aria-pressed='true'] {\n  text-decoration: underline;\n  border-color: #4d4d4d;\n}\n.btn__danger {\n  color: #fff;\n  background-color: #ca3c3c;\n  border-color: #bd2130;\n}\n.btn__primary {\n  color: #fff;\n  background-color: #000;\n}\n.btn-group {\n  display: flex;\n  justify-content: space-between;\n}\n.btn-group > * {\n  flex: 1 1 49%;\n}\n.btn-group > * + * {\n  margin-left: 0.8rem;\n}\n.label-wrapper {\n  margin: 0;\n  flex: 0 0 100%;\n  text-align: center;\n}\n.visually-hidden {\n  position: absolute !important;\n  height: 1px;\n  width: 1px;\n  overflow: hidden;\n  clip: rect(1px 1px 1px 1px);\n  clip: rect(1px, 1px, 1px, 1px);\n  white-space: nowrap;\n}\n[class*='stack'] > * {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.stack-small > * + * {\n  margin-top: 1.25rem;\n}\n.stack-large > * + * {\n  margin-top: 2.5rem;\n}\n@media screen and (min-width: 550px) {\n  .stack-small > * + * {\n    margin-top: 1.4rem;\n  }\n  .stack-large > * + * {\n    margin-top: 2.8rem;\n  }\n}\n.stack-exception {\n  margin-top: 1.2rem;\n}\n/* END GLOBAL STYLES */\n.todoapp {\n  background: #fff;\n  margin: 2rem 0 4rem 0;\n  padding: 1rem;\n  position: relative;\n  box-shadow:\n    0 2px 4px 0 rgba(0, 0, 0, 0.2),\n    0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);\n}\n@media screen and (min-width: 550px) {\n  .todoapp {\n    padding: 4rem;\n  }\n}\n.todoapp > * {\n  max-width: 50rem;\n  margin-left: auto;\n  margin-right: auto;\n}\n.todoapp > form {\n  max-width: 100%;\n}\n.todoapp > h1 {\n  display: block;\n  max-width: 100%;\n  text-align: center;\n  margin: 0;\n  margin-bottom: 1rem;\n}\n.label__lg {\n  line-height: 1.01567;\n  font-weight: 300;\n  padding: 0.8rem;\n  margin-bottom: 1rem;\n  text-align: center;\n}\n.input__lg {\n  padding: 2rem;\n  border: 2px solid #000;\n}\n.input__lg:focus {\n  border-color: #4d4d4d;\n  box-shadow: inset 0 0 0 2px;\n}\n[class*='__lg'] {\n  display: inline-block;\n  width: 100%;\n  font-size: 1.9rem;\n}\n[class*='__lg']:not(:last-child) {\n  margin-bottom: 1rem;\n}\n@media screen and (min-width: 620px) {\n  [class*='__lg'] {\n    font-size: 2.4rem;\n  }\n}\n/* Todo item styles */\n.todo {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n}\n.todo > * {\n  flex: 0 0 100%;\n}\n.todo-text {\n  width: 100%;\n  min-height: 4.4rem;\n  padding: 0.4rem 0.8rem;\n  border: 2px solid #565656;\n}\n.todo-text:focus {\n  box-shadow: inset 0 0 0 2px;\n}\n/* CHECKBOX STYLES */\n.c-cb {\n  box-sizing: border-box;\n  font-family: Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  font-weight: 400;\n  font-size: 1.6rem;\n  line-height: 1.25;\n  display: block;\n  position: relative;\n  min-height: 44px;\n  padding-left: 40px;\n  clear: left;\n}\n.c-cb > label::before,\n.c-cb > input[type='checkbox'] {\n  box-sizing: border-box;\n  top: -2px;\n  left: -2px;\n  width: 44px;\n  height: 44px;\n}\n.c-cb > input[type='checkbox'] {\n  -webkit-font-smoothing: antialiased;\n  cursor: pointer;\n  position: absolute;\n  z-index: 1;\n  margin: 0;\n  opacity: 0;\n}\n.c-cb > label {\n  font-size: inherit;\n  font-family: inherit;\n  line-height: inherit;\n  display: inline-block;\n  margin-bottom: 0;\n  padding: 8px 15px 5px;\n  cursor: pointer;\n  touch-action: manipulation;\n}\n.c-cb > label::before {\n  content: '';\n  position: absolute;\n  border: 2px solid currentcolor;\n  background: transparent;\n}\n.c-cb > input[type='checkbox']:focus + label::before {\n  border-width: 4px;\n  outline: 3px dashed #228bec;\n}\n.c-cb > label::after {\n  box-sizing: content-box;\n  content: '';\n  position: absolute;\n  top: 11px;\n  left: 9px;\n  width: 18px;\n  height: 7px;\n  transform: rotate(-45deg);\n  border: solid;\n  border-width: 0 0 5px 5px;\n  border-top-color: transparent;\n  opacity: 0;\n  background: transparent;\n}\n.c-cb > input[type='checkbox']:checked + label::after {\n  opacity: 1;\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/app.tsx",
    "content": "import {\n  ChangeEvent,\n  FormEvent,\n  MouseEventHandler,\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useState\n} from 'react';\nimport { IDL, Todo } from '../../target/types/todo';\nimport './app.css';\nimport { AnchorProvider, Program } from '@coral-xyz/anchor';\nimport { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';\nimport { Connection, PublicKey } from '@solana/web3.js';\nimport { Buffer } from 'buffer';\nimport {\n  isWalletConnected,\n  Task,\n  Filters,\n  FILTER_MAP,\n  usePrevious,\n  FILTER_NAMES,\n  TodoT,\n  FormT,\n  FilterButtonT\n} from './utils';\n\nwindow.Buffer = Buffer;\n\nconst PROGRAM_ID = new PublicKey(\n  '9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2'\n);\nconst ENDPOINT =\n  import.meta.env.VITE_SOLANA_CONNECTION_URL || 'http://localhost:8899';\nconst connection = new Connection(ENDPOINT, 'confirmed');\nconst wallet = new PhantomWalletAdapter();\nconst ProgramContext = createContext<Program<Todo> | null>(null);\n\nexport function App() {\n  const [program, setProgram] = useState<Program<Todo> | null>(null);\n\n  const connectWallet: MouseEventHandler<HTMLButtonElement> = async e => {\n    e.preventDefault();\n    await wallet.connect();\n    console.log('Connected to: ', wallet.publicKey);\n    if (isWalletConnected(wallet)) {\n      const provider = new AnchorProvider(connection, wallet, {});\n      const program = new Program(IDL, PROGRAM_ID, provider);\n      setProgram(program);\n    }\n  };\n\n  return (\n    <ProgramContext.Provider value={program}>\n      {program ? <Landing /> : <LogIn connectWallet={connectWallet} />}\n    </ProgramContext.Provider>\n  );\n}\n\nfunction LogIn({\n  connectWallet\n}: {\n  connectWallet: MouseEventHandler<HTMLButtonElement>;\n}) {\n  return (\n    <div className='todoapp stack-large'>\n      <h1>Connect and Login</h1>\n      <button onClick={connectWallet} className='btn'>\n        Connect Wallet\n      </button>\n    </div>\n  );\n}\n\nfunction Landing() {\n  const [tasks, setTasks] = useState<Task[]>([]);\n  const [filter, setFilter] = useState<Filters>('All');\n  const program = useContext(ProgramContext);\n\n  async function loadTasksFromChain() {\n    if (program && program.provider.publicKey) {\n      const [tasksPublicKey, _] = PublicKey.findProgramAddressSync(\n        [program.provider.publicKey.toBuffer()],\n        PROGRAM_ID\n      );\n      try {\n        const tasks = await program.account.tasksAccount.fetch(tasksPublicKey);\n        setTasks(tasks.tasks);\n      } catch (e) {\n        console.warn(\n          'Your account does not yet exist. Save your ToDos to Solana to create an account.',\n          e\n        );\n      }\n    }\n  }\n\n  useEffect(() => {\n    loadTasksFromChain();\n  }, []);\n\n  function toggleTaskCompleted(id: number) {\n    const updatedTasks = tasks.map(task => {\n      if (id === task.id) {\n        return { ...task, completed: !task.completed };\n      }\n      return task;\n    });\n    setTasks(updatedTasks);\n  }\n\n  function deleteTask(id: number) {\n    const remainingTasks = tasks.filter(task => id !== task.id);\n    setTasks(remainingTasks);\n  }\n\n  function editTask(id: number, newName: string) {\n    const editedTaskList = tasks.map(task => {\n      if (id === task.id) {\n        return { ...task, name: newName };\n      }\n      return task;\n    });\n    setTasks(editedTaskList);\n  }\n\n  function addTask(name: string) {\n    const id = Math.floor(Math.random() * 1_000_000);\n    const newTask = { id, name, completed: false };\n    setTasks([...tasks, newTask]);\n  }\n\n  async function saveTasksToChain() {\n    if (program && program.provider.publicKey) {\n      const [tasksPublicKey, _] = PublicKey.findProgramAddressSync(\n        [program.provider.publicKey.toBuffer()],\n        PROGRAM_ID\n      );\n      await program.methods\n        .saveTasks(tasks)\n        .accounts({\n          tasks: tasksPublicKey\n        })\n        .rpc();\n    }\n  }\n\n  const taskList = tasks\n    .filter(FILTER_MAP[filter])\n    .map(task => (\n      <TodoC\n        id={task.id}\n        name={task.name}\n        completed={task.completed}\n        key={task.id}\n        toggleTaskCompleted={toggleTaskCompleted}\n        deleteTask={deleteTask}\n        editTask={editTask}\n      />\n    ));\n\n  const tasksNoun = taskList.length !== 1 ? 'tasks' : 'task';\n  const headingText = `${taskList.length} ${tasksNoun} remaining`;\n\n  const listHeadingRef = useRef<HTMLHeadingElement>(null);\n  const prevTaskLength = usePrevious(tasks.length) ?? 0;\n\n  useEffect(() => {\n    if (tasks.length - prevTaskLength === -1) {\n      listHeadingRef?.current?.focus();\n    }\n  }, [tasks.length, prevTaskLength]);\n\n  return (\n    <div className='todoapp stack-large'>\n      <h1>ToDos</h1>\n      <Form addTask={addTask} />\n      <div className='filters btn-group stack-exception'>\n        {FILTER_NAMES.map(name => (\n          <FilterButton\n            key={name}\n            name={name}\n            isPressed={name === filter}\n            setFilter={setFilter}\n          />\n        ))}\n      </div>\n      <button className='btn' onClick={saveTasksToChain}>\n        Save to Chain\n      </button>\n      <h2 id='list-heading' ref={listHeadingRef}>\n        {headingText}\n      </h2>\n      <ul\n        role='list'\n        className='todo-list stack-large stack-exception'\n        aria-labelledby='list-heading'\n      >\n        {taskList}\n      </ul>\n    </div>\n  );\n}\n\nfunction TodoC({\n  name,\n  id,\n  editTask,\n  toggleTaskCompleted,\n  deleteTask,\n  completed\n}: TodoT) {\n  const [isEditing, setEditing] = useState(false);\n  const [newName, setNewName] = useState('');\n\n  const editFieldRef = useRef<HTMLInputElement>(null);\n  const editButtonRef = useRef<HTMLButtonElement>(null);\n\n  const wasEditing = usePrevious(isEditing);\n\n  function handleChange(e: ChangeEvent<HTMLInputElement>) {\n    setNewName(e.target.value);\n  }\n\n  function handleSubmit(e: FormEvent<HTMLFormElement>) {\n    e.preventDefault();\n    if (!newName.trim()) {\n      return;\n    }\n    editTask(id, newName);\n    setNewName('');\n    setEditing(false);\n  }\n\n  const editingTemplate = (\n    <form className='stack-small' onSubmit={handleSubmit}>\n      <div className='form-group'>\n        <label className='todo-label' htmlFor={id.toString()}>\n          New name for {name}\n        </label>\n        <input\n          id={id.toString()}\n          className='todo-text'\n          type='text'\n          value={newName || name}\n          onChange={handleChange}\n          ref={editFieldRef}\n        />\n      </div>\n      <div className='btn-group'>\n        <button\n          type='button'\n          className='btn todo-cancel'\n          onClick={() => setEditing(false)}\n        >\n          Cancel\n          <span className='visually-hidden'>renaming {name}</span>\n        </button>\n        <button type='submit' className='btn btn__primary todo-edit'>\n          Save\n          <span className='visually-hidden'>new name for {name}</span>\n        </button>\n      </div>\n    </form>\n  );\n\n  const viewTemplate = (\n    <div className='stack-small'>\n      <div className='c-cb'>\n        <input\n          id={id.toString()}\n          type='checkbox'\n          defaultChecked={completed}\n          onChange={() => toggleTaskCompleted(id)}\n        />\n        <label className='todo-label' htmlFor={id.toString()}>\n          {name}\n        </label>\n      </div>\n      <div className='btn-group'>\n        <button\n          type='button'\n          className='btn'\n          onClick={() => setEditing(true)}\n          ref={editButtonRef}\n        >\n          Edit <span className='visually-hidden'>{name}</span>\n        </button>\n        <button\n          type='button'\n          className='btn btn__danger'\n          onClick={() => deleteTask(id)}\n        >\n          Delete <span className='visually-hidden'>{name}</span>\n        </button>\n      </div>\n    </div>\n  );\n\n  useEffect(() => {\n    if (!wasEditing && isEditing) {\n      editFieldRef?.current?.focus();\n    }\n    if (wasEditing && !isEditing) {\n      editButtonRef?.current?.focus();\n    }\n  }, [wasEditing, isEditing]);\n\n  return <li className='todo'>{isEditing ? editingTemplate : viewTemplate}</li>;\n}\n\nfunction Form({ addTask }: FormT) {\n  const [name, setName] = useState('');\n  function handleSubmit(e: FormEvent<HTMLFormElement>) {\n    e.preventDefault();\n    if (!name) return;\n    addTask(name);\n    setName('');\n  }\n  function handleChange(e: ChangeEvent<HTMLInputElement>) {\n    setName(e.target.value);\n  }\n  return (\n    <form onSubmit={handleSubmit}>\n      <h2 className='label-wrapper'>\n        <label htmlFor='new-todo-input' className='label__lg'>\n          What todo?\n        </label>\n      </h2>\n      <input\n        type='text'\n        id='new-todo-input'\n        className='input input__lg'\n        name='text'\n        autoComplete='off'\n        value={name}\n        onChange={handleChange}\n      />\n      <button type='submit' className='btn btn__primary btn__lg'>\n        Add\n      </button>\n    </form>\n  );\n}\n\nfunction FilterButton({ name, isPressed, setFilter }: FilterButtonT) {\n  return (\n    <button\n      type='button'\n      className='btn toggle-btn'\n      aria-pressed={isPressed}\n      onClick={() => setFilter(name)}\n    >\n      <span className='visually-hidden'>Show </span>\n      <span>{name}</span>\n      <span className='visually-hidden'> tasks</span>\n    </button>\n  );\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/index.css",
    "content": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n  color-scheme: light dark;\n  color: rgba(255, 255, 255, 0.87);\n  background-color: #242424;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-text-size-adjust: 100%;\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './app';\nimport './index.css';\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/utils.ts",
    "content": "import { Wallet } from '@coral-xyz/anchor';\nimport { useEffect, useRef } from 'react';\n\nexport type Task = {\n  id: number;\n  name: string;\n  completed: boolean;\n};\n\nexport type TodoT = {\n  name: string;\n  id: number;\n  completed: boolean;\n  toggleTaskCompleted: (id: number) => void;\n  deleteTask: (id: number) => void;\n  editTask: (id: number, newName: string) => void;\n};\n\nexport type FormT = {\n  addTask: (name: string) => void;\n};\n\nexport type FilterButtonT = {\n  name: Filters;\n  isPressed: boolean;\n  setFilter: (name: Filters) => void;\n};\n\nexport type Filters = keyof typeof FILTER_MAP;\n\nexport const FILTER_MAP = {\n  All: () => true,\n  Active: (task: Task) => !task.completed,\n  Completed: (task: Task) => task.completed\n} as const;\n\nexport const FILTER_NAMES = Object.keys(FILTER_MAP) as Array<\n  keyof typeof FILTER_MAP\n>;\n\nexport function usePrevious<T>(value: T) {\n  const ref = useRef<T>();\n  useEffect(() => {\n    ref.current = value;\n  });\n  return ref.current;\n}\n\nexport function isWalletConnected(wallet: any): wallet is Wallet {\n  return wallet.publicKey !== null;\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/app/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n  envDir: '../'\n});\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/migrations/deploy.ts",
    "content": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked from the CLI, injecting a provider\n// configured from the workspace's Anchor.toml.\n\nconst anchor = require(\"@coral-xyz/anchor\");\n\nmodule.exports = async function (provider) {\n  // Configure client to use the provider.\n  anchor.setProvider(provider);\n\n  // Add your deploy script here.\n};\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/package.json",
    "content": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" --check\"\n  },\n  \"dependencies\": {\n    \"@coral-xyz/anchor\": \"0.28.1-beta.1\"\n  },\n  \"devDependencies\": {\n    \"chai\": \"4.3.10\",\n    \"mocha\": \"10.2.0\",\n    \"ts-mocha\": \"10.0.0\",\n    \"@types/bn.js\": \"5.1.3\",\n    \"@types/chai\": \"4.3.9\",\n    \"@types/mocha\": \"10.0.1\",\n    \"typescript\": \"5.1.6\",\n    \"prettier\": \"3.0.1\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/programs/todo/Cargo.toml",
    "content": "[package]\nname = \"todo\"\nversion = \"0.1.0\"\ndescription = \"A todo list program\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\nname = \"todo\"\n\n[features]\nno-entrypoint = []\nno-idl = []\nno-log-ix-name = []\ncpi = [\"no-entrypoint\"]\ndefault = []\n\n[dependencies]\nanchor-lang = { version = \"0.28.0\", features = [\"init-if-needed\"] }\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/programs/todo/Xargo.toml",
    "content": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/programs/todo/src/lib.rs",
    "content": "use anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\nconst TASK_SIZE: usize = 4 + (4 + 32) + 1;\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        let tasks = &mut ctx.accounts.tasks;\n\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        // If length of tasks is not equal to the length of replacing_tasks, then\n        // reallocate the tasks account.\n        if tasks.tasks.len() < replacing_tasks.len() {\n            let new_space = 8 + TASK_SIZE * replacing_tasks.len();\n\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n\n            let tasks_account_info = tasks.to_account_info();\n\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n            **ctx\n                .accounts\n                .user\n                .to_account_info()\n                .try_borrow_mut_lamports()? -= lamports_diff;\n            **tasks_account_info.try_borrow_mut_lamports()? += lamports_diff;\n\n            // Allocate the new space for the tasks account.\n            tasks_account_info.realloc(new_space, false)?;\n        }\n\n        tasks.tasks = replacing_tasks;\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * TASK_SIZE, payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\n#[derive(Debug)]\npub struct TasksAccount {\n    tasks: Vec<Task>,\n}\n\n#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    /// The name of the task. Max 32 characters, min 1 character.\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    #[msg(\"The task name must be less than 32 characters.\")]\n    TaskNameTooLong,\n    #[msg(\"The task name must be at least 1 character.\")]\n    TaskNameTooShort,\n    #[msg(\"The task id must be unique.\")]\n    TaskIdNotUnique,\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/tests/todo.ts",
    "content": "import * as anchor from '@coral-xyz/anchor';\nimport { Program } from '@coral-xyz/anchor';\nimport { Todo } from '../target/types/todo';\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\n\ndescribe('todo', () => {\n  // Configure the client to use the local cluster.\n  anchor.setProvider(anchor.AnchorProvider.env());\n\n  const program = anchor.workspace.Todo as Program<Todo>;\n  const connection = new Connection('http://localhost:8899', 'confirmed');\n  it('saves a new task', async () => {\n    const user = Keypair.generate();\n\n    const sig = await connection.requestAirdrop(user.publicKey, 10_000_000_000);\n    await connection.confirmTransaction(sig);\n\n    const [tasksPublicKey, _] = PublicKey.findProgramAddressSync(\n      [user.publicKey.toBuffer()],\n      program.programId\n    );\n    const _tx = await program.methods\n      .saveTasks([\n        {\n          id: 1,\n          name: 'example',\n          completed: false\n        }\n      ])\n      .accounts({ user: user.publicKey, tasks: tasksPublicKey })\n      .signers([user])\n      .rpc({ skipPreflight: true });\n    const tasks = await program.account.tasksAccount.fetch(tasksPublicKey);\n\n    console.log('tasks', tasks);\n  });\n});\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/_answer/tsconfig.json",
    "content": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules/@types\"],\n              \"lib\": [\"es2015\"],\n              \"module\": \"commonjs\",\n              \"target\": \"es6\",\n              \"esModuleInterop\": true\n            }\n          }\n          "
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/.gitignore",
    "content": "\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",
    "content": "\n.anchor\n.DS_Store\ntarget\nnode_modules\ndist\nbuild\ntest-ledger\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/Anchor.toml",
    "content": "[features]\nseeds = false\nskip-lint = false\nskip-preflight = true\n[programs.localnet]\ntodo = \"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\"\n\n[registry]\nurl = \"https://api.apr.dev\"\n\n[provider]\ncluster = \"Localnet\"\nwallet = \"~/.config/solana/id.json\"\n\n[scripts]\ntest = \"yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts\"\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[profile.release.build-override]\nopt-level = 3\nincremental = false\ncodegen-units = 1\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>TODO</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/package.json",
    "content": "{\n  \"name\": \"todo\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"lint\": \"eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"18.2.31\",\n    \"@types/react-dom\": \"18.2.14\",\n    \"@typescript-eslint/eslint-plugin\": \"6.8.0\",\n    \"@typescript-eslint/parser\": \"6.8.0\",\n    \"@vitejs/plugin-react\": \"4.0.4\",\n    \"eslint\": \"8.52.0\",\n    \"eslint-plugin-react-hooks\": \"4.6.0\",\n    \"eslint-plugin-react-refresh\": \"0.4.3\",\n    \"typescript\": \"5.1.6\",\n    \"vite\": \"4.5.6\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/app.css",
    "content": "/* RESETS */\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n*:focus {\n  outline: 3px dashed #228bec;\n  outline-offset: 0;\n}\nhtml {\n  font: 62.5% / 1.15 sans-serif;\n}\nh1,\nh2 {\n  margin-bottom: 0;\n}\nul {\n  list-style: none;\n  padding: 0;\n}\nbutton {\n  border: none;\n  margin: 0;\n  padding: 0;\n  width: auto;\n  overflow: visible;\n  background: transparent;\n  color: inherit;\n  font: inherit;\n  line-height: normal;\n  -webkit-font-smoothing: inherit;\n  -moz-osx-font-smoothing: inherit;\n  appearance: none;\n}\nbutton::-moz-focus-inner {\n  border: 0;\n}\nbutton:hover {\n  cursor: pointer;\n}\nbutton,\ninput {\n  font-family: inherit;\n  font-size: 100%;\n  line-height: 1.15;\n  margin: 0;\n}\nbutton,\ninput {\n  overflow: visible;\n}\ninput[type='text'] {\n  border-radius: 0;\n}\nbody {\n  width: 100%;\n  max-width: 68rem;\n  margin: 0 auto;\n  font:\n    1.6rem/1.25 Arial,\n    sans-serif;\n  background-color: #f5f5f5;\n  color: #4d4d4d;\n}\n@media screen and (min-width: 620px) {\n  body {\n    font-size: 1.9rem;\n    line-height: 1.31579;\n  }\n}\n/*END RESETS*/\n/* GLOBAL STYLES */\n.btn {\n  padding: 0.8rem 1rem 0.7rem;\n  border: 0.2rem solid #4d4d4d;\n  cursor: pointer;\n  text-transform: capitalize;\n}\n.btn.toggle-btn {\n  border-width: 1px;\n  border-color: #d3d3d3;\n}\n.btn.toggle-btn[aria-pressed='true'] {\n  text-decoration: underline;\n  border-color: #4d4d4d;\n}\n.btn__danger {\n  color: #fff;\n  background-color: #ca3c3c;\n  border-color: #bd2130;\n}\n.btn__primary {\n  color: #fff;\n  background-color: #000;\n}\n.btn-group {\n  display: flex;\n  justify-content: space-between;\n}\n.btn-group > * {\n  flex: 1 1 49%;\n}\n.btn-group > * + * {\n  margin-left: 0.8rem;\n}\n.label-wrapper {\n  margin: 0;\n  flex: 0 0 100%;\n  text-align: center;\n}\n.visually-hidden {\n  position: absolute !important;\n  height: 1px;\n  width: 1px;\n  overflow: hidden;\n  clip: rect(1px 1px 1px 1px);\n  clip: rect(1px, 1px, 1px, 1px);\n  white-space: nowrap;\n}\n[class*='stack'] > * {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.stack-small > * + * {\n  margin-top: 1.25rem;\n}\n.stack-large > * + * {\n  margin-top: 2.5rem;\n}\n@media screen and (min-width: 550px) {\n  .stack-small > * + * {\n    margin-top: 1.4rem;\n  }\n  .stack-large > * + * {\n    margin-top: 2.8rem;\n  }\n}\n.stack-exception {\n  margin-top: 1.2rem;\n}\n/* END GLOBAL STYLES */\n.todoapp {\n  background: #fff;\n  margin: 2rem 0 4rem 0;\n  padding: 1rem;\n  position: relative;\n  box-shadow:\n    0 2px 4px 0 rgba(0, 0, 0, 0.2),\n    0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);\n}\n@media screen and (min-width: 550px) {\n  .todoapp {\n    padding: 4rem;\n  }\n}\n.todoapp > * {\n  max-width: 50rem;\n  margin-left: auto;\n  margin-right: auto;\n}\n.todoapp > form {\n  max-width: 100%;\n}\n.todoapp > h1 {\n  display: block;\n  max-width: 100%;\n  text-align: center;\n  margin: 0;\n  margin-bottom: 1rem;\n}\n.label__lg {\n  line-height: 1.01567;\n  font-weight: 300;\n  padding: 0.8rem;\n  margin-bottom: 1rem;\n  text-align: center;\n}\n.input__lg {\n  padding: 2rem;\n  border: 2px solid #000;\n}\n.input__lg:focus {\n  border-color: #4d4d4d;\n  box-shadow: inset 0 0 0 2px;\n}\n[class*='__lg'] {\n  display: inline-block;\n  width: 100%;\n  font-size: 1.9rem;\n}\n[class*='__lg']:not(:last-child) {\n  margin-bottom: 1rem;\n}\n@media screen and (min-width: 620px) {\n  [class*='__lg'] {\n    font-size: 2.4rem;\n  }\n}\n/* Todo item styles */\n.todo {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n}\n.todo > * {\n  flex: 0 0 100%;\n}\n.todo-text {\n  width: 100%;\n  min-height: 4.4rem;\n  padding: 0.4rem 0.8rem;\n  border: 2px solid #565656;\n}\n.todo-text:focus {\n  box-shadow: inset 0 0 0 2px;\n}\n/* CHECKBOX STYLES */\n.c-cb {\n  box-sizing: border-box;\n  font-family: Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  font-weight: 400;\n  font-size: 1.6rem;\n  line-height: 1.25;\n  display: block;\n  position: relative;\n  min-height: 44px;\n  padding-left: 40px;\n  clear: left;\n}\n.c-cb > label::before,\n.c-cb > input[type='checkbox'] {\n  box-sizing: border-box;\n  top: -2px;\n  left: -2px;\n  width: 44px;\n  height: 44px;\n}\n.c-cb > input[type='checkbox'] {\n  -webkit-font-smoothing: antialiased;\n  cursor: pointer;\n  position: absolute;\n  z-index: 1;\n  margin: 0;\n  opacity: 0;\n}\n.c-cb > label {\n  font-size: inherit;\n  font-family: inherit;\n  line-height: inherit;\n  display: inline-block;\n  margin-bottom: 0;\n  padding: 8px 15px 5px;\n  cursor: pointer;\n  touch-action: manipulation;\n}\n.c-cb > label::before {\n  content: '';\n  position: absolute;\n  border: 2px solid currentcolor;\n  background: transparent;\n}\n.c-cb > input[type='checkbox']:focus + label::before {\n  border-width: 4px;\n  outline: 3px dashed #228bec;\n}\n.c-cb > label::after {\n  box-sizing: content-box;\n  content: '';\n  position: absolute;\n  top: 11px;\n  left: 9px;\n  width: 18px;\n  height: 7px;\n  transform: rotate(-45deg);\n  border: solid;\n  border-width: 0 0 5px 5px;\n  border-top-color: transparent;\n  opacity: 0;\n  background: transparent;\n}\n.c-cb > input[type='checkbox']:checked + label::after {\n  opacity: 1;\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/app.tsx",
    "content": "import {\n  ChangeEvent,\n  FormEvent,\n  MouseEventHandler,\n  useEffect,\n  useRef,\n  useState\n} from 'react';\nimport './app.css';\nimport {\n  FILTER_MAP,\n  FILTER_NAMES,\n  FilterButtonT,\n  Filters,\n  FormT,\n  Task,\n  TodoT,\n  usePrevious\n} from './utils';\n\n// TODO:1 Attach Buffer to window\n// TODO:2 Create PROGRAM_ID\n// TODO:3 Get ENDPOINT from env\n// TODO:4 Create Connection\n// TODO:5 Create wallet adapter\n// TODO:6 Create ProgramContext from IDL\n\nexport function App() {\n  // TODO:7 Initialise program state\n\n  const connectWallet: MouseEventHandler<HTMLButtonElement> = async e => {\n    e.preventDefault();\n    // TODO:8 Connect wallet\n    // TODO:9 Check if wallet is connected\n    // TODO:10 Create provider\n    // TODO:11 Create program\n    // TODO:12 Set program\n  };\n\n  return (\n    // TODO:14 Wrap page in program context provider\n    <>\n      {/* TODO:13 If program is set, show landing page, otherwise show login page */}\n    </>\n  );\n}\n\nfunction LogIn({\n  connectWallet\n}: {\n  connectWallet: MouseEventHandler<HTMLButtonElement>;\n}) {\n  return (\n    <div className='todoapp stack-large'>\n      <h1>Connect and Login</h1>\n      <button onClick={connectWallet} className='btn'>\n        Connect Wallet\n      </button>\n    </div>\n  );\n}\n\nfunction Landing() {\n  const [tasks, setTasks] = useState<Task[]>([]);\n  const [filter, setFilter] = useState<Filters>('All');\n  // TODO:15 Use program context\n\n  async function loadTasksFromChain() {\n    // TODO:16 If program is connected,\n    // TODO:17 Derive tasks account public key\n    // TODO:18 Fetch the tasksAccount data\n    // TODO:19 Set tasks state\n  }\n\n  useEffect(() => {\n    loadTasksFromChain();\n  }, []);\n\n  function toggleTaskCompleted(id: number) {\n    const updatedTasks = tasks.map(task => {\n      if (id === task.id) {\n        return { ...task, completed: !task.completed };\n      }\n      return task;\n    });\n    setTasks(updatedTasks);\n  }\n\n  function deleteTask(id: number) {\n    const remainingTasks = tasks.filter(task => id !== task.id);\n    setTasks(remainingTasks);\n  }\n\n  function editTask(id: number, newName: string) {\n    const editedTaskList = tasks.map(task => {\n      if (id === task.id) {\n        return { ...task, name: newName };\n      }\n      return task;\n    });\n    setTasks(editedTaskList);\n  }\n\n  function addTask(name: string) {\n    const id = Math.floor(Math.random() * 1_000_000);\n    const newTask = { id, name, completed: false };\n    setTasks([...tasks, newTask]);\n  }\n\n  async function saveTasksToChain() {\n    // TODO:20 If program exists, derive tasks account public key, and initiate `save_tasks` instruction\n  }\n\n  const taskList = tasks\n    .filter(FILTER_MAP[filter])\n    .map(task => (\n      <TodoC\n        id={task.id}\n        name={task.name}\n        completed={task.completed}\n        key={task.id}\n        toggleTaskCompleted={toggleTaskCompleted}\n        deleteTask={deleteTask}\n        editTask={editTask}\n      />\n    ));\n\n  const tasksNoun = taskList.length !== 1 ? 'tasks' : 'task';\n  const headingText = `${taskList.length} ${tasksNoun} remaining`;\n\n  const listHeadingRef = useRef<HTMLHeadingElement>(null);\n  const prevTaskLength = usePrevious(tasks.length) ?? 0;\n\n  useEffect(() => {\n    if (tasks.length - prevTaskLength === -1) {\n      listHeadingRef?.current?.focus();\n    }\n  }, [tasks.length, prevTaskLength]);\n\n  return (\n    <div className='todoapp stack-large'>\n      <h1>ToDos</h1>\n      <Form addTask={addTask} />\n      <div className='filters btn-group stack-exception'>\n        {FILTER_NAMES.map(name => (\n          <FilterButton\n            key={name}\n            name={name}\n            isPressed={name === filter}\n            setFilter={setFilter}\n          />\n        ))}\n      </div>\n      <button className='btn' onClick={saveTasksToChain}>\n        Save to Chain\n      </button>\n      <h2 id='list-heading' ref={listHeadingRef}>\n        {headingText}\n      </h2>\n      <ul\n        role='list'\n        className='todo-list stack-large stack-exception'\n        aria-labelledby='list-heading'\n      >\n        {taskList}\n      </ul>\n    </div>\n  );\n}\n\nfunction TodoC({\n  name,\n  id,\n  editTask,\n  toggleTaskCompleted,\n  deleteTask,\n  completed\n}: TodoT) {\n  const [isEditing, setEditing] = useState(false);\n  const [newName, setNewName] = useState('');\n\n  const editFieldRef = useRef<HTMLInputElement>(null);\n  const editButtonRef = useRef<HTMLButtonElement>(null);\n\n  const wasEditing = usePrevious(isEditing);\n\n  function handleChange(e: ChangeEvent<HTMLInputElement>) {\n    setNewName(e.target.value);\n  }\n\n  function handleSubmit(e: FormEvent<HTMLFormElement>) {\n    e.preventDefault();\n    if (!newName.trim()) {\n      return;\n    }\n    editTask(id, newName);\n    setNewName('');\n    setEditing(false);\n  }\n\n  const editingTemplate = (\n    <form className='stack-small' onSubmit={handleSubmit}>\n      <div className='form-group'>\n        <label className='todo-label' htmlFor={id.toString()}>\n          New name for {name}\n        </label>\n        <input\n          id={id.toString()}\n          className='todo-text'\n          type='text'\n          value={newName || name}\n          onChange={handleChange}\n          ref={editFieldRef}\n        />\n      </div>\n      <div className='btn-group'>\n        <button\n          type='button'\n          className='btn todo-cancel'\n          onClick={() => setEditing(false)}\n        >\n          Cancel\n          <span className='visually-hidden'>renaming {name}</span>\n        </button>\n        <button type='submit' className='btn btn__primary todo-edit'>\n          Save\n          <span className='visually-hidden'>new name for {name}</span>\n        </button>\n      </div>\n    </form>\n  );\n\n  const viewTemplate = (\n    <div className='stack-small'>\n      <div className='c-cb'>\n        <input\n          id={id.toString()}\n          type='checkbox'\n          defaultChecked={completed}\n          onChange={() => toggleTaskCompleted(id)}\n        />\n        <label className='todo-label' htmlFor={id.toString()}>\n          {name}\n        </label>\n      </div>\n      <div className='btn-group'>\n        <button\n          type='button'\n          className='btn'\n          onClick={() => setEditing(true)}\n          ref={editButtonRef}\n        >\n          Edit <span className='visually-hidden'>{name}</span>\n        </button>\n        <button\n          type='button'\n          className='btn btn__danger'\n          onClick={() => deleteTask(id)}\n        >\n          Delete <span className='visually-hidden'>{name}</span>\n        </button>\n      </div>\n    </div>\n  );\n\n  useEffect(() => {\n    if (!wasEditing && isEditing) {\n      editFieldRef?.current?.focus();\n    }\n    if (wasEditing && !isEditing) {\n      editButtonRef?.current?.focus();\n    }\n  }, [wasEditing, isEditing]);\n\n  return <li className='todo'>{isEditing ? editingTemplate : viewTemplate}</li>;\n}\n\nfunction Form({ addTask }: FormT) {\n  const [name, setName] = useState('');\n  function handleSubmit(e: FormEvent<HTMLFormElement>) {\n    e.preventDefault();\n    if (!name) return;\n    addTask(name);\n    setName('');\n  }\n  function handleChange(e: ChangeEvent<HTMLInputElement>) {\n    setName(e.target.value);\n  }\n  return (\n    <form onSubmit={handleSubmit}>\n      <h2 className='label-wrapper'>\n        <label htmlFor='new-todo-input' className='label__lg'>\n          What todo?\n        </label>\n      </h2>\n      <input\n        type='text'\n        id='new-todo-input'\n        className='input input__lg'\n        name='text'\n        autoComplete='off'\n        value={name}\n        onChange={handleChange}\n      />\n      <button type='submit' className='btn btn__primary btn__lg'>\n        Add\n      </button>\n    </form>\n  );\n}\n\nfunction FilterButton({ name, isPressed, setFilter }: FilterButtonT) {\n  return (\n    <button\n      type='button'\n      className='btn toggle-btn'\n      aria-pressed={isPressed}\n      onClick={() => setFilter(name)}\n    >\n      <span className='visually-hidden'>Show </span>\n      <span>{name}</span>\n      <span className='visually-hidden'> tasks</span>\n    </button>\n  );\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/index.css",
    "content": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n  color-scheme: light dark;\n  color: rgba(255, 255, 255, 0.87);\n  background-color: #242424;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-text-size-adjust: 100%;\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './app';\nimport './index.css';\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/utils.ts",
    "content": "import { Wallet } from '@coral-xyz/anchor';\nimport { useEffect, useRef } from 'react';\n\nexport type Task = {\n  id: number;\n  name: string;\n  completed: boolean;\n};\n\nexport type TodoT = {\n  name: string;\n  id: number;\n  completed: boolean;\n  toggleTaskCompleted: (id: number) => void;\n  deleteTask: (id: number) => void;\n  editTask: (id: number, newName: string) => void;\n};\n\nexport type FormT = {\n  addTask: (name: string) => void;\n};\n\nexport type FilterButtonT = {\n  name: Filters;\n  isPressed: boolean;\n  setFilter: (name: Filters) => void;\n};\n\nexport type Filters = keyof typeof FILTER_MAP;\n\nexport const FILTER_MAP = {\n  All: () => true,\n  Active: (task: Task) => !task.completed,\n  Completed: (task: Task) => task.completed\n} as const;\n\nexport const FILTER_NAMES = Object.keys(FILTER_MAP) as Array<\n  keyof typeof FILTER_MAP\n>;\n\nexport function usePrevious<T>(value: T) {\n  const ref = useRef<T>();\n  useEffect(() => {\n    ref.current = value;\n  });\n  return ref.current;\n}\n\nexport function isWalletConnected(wallet: any): wallet is Wallet {\n  return wallet.publicKey !== null;\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/app/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n  envDir: '../'\n});\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/migrations/deploy.ts",
    "content": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked from the CLI, injecting a provider\n// configured from the workspace's Anchor.toml.\n\nconst anchor = require(\"@coral-xyz/anchor\");\n\nmodule.exports = async function (provider) {\n  // Configure client to use the provider.\n  anchor.setProvider(provider);\n\n  // Add your deploy script here.\n};\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/package.json",
    "content": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" --check\"\n  },\n  \"dependencies\": {\n    \"@coral-xyz/anchor\": \"0.28.1-beta.1\"\n  },\n  \"devDependencies\": {\n    \"chai\": \"4.3.10\",\n    \"mocha\": \"10.2.0\",\n    \"ts-mocha\": \"10.0.0\",\n    \"@types/bn.js\": \"5.1.3\",\n    \"@types/chai\": \"4.3.9\",\n    \"@types/mocha\": \"10.0.1\",\n    \"typescript\": \"5.1.6\",\n    \"prettier\": \"3.0.1\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/programs/todo/Cargo.toml",
    "content": "[package]\nname = \"todo\"\nversion = \"0.1.0\"\ndescription = \"A todo list program\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\nname = \"todo\"\n\n[features]\nno-entrypoint = []\nno-idl = []\nno-log-ix-name = []\ncpi = [\"no-entrypoint\"]\ndefault = []\n\n[dependencies]\nanchor-lang = { version = \"0.28.0\", features = [] }\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/programs/todo/Xargo.toml",
    "content": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/programs/todo/src/lib.rs",
    "content": "use anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\npub struct Initialize {}\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/tests/todo.ts",
    "content": "import * as anchor from '@coral-xyz/anchor';\nimport { Program } from '@coral-xyz/anchor';\nimport { Todo } from '../target/types/todo';\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\n\ndescribe('todo', () => {\n  // Configure the client to use the local cluster.\n  anchor.setProvider(anchor.AnchorProvider.env());\n\n  const program = anchor.workspace.Todo as Program<Todo>;\n  const connection = new Connection('http://localhost:8899', 'confirmed');\n  it('saves a new task', async () => {\n    const user = Keypair.generate();\n\n    const sig = await connection.requestAirdrop(user.publicKey, 10_000_000_000);\n    await connection.confirmTransaction(sig);\n\n    const [tasksPublicKey, _] = PublicKey.findProgramAddressSync(\n      [user.publicKey.toBuffer()],\n      program.programId\n    );\n    const _tx = await program.methods\n      .saveTasks([\n        {\n          id: 1,\n          name: 'example',\n          completed: false\n        }\n      ])\n      .accounts({ user: user.publicKey, tasks: tasksPublicKey })\n      .signers([user])\n      .rpc({ skipPreflight: true });\n    const tasks = await program.account.tasksAccount.fetch(tasksPublicKey);\n\n    console.log('tasks', tasks);\n  });\n});\n"
  },
  {
    "path": "learn-how-to-build-for-mainnet/todo/tsconfig.json",
    "content": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules/@types\"],\n              \"lib\": [\"es2015\"],\n              \"module\": \"commonjs\",\n              \"target\": \"es6\",\n              \"esModuleInterop\": true\n            }\n          }\n          "
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/Anchor.toml",
    "content": "[features]\nseeds = false\nskip-lint = false\n[programs.localnet]\ntodo = \"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\"\n\n[registry]\nurl = \"https://api.apr.dev\"\n\n[provider]\ncluster = \"Localnet\"\nwallet = \"~/.config/solana/id.json\"\n\n[scripts]\ntest = \"yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts\"\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"programs/*\"\n]\n\n[profile.release]\noverflow-checks = true\nlto = \"fat\"\ncodegen-units = 1\n[profile.release.build-override]\nopt-level = 3\nincremental = false\ncodegen-units = 1\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>TODO</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/package.json",
    "content": "{\n  \"name\": \"todo\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"lint\": \"eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@coral-xyz/anchor\": \"0.28.1-beta.1\",\n    \"@solana/wallet-adapter-phantom\": \"0.9.24\",\n    \"@solana/web3.js\": \"1.87.7\",\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"18.2.31\",\n    \"@types/react-dom\": \"18.2.14\",\n    \"@typescript-eslint/eslint-plugin\": \"6.8.0\",\n    \"@typescript-eslint/parser\": \"6.8.0\",\n    \"@vitejs/plugin-react\": \"4.0.4\",\n    \"eslint\": \"8.52.0\",\n    \"eslint-plugin-react-hooks\": \"4.6.0\",\n    \"eslint-plugin-react-refresh\": \"0.4.3\",\n    \"typescript\": \"5.1.6\",\n    \"vite\": \"4.5.6\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/app.css",
    "content": "/* RESETS */\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n*:focus {\n  outline: 3px dashed #228bec;\n  outline-offset: 0;\n}\nhtml {\n  font: 62.5% / 1.15 sans-serif;\n}\nh1,\nh2 {\n  margin-bottom: 0;\n}\nul {\n  list-style: none;\n  padding: 0;\n}\nbutton {\n  border: none;\n  margin: 0;\n  padding: 0;\n  width: auto;\n  overflow: visible;\n  background: transparent;\n  color: inherit;\n  font: inherit;\n  line-height: normal;\n  -webkit-font-smoothing: inherit;\n  -moz-osx-font-smoothing: inherit;\n  appearance: none;\n}\nbutton::-moz-focus-inner {\n  border: 0;\n}\nbutton:hover {\n  cursor: pointer;\n}\nbutton,\ninput {\n  font-family: inherit;\n  font-size: 100%;\n  line-height: 1.15;\n  margin: 0;\n}\nbutton,\ninput {\n  overflow: visible;\n}\ninput[type='text'] {\n  border-radius: 0;\n}\nbody {\n  width: 100%;\n  max-width: 68rem;\n  margin: 0 auto;\n  font:\n    1.6rem/1.25 Arial,\n    sans-serif;\n  background-color: #f5f5f5;\n  color: #4d4d4d;\n}\n@media screen and (min-width: 620px) {\n  body {\n    font-size: 1.9rem;\n    line-height: 1.31579;\n  }\n}\n/*END RESETS*/\n/* GLOBAL STYLES */\n.btn {\n  padding: 0.8rem 1rem 0.7rem;\n  border: 0.2rem solid #4d4d4d;\n  cursor: pointer;\n  text-transform: capitalize;\n}\n.btn.toggle-btn {\n  border-width: 1px;\n  border-color: #d3d3d3;\n}\n.btn.toggle-btn[aria-pressed='true'] {\n  text-decoration: underline;\n  border-color: #4d4d4d;\n}\n.btn__danger {\n  color: #fff;\n  background-color: #ca3c3c;\n  border-color: #bd2130;\n}\n.btn__primary {\n  color: #fff;\n  background-color: #000;\n}\n.btn-group {\n  display: flex;\n  justify-content: space-between;\n}\n.btn-group > * {\n  flex: 1 1 49%;\n}\n.btn-group > * + * {\n  margin-left: 0.8rem;\n}\n.label-wrapper {\n  margin: 0;\n  flex: 0 0 100%;\n  text-align: center;\n}\n.visually-hidden {\n  position: absolute !important;\n  height: 1px;\n  width: 1px;\n  overflow: hidden;\n  clip: rect(1px 1px 1px 1px);\n  clip: rect(1px, 1px, 1px, 1px);\n  white-space: nowrap;\n}\n[class*='stack'] > * {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.stack-small > * + * {\n  margin-top: 1.25rem;\n}\n.stack-large > * + * {\n  margin-top: 2.5rem;\n}\n@media screen and (min-width: 550px) {\n  .stack-small > * + * {\n    margin-top: 1.4rem;\n  }\n  .stack-large > * + * {\n    margin-top: 2.8rem;\n  }\n}\n.stack-exception {\n  margin-top: 1.2rem;\n}\n/* END GLOBAL STYLES */\n.todoapp {\n  background: #fff;\n  margin: 2rem 0 4rem 0;\n  padding: 1rem;\n  position: relative;\n  box-shadow:\n    0 2px 4px 0 rgba(0, 0, 0, 0.2),\n    0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);\n}\n@media screen and (min-width: 550px) {\n  .todoapp {\n    padding: 4rem;\n  }\n}\n.todoapp > * {\n  max-width: 50rem;\n  margin-left: auto;\n  margin-right: auto;\n}\n.todoapp > form {\n  max-width: 100%;\n}\n.todoapp > h1 {\n  display: block;\n  max-width: 100%;\n  text-align: center;\n  margin: 0;\n  margin-bottom: 1rem;\n}\n.label__lg {\n  line-height: 1.01567;\n  font-weight: 300;\n  padding: 0.8rem;\n  margin-bottom: 1rem;\n  text-align: center;\n}\n.input__lg {\n  padding: 2rem;\n  border: 2px solid #000;\n}\n.input__lg:focus {\n  border-color: #4d4d4d;\n  box-shadow: inset 0 0 0 2px;\n}\n[class*='__lg'] {\n  display: inline-block;\n  width: 100%;\n  font-size: 1.9rem;\n}\n[class*='__lg']:not(:last-child) {\n  margin-bottom: 1rem;\n}\n@media screen and (min-width: 620px) {\n  [class*='__lg'] {\n    font-size: 2.4rem;\n  }\n}\n/* Todo item styles */\n.todo {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n}\n.todo > * {\n  flex: 0 0 100%;\n}\n.todo-text {\n  width: 100%;\n  min-height: 4.4rem;\n  padding: 0.4rem 0.8rem;\n  border: 2px solid #565656;\n}\n.todo-text:focus {\n  box-shadow: inset 0 0 0 2px;\n}\n/* CHECKBOX STYLES */\n.c-cb {\n  box-sizing: border-box;\n  font-family: Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  font-weight: 400;\n  font-size: 1.6rem;\n  line-height: 1.25;\n  display: block;\n  position: relative;\n  min-height: 44px;\n  padding-left: 40px;\n  clear: left;\n}\n.c-cb > label::before,\n.c-cb > input[type='checkbox'] {\n  box-sizing: border-box;\n  top: -2px;\n  left: -2px;\n  width: 44px;\n  height: 44px;\n}\n.c-cb > input[type='checkbox'] {\n  -webkit-font-smoothing: antialiased;\n  cursor: pointer;\n  position: absolute;\n  z-index: 1;\n  margin: 0;\n  opacity: 0;\n}\n.c-cb > label {\n  font-size: inherit;\n  font-family: inherit;\n  line-height: inherit;\n  display: inline-block;\n  margin-bottom: 0;\n  padding: 8px 15px 5px;\n  cursor: pointer;\n  touch-action: manipulation;\n}\n.c-cb > label::before {\n  content: '';\n  position: absolute;\n  border: 2px solid currentcolor;\n  background: transparent;\n}\n.c-cb > input[type='checkbox']:focus + label::before {\n  border-width: 4px;\n  outline: 3px dashed #228bec;\n}\n.c-cb > label::after {\n  box-sizing: content-box;\n  content: '';\n  position: absolute;\n  top: 11px;\n  left: 9px;\n  width: 18px;\n  height: 7px;\n  transform: rotate(-45deg);\n  border: solid;\n  border-width: 0 0 5px 5px;\n  border-top-color: transparent;\n  opacity: 0;\n  background: transparent;\n}\n.c-cb > input[type='checkbox']:checked + label::after {\n  opacity: 1;\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/app.tsx",
    "content": "import {\n  ChangeEvent,\n  FormEvent,\n  MouseEventHandler,\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useState\n} from 'react';\nimport { IDL, Todo } from '../../target/types/todo';\nimport './app.css';\nimport { AnchorProvider, Program } from '@coral-xyz/anchor';\nimport { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';\nimport { Connection, PublicKey } from '@solana/web3.js';\nimport { Buffer } from 'buffer';\nimport {\n  isWalletConnected,\n  Task,\n  Filters,\n  FILTER_MAP,\n  usePrevious,\n  FILTER_NAMES,\n  TodoT,\n  FormT,\n  FilterButtonT\n} from './utils';\n\nwindow.Buffer = Buffer;\n\nconst PROGRAM_ID = new PublicKey(\n  import.meta.env.VITE_PROGRAM_ID ||\n    '9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2'\n);\nconst ENDPOINT =\n  import.meta.env.VITE_SOLANA_CONNECTION_URL || 'http://localhost:8899';\nconst connection = new Connection(ENDPOINT, 'confirmed');\nconst wallet = new PhantomWalletAdapter();\nconst ProgramContext = createContext<Program<Todo> | null>(null);\n\nexport function App() {\n  const [program, setProgram] = useState<Program<Todo> | null>(null);\n\n  const connectWallet: MouseEventHandler<HTMLButtonElement> = async e => {\n    e.preventDefault();\n    await wallet.connect();\n    console.log('Connected to: ', wallet.publicKey);\n    if (isWalletConnected(wallet)) {\n      const provider = new AnchorProvider(connection, wallet, {});\n      const program = new Program(IDL, PROGRAM_ID, provider);\n      setProgram(program);\n    }\n  };\n\n  return (\n    <ProgramContext.Provider value={program}>\n      {program ? <Landing /> : <LogIn connectWallet={connectWallet} />}\n    </ProgramContext.Provider>\n  );\n}\n\nfunction LogIn({\n  connectWallet\n}: {\n  connectWallet: MouseEventHandler<HTMLButtonElement>;\n}) {\n  return (\n    <div className='todoapp stack-large'>\n      <h1>Connect and Login</h1>\n      <button onClick={connectWallet} className='btn'>\n        Connect Wallet\n      </button>\n    </div>\n  );\n}\n\nfunction Landing() {\n  const [tasks, setTasks] = useState<Task[]>([]);\n  const [filter, setFilter] = useState<Filters>('All');\n  const program = useContext(ProgramContext);\n\n  async function loadTasksFromChain() {\n    if (program && program.provider.publicKey) {\n      const [tasksPublicKey, _] = PublicKey.findProgramAddressSync(\n        [program.provider.publicKey.toBuffer()],\n        PROGRAM_ID\n      );\n      try {\n        const tasks = await program.account.tasksAccount.fetch(tasksPublicKey);\n        setTasks(tasks.tasks);\n      } catch (e) {\n        console.warn(\n          'Your account does not yet exist. Save your ToDos to Solana to create an account.',\n          e\n        );\n      }\n    }\n  }\n\n  useEffect(() => {\n    loadTasksFromChain();\n  }, []);\n\n  function toggleTaskCompleted(id: number) {\n    const updatedTasks = tasks.map(task => {\n      if (id === task.id) {\n        return { ...task, completed: !task.completed };\n      }\n      return task;\n    });\n    setTasks(updatedTasks);\n  }\n\n  function deleteTask(id: number) {\n    const remainingTasks = tasks.filter(task => id !== task.id);\n    setTasks(remainingTasks);\n  }\n\n  function editTask(id: number, newName: string) {\n    const editedTaskList = tasks.map(task => {\n      if (id === task.id) {\n        return { ...task, name: newName };\n      }\n      return task;\n    });\n    setTasks(editedTaskList);\n  }\n\n  function addTask(name: string) {\n    const id = Math.floor(Math.random() * 1_000_000);\n    const newTask = { id, name, completed: false };\n    setTasks([...tasks, newTask]);\n  }\n\n  async function saveTasksToChain() {\n    if (program && program.provider.publicKey) {\n      const [tasksPublicKey, _] = PublicKey.findProgramAddressSync(\n        [program.provider.publicKey.toBuffer()],\n        PROGRAM_ID\n      );\n      await program.methods\n        .saveTasks(tasks)\n        .accounts({\n          tasks: tasksPublicKey\n        })\n        .rpc();\n    }\n  }\n\n  const taskList = tasks\n    .filter(FILTER_MAP[filter])\n    .map(task => (\n      <TodoC\n        id={task.id}\n        name={task.name}\n        completed={task.completed}\n        key={task.id}\n        toggleTaskCompleted={toggleTaskCompleted}\n        deleteTask={deleteTask}\n        editTask={editTask}\n      />\n    ));\n\n  const tasksNoun = taskList.length !== 1 ? 'tasks' : 'task';\n  const headingText = `${taskList.length} ${tasksNoun} remaining`;\n\n  const listHeadingRef = useRef<HTMLHeadingElement>(null);\n  const prevTaskLength = usePrevious(tasks.length) ?? 0;\n\n  useEffect(() => {\n    if (tasks.length - prevTaskLength === -1) {\n      listHeadingRef?.current?.focus();\n    }\n  }, [tasks.length, prevTaskLength]);\n\n  return (\n    <div className='todoapp stack-large'>\n      <h1>ToDos</h1>\n      <Form addTask={addTask} />\n      <div className='filters btn-group stack-exception'>\n        {FILTER_NAMES.map(name => (\n          <FilterButton\n            key={name}\n            name={name}\n            isPressed={name === filter}\n            setFilter={setFilter}\n          />\n        ))}\n      </div>\n      <button className='btn' onClick={saveTasksToChain}>\n        Save to Chain\n      </button>\n      <h2 id='list-heading' ref={listHeadingRef}>\n        {headingText}\n      </h2>\n      <ul\n        role='list'\n        className='todo-list stack-large stack-exception'\n        aria-labelledby='list-heading'\n      >\n        {taskList}\n      </ul>\n    </div>\n  );\n}\n\nfunction TodoC({\n  name,\n  id,\n  editTask,\n  toggleTaskCompleted,\n  deleteTask,\n  completed\n}: TodoT) {\n  const [isEditing, setEditing] = useState(false);\n  const [newName, setNewName] = useState('');\n\n  const editFieldRef = useRef<HTMLInputElement>(null);\n  const editButtonRef = useRef<HTMLButtonElement>(null);\n\n  const wasEditing = usePrevious(isEditing);\n\n  function handleChange(e: ChangeEvent<HTMLInputElement>) {\n    setNewName(e.target.value);\n  }\n\n  function handleSubmit(e: FormEvent<HTMLFormElement>) {\n    e.preventDefault();\n    if (!newName.trim()) {\n      return;\n    }\n    editTask(id, newName);\n    setNewName('');\n    setEditing(false);\n  }\n\n  const editingTemplate = (\n    <form className='stack-small' onSubmit={handleSubmit}>\n      <div className='form-group'>\n        <label className='todo-label' htmlFor={id.toString()}>\n          New name for {name}\n        </label>\n        <input\n          id={id.toString()}\n          className='todo-text'\n          type='text'\n          value={newName || name}\n          onChange={handleChange}\n          ref={editFieldRef}\n        />\n      </div>\n      <div className='btn-group'>\n        <button\n          type='button'\n          className='btn todo-cancel'\n          onClick={() => setEditing(false)}\n        >\n          Cancel\n          <span className='visually-hidden'>renaming {name}</span>\n        </button>\n        <button type='submit' className='btn btn__primary todo-edit'>\n          Save\n          <span className='visually-hidden'>new name for {name}</span>\n        </button>\n      </div>\n    </form>\n  );\n\n  const viewTemplate = (\n    <div className='stack-small'>\n      <div className='c-cb'>\n        <input\n          id={id.toString()}\n          type='checkbox'\n          defaultChecked={completed}\n          onChange={() => toggleTaskCompleted(id)}\n        />\n        <label className='todo-label' htmlFor={id.toString()}>\n          {name}\n        </label>\n      </div>\n      <div className='btn-group'>\n        <button\n          type='button'\n          className='btn'\n          onClick={() => setEditing(true)}\n          ref={editButtonRef}\n        >\n          Edit <span className='visually-hidden'>{name}</span>\n        </button>\n        <button\n          type='button'\n          className='btn btn__danger'\n          onClick={() => deleteTask(id)}\n        >\n          Delete <span className='visually-hidden'>{name}</span>\n        </button>\n      </div>\n    </div>\n  );\n\n  useEffect(() => {\n    if (!wasEditing && isEditing) {\n      editFieldRef?.current?.focus();\n    }\n    if (wasEditing && !isEditing) {\n      editButtonRef?.current?.focus();\n    }\n  }, [wasEditing, isEditing]);\n\n  return <li className='todo'>{isEditing ? editingTemplate : viewTemplate}</li>;\n}\n\nfunction Form({ addTask }: FormT) {\n  const [name, setName] = useState('');\n  function handleSubmit(e: FormEvent<HTMLFormElement>) {\n    e.preventDefault();\n    if (!name) return;\n    addTask(name);\n    setName('');\n  }\n  function handleChange(e: ChangeEvent<HTMLInputElement>) {\n    setName(e.target.value);\n  }\n  return (\n    <form onSubmit={handleSubmit}>\n      <h2 className='label-wrapper'>\n        <label htmlFor='new-todo-input' className='label__lg'>\n          What todo?\n        </label>\n      </h2>\n      <input\n        type='text'\n        id='new-todo-input'\n        className='input input__lg'\n        name='text'\n        autoComplete='off'\n        value={name}\n        onChange={handleChange}\n      />\n      <button type='submit' className='btn btn__primary btn__lg'>\n        Add\n      </button>\n    </form>\n  );\n}\n\nfunction FilterButton({ name, isPressed, setFilter }: FilterButtonT) {\n  return (\n    <button\n      type='button'\n      className='btn toggle-btn'\n      aria-pressed={isPressed}\n      onClick={() => setFilter(name)}\n    >\n      <span className='visually-hidden'>Show </span>\n      <span>{name}</span>\n      <span className='visually-hidden'> tasks</span>\n    </button>\n  );\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/index.css",
    "content": ":root {\n  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n  color-scheme: light dark;\n  color: rgba(255, 255, 255, 0.87);\n  background-color: #242424;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-text-size-adjust: 100%;\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { App } from './app';\nimport './index.css';\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/utils.ts",
    "content": "import { Wallet } from '@coral-xyz/anchor';\nimport { useEffect, useRef } from 'react';\n\nexport type Task = {\n  id: number;\n  name: string;\n  completed: boolean;\n};\n\nexport type TodoT = {\n  name: string;\n  id: number;\n  completed: boolean;\n  toggleTaskCompleted: (id: number) => void;\n  deleteTask: (id: number) => void;\n  editTask: (id: number, newName: string) => void;\n};\n\nexport type FormT = {\n  addTask: (name: string) => void;\n};\n\nexport type FilterButtonT = {\n  name: Filters;\n  isPressed: boolean;\n  setFilter: (name: Filters) => void;\n};\n\nexport type Filters = keyof typeof FILTER_MAP;\n\nexport const FILTER_MAP = {\n  All: () => true,\n  Active: (task: Task) => !task.completed,\n  Completed: (task: Task) => task.completed\n} as const;\n\nexport const FILTER_NAMES = Object.keys(FILTER_MAP) as Array<\n  keyof typeof FILTER_MAP\n>;\n\nexport function usePrevious<T>(value: T) {\n  const ref = useRef<T>();\n  useEffect(() => {\n    ref.current = value;\n  });\n  return ref.current;\n}\n\nexport function isWalletConnected(wallet: any): wallet is Wallet {\n  return wallet.publicKey !== null;\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/app/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n  envDir: '../'\n});\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/migrations/deploy.ts",
    "content": "// Migrations are an early feature. Currently, they're nothing more than this\n// single deploy script that's invoked from the CLI, injecting a provider\n// configured from the workspace's Anchor.toml.\n\nconst anchor = require(\"@coral-xyz/anchor\");\n\nmodule.exports = async function (provider) {\n  // Configure client to use the provider.\n  anchor.setProvider(provider);\n\n  // Add your deploy script here.\n};\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/package.json",
    "content": "{\n  \"scripts\": {\n    \"lint:fix\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" -w\",\n    \"lint\": \"prettier */*.js \\\"*/**/*{.js,.ts}\\\" --check\"\n  },\n  \"dependencies\": {\n    \"@coral-xyz/anchor\": \"0.28.1-beta.1\"\n  },\n  \"devDependencies\": {\n    \"chai\": \"4.3.10\",\n    \"mocha\": \"10.2.0\",\n    \"ts-mocha\": \"10.0.0\",\n    \"@types/bn.js\": \"5.1.3\",\n    \"@types/chai\": \"4.3.9\",\n    \"@types/mocha\": \"10.0.1\",\n    \"typescript\": \"5.1.6\",\n    \"prettier\": \"3.0.1\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/programs/todo/Cargo.toml",
    "content": "[package]\nname = \"todo\"\nversion = \"0.1.0\"\ndescription = \"A todo list program\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\nname = \"todo\"\n\n[features]\nno-entrypoint = []\nno-idl = []\nno-log-ix-name = []\ncpi = [\"no-entrypoint\"]\ndefault = []\n\n[dependencies]\nanchor-lang = { version = \"0.28.0\", features = [\"init-if-needed\"] }\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/programs/todo/Xargo.toml",
    "content": "[target.bpfel-unknown-unknown.dependencies.std]\nfeatures = []\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/programs/todo/src/lib.rs",
    "content": "use anchor_lang::prelude::*;\n\ndeclare_id!(\"9a43FDYE3S98dfN1rPAeavJT6MzBUEuF3bdX94zihQG2\");\n\nconst TASK_SIZE: usize = 4 + (4 + 32) + 1;\n\n#[program]\npub mod todo {\n    use super::*;\n\n    pub fn save_tasks(ctx: Context<SaveTasks>, replacing_tasks: Vec<Task>) -> Result<()> {\n        let tasks = &mut ctx.accounts.tasks;\n\n        // Check that the task name is not too long.\n        for task in replacing_tasks.iter() {\n            if task.name.len() > 32 {\n                return Err(ErrorCode::TaskNameTooLong.into());\n            }\n        }\n\n        // Check that the task name is not too short.\n        for task in replacing_tasks.iter() {\n            if task.name.len() < 1 {\n                return Err(ErrorCode::TaskNameTooShort.into());\n            }\n        }\n\n        // Check that the task id is unique.\n        for (i, task) in replacing_tasks.iter().enumerate() {\n            for (j, other_task) in replacing_tasks.iter().enumerate() {\n                if i != j && task.id == other_task.id {\n                    return Err(ErrorCode::TaskIdNotUnique.into());\n                }\n            }\n        }\n\n        // If length of tasks is not equal to the length of replacing_tasks, then\n        // reallocate the tasks account.\n        if tasks.tasks.len() < replacing_tasks.len() {\n            let new_space = 8 + TASK_SIZE * replacing_tasks.len();\n\n            let new_minimum_balance = Rent::get()?.minimum_balance(new_space);\n\n            let tasks_account_info = tasks.to_account_info();\n\n            let lamports_diff = new_minimum_balance.saturating_sub(tasks_account_info.lamports());\n            **ctx\n                .accounts\n                .user\n                .to_account_info()\n                .try_borrow_mut_lamports()? -= lamports_diff;\n            **tasks_account_info.try_borrow_mut_lamports()? += lamports_diff;\n\n            // Allocate the new space for the tasks account.\n            tasks_account_info.realloc(new_space, false)?;\n        }\n\n        tasks.tasks = replacing_tasks;\n\n        Ok(())\n    }\n}\n\n#[derive(Accounts)]\n#[instruction(replacing_tasks: Vec<Task>)]\npub struct SaveTasks<'info> {\n    #[account(init_if_needed, space = 8 + replacing_tasks.len() * TASK_SIZE, payer = user, seeds = [user.key().as_ref()], bump)]\n    pub tasks: Account<'info, TasksAccount>,\n    #[account(mut)]\n    pub user: Signer<'info>,\n    pub system_program: Program<'info, System>,\n}\n\n#[account]\n#[derive(Debug)]\npub struct TasksAccount {\n    tasks: Vec<Task>,\n}\n\n#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)]\npub struct Task {\n    pub id: u32,\n    /// The name of the task. Max 32 characters, min 1 character.\n    pub name: String,\n    pub completed: bool,\n}\n\n#[error_code]\npub enum ErrorCode {\n    #[msg(\"The task name must be less than 32 characters.\")]\n    TaskNameTooLong,\n    #[msg(\"The task name must be at least 1 character.\")]\n    TaskNameTooShort,\n    #[msg(\"The task id must be unique.\")]\n    TaskIdNotUnique,\n}\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/tests/todo.ts",
    "content": "import * as anchor from '@coral-xyz/anchor';\nimport { Program } from '@coral-xyz/anchor';\nimport { Todo } from '../target/types/todo';\nimport { Connection, Keypair, PublicKey } from '@solana/web3.js';\n\ndescribe('todo', () => {\n  // Configure the client to use the local cluster.\n  anchor.setProvider(anchor.AnchorProvider.env());\n\n  const program = anchor.workspace.Todo as Program<Todo>;\n  const connection = new Connection('http://localhost:8899', 'confirmed');\n  it('saves a new task', async () => {\n    const user = Keypair.generate();\n\n    const sig = await connection.requestAirdrop(user.publicKey, 10_000_000_000);\n    await connection.confirmTransaction(sig);\n\n    const [tasksPublicKey, _] = PublicKey.findProgramAddressSync(\n      [user.publicKey.toBuffer()],\n      program.programId\n    );\n    const _tx = await program.methods\n      .saveTasks([\n        {\n          id: 1,\n          name: 'example',\n          completed: false\n        }\n      ])\n      .accounts({ user: user.publicKey, tasks: tasksPublicKey })\n      .signers([user])\n      .rpc({ skipPreflight: true });\n    const tasks = await program.account.tasksAccount.fetch(tasksPublicKey);\n\n    console.log('tasks', tasks);\n  });\n});\n"
  },
  {
    "path": "learn-how-to-deploy-to-devnet/todo/tsconfig.json",
    "content": "{\n            \"compilerOptions\": {\n              \"types\": [\"mocha\", \"chai\"],\n              \"typeRoots\": [\"./node_modules/@types\"],\n              \"lib\": [\"es2015\"],\n              \"module\": \"commonjs\",\n              \"target\": \"es6\",\n              \"esModuleInterop\": true\n            }\n          }\n          "
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/cluster-devnet.env",
    "content": "LIVE=1\nCLUSTER=devnet\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/cluster-mainnet-beta.env",
    "content": "LIVE=1\nCLUSTER=mainnet-beta\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/cluster-testnet.env",
    "content": "LIVE=1\nCLUSTER=testnet\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/package.json",
    "content": "{\n  \"name\": \"learn-how-to-interact-with-on-chain-programs\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"build\": \"cargo build-sbf --manifest-path=./src/program-rust/Cargo.toml --sbf-out-dir=dist/program\",\n    \"call:hello-world\": \"node src/client/main.js\",\n    \"clean\": \"npm run clean:program-rust\",\n    \"clean:program-rust\": \"cargo clean --manifest-path=./src/program-rust/Cargo.toml && rm -rf ./dist\",\n    \"deploy\": \"solana program deploy dist/program/helloworld.so\"\n  },\n  \"dependencies\": {\n    \"@solana/web3.js\": \"1.87.7\",\n    \"borsh\": \"0.7.0\"\n  },\n  \"engines\": {\n    \"node\": \">=16.0.0\"\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/_answer/client/hello-world.js",
    "content": "import {\n  Connection,\n  Keypair,\n  PublicKey,\n  Transaction,\n  SystemProgram,\n  sendAndConfirmTransaction,\n  TransactionInstruction\n} from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\nimport * as borsh from 'borsh';\n\nexport function establishConnection() {\n  return new Connection('http://localhost:8899');\n}\n\nexport async function establishPayer() {\n  const secretKeyString = await readFile(\n    '../../../root/.config/solana/id.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n\nexport async function getProgramId() {\n  const secretKeyString = await readFile(\n    'dist/program/helloworld-keypair.json',\n    'utf8'\n  );\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  const keypair = Keypair.fromSecretKey(secretKey);\n  return keypair.publicKey;\n}\n\nexport async function getAccountPubkey(payer, programId) {\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    'seed-string',\n    programId\n  );\n}\n\nexport async function checkProgram(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const programAccountInfo = await connection.getAccountInfo(programId);\n  if (programAccountInfo === null) {\n    throw new Error('Program account info not found');\n  }\n  if (!programAccountInfo.executable) {\n    throw new Error('Program account is not executable');\n  }\n  const dataAccountInfo = await connection.getAccountInfo(accountPubkey);\n  if (dataAccountInfo === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nclass HelloWorldAccount {\n  constructor(fields) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\nexport async function createAccount(\n  connection,\n  payer,\n  programId,\n  accountPubkey\n) {\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n  const transaction = new Transaction();\n  const instruction = {\n    basePubkey: payer.publicKey,\n    fromPubkey: payer.publicKey,\n    lamports,\n    newAccountPubkey: accountPubkey,\n    programId,\n    seed: 'seed-string',\n    space: ACCOUNT_SIZE\n  };\n  const tx = SystemProgram.createAccountWithSeed(instruction);\n  transaction.add(tx);\n\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\nexport async function sayHello(connection, payer, programId, accountPubkey) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0)\n  };\n  const instruction = new TransactionInstruction(transaction);\n  await sendAndConfirmTransaction(\n    connection,\n    new Transaction().add(instruction),\n    [payer]\n  );\n}\n\nexport async function getHelloCount(connection, accountPubkey) {\n  const accountInfo = await connection.getAccountInfo(accountPubkey);\n  const greeting = borsh.deserialize(\n    HelloWorldSchema,\n    HelloWorldAccount,\n    accountInfo.data\n  );\n  return greeting.counter;\n}\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/_answer/client/main.js",
    "content": "import {\n  checkProgram,\n  establishConnection,\n  establishPayer,\n  getAccountPubkey,\n  getHelloCount,\n  getProgramId,\n  sayHello\n} from './hello-world.js';\n\nasync function main() {\n  console.log(`Saying 'hello' to a Solana account`);\n  const connection = establishConnection();\n  const programId = await getProgramId();\n  const payer = await establishPayer();\n  const accountPubkey = await getAccountPubkey(payer, programId);\n  await checkProgram(connection, payer, programId, accountPubkey);\n  await sayHello(connection, payer, programId, accountPubkey);\n  const helloCount = await getHelloCount(connection, accountPubkey);\n  console.log(`Hello count: ${helloCount}`);\n}\n\nawait main();\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/program-rust/.gitignore",
    "content": "/target/\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/program-rust/Cargo.toml",
    "content": "\n[package]\nname = \"solana-sbf-helloworld\"\nversion = \"0.0.1\"\ndescription = \"Hello World program written in Rust\"\nauthors = [\n  \"Solana Maintainers <maintainers@solana.com>\",\n  \"Shaun Hamilton <shauhami020@gmail.com>\",\n]\nrepository = \"https://github.com/freeCodeCamp/solana-curriculum\"\nlicense = \"Apache-2.0\"\nhomepage = \"https://web3.freecodecamp.org/\"\nedition = \"2021\"\n\n[features]\nno-entrypoint = []\n\n[dependencies]\nborsh = \"0.10.3\"\nborsh-derive = \"0.10.3\"\nsolana-program = \"1.17.3\"\n\n[dev-dependencies]\nsolana-sdk = \"1.17.3\"\n\n[lib]\nname = \"helloworld\"\ncrate-type = [\"cdylib\", \"lib\"]\n"
  },
  {
    "path": "learn-how-to-interact-with-on-chain-programs/src/program-rust/src/lib.rs",
    "content": "use borsh::{BorshDeserialize, BorshSerialize};\nuse solana_program::{\n    account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg,\n    program_error::ProgramError, pubkey::Pubkey,\n};\n\nentrypoint!(process_instruction);\n\npub fn process_instruction(\n    program_id: &Pubkey,\n    accounts: &[AccountInfo],\n    _instruction_data: &[u8],\n) -> ProgramResult {\n    msg!(\"Hello World\");\n\n    let mut accounts_iter = accounts.iter();\n    if let Some(account) = accounts_iter.next() {\n        if account.owner != program_id {\n            msg!(\"Account info does not match program id\");\n            return Err(ProgramError::IncorrectProgramId);\n        }\n\n        let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?;\n\n        greeting_account.counter += 1;\n        let acc_data = &mut account.data.borrow_mut()[..];\n        greeting_account.serialize(&mut acc_data.as_mut())?;\n\n        msg!(\"Greeted {} time(s)!\", greeting_account.counter);\n\n        Ok(())\n    } else {\n        msg!(\"No accounts provided to say hello to\");\n        return Err(ProgramError::NotEnoughAccountKeys);\n    }\n}\n\n/// Define the type of state stored in accounts\n#[derive(BorshSerialize, BorshDeserialize, Debug)]\npub struct GreetingAccount {\n    /// number of greetings\n    pub counter: u32,\n}\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/.gitignore",
    "content": "/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",
    "content": "{\n  \"name\": \"learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract\",\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"call:hello-world\": \"ts-node src/client/main.ts\",\n    \"clean\": \"npm run clean:program-rust\",\n    \"clean:program-rust\": \"cargo clean --manifest-path=./src/program-rust/Cargo.toml && rm -rf ./dist\"\n  },\n  \"dependencies\": {\n    \"@solana/web3.js\": \"1.87.7\",\n    \"borsh\": \"0.7.0\",\n    \"yaml\": \"2.3.3\"\n  },\n  \"devDependencies\": {\n    \"@tsconfig/recommended\": \"1.0.3\",\n    \"@types/node\": \"18.18.6\",\n    \"@types/yaml\": \"1.9.7\",\n    \"ts-node\": \"10.9.1\",\n    \"typescript\": \"4.9.5\"\n  },\n  \"engines\": {\n    \"node\": \">=16.0.0\"\n  }\n}\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/hello_world.ts",
    "content": "import {\n  Keypair,\n  Connection,\n  PublicKey,\n  LAMPORTS_PER_SOL,\n  SystemProgram,\n  TransactionInstruction,\n  Transaction,\n  sendAndConfirmTransaction\n} from '@solana/web3.js';\nimport { existsSync } from 'fs';\nimport { resolve, join } from 'path';\nimport * as borsh from 'borsh';\n\nimport { getPayer, getRpcUrl, createKeypairFromFile } from './utils';\n\nconst GREETING_SEED = 'hello';\n\n/**\n * Path to program files\n */\nconst PROGRAM_PATH = resolve(__dirname, '../../dist/program');\n\n/**\n * Path to program shared object file which should be deployed on chain.\n * This file is created when running either:\n *   - `npm run build:program-c`\n *   - `npm run build:program-rust`\n */\nconst PROGRAM_SO_PATH = join(PROGRAM_PATH, 'helloworld.so');\n\n/**\n * Path to the keypair of the deployed program.\n * This file is created when running `solana program deploy dist/program/helloworld.so`\n */\nconst PROGRAM_KEYPAIR_PATH = join(PROGRAM_PATH, 'helloworld-keypair.json');\n\n/**\n * The state of an account managed by the hello world program\n */\nclass HelloWorldAccount {\n  counter = 0;\n  constructor(fields: { counter: number } | undefined = undefined) {\n    if (fields) {\n      this.counter = fields.counter;\n    }\n  }\n}\n\n/**\n * Borsh schema definition for hello world accounts\n */\nconst HelloWorldSchema = new Map([\n  [HelloWorldAccount, { kind: 'struct', fields: [['counter', 'u32']] }]\n]);\n\n/**\n * The expected size of each hello world account.\n */\nconst ACCOUNT_SIZE = borsh.serialize(\n  HelloWorldSchema,\n  new HelloWorldAccount()\n).length;\n\n/**\n * Establish a connection to the cluster\n */\nexport async function establishConnection(): Promise<Connection> {\n  const rpcUrl = await getRpcUrl();\n  const connection = new Connection(rpcUrl, 'confirmed');\n  const version = await connection.getVersion();\n  console.log('Connection to cluster established:', rpcUrl, version);\n  return connection;\n}\n\n/**\n * Establish an account to pay for everything\n */\nexport async function establishPayer(connection: Connection): Promise<Keypair> {\n  const payer = await getPayer();\n\n  let lamports = await connection.getBalance(payer.publicKey);\n\n  `Using account ${payer.publicKey.toBase58()} containing ${\n    lamports / LAMPORTS_PER_SOL\n  } SOL to pay for fees.`;\n  return payer;\n}\n\nexport async function getProgramId(): Promise<PublicKey> {\n  try {\n    const programKeypair = await createKeypairFromFile(PROGRAM_KEYPAIR_PATH);\n    return programKeypair.publicKey;\n  } catch (err) {\n    const errMsg = (err as Error).message;\n    throw new Error(\n      `Failed to read program keypair at '${PROGRAM_KEYPAIR_PATH}' due to error: ${errMsg}. Program may need to be deployed with \\`solana program deploy dist/program/helloworld.so\\``\n    );\n  }\n}\n\nexport async function getAccountPubkey(\n  payer: Keypair,\n  programId: PublicKey\n): Promise<PublicKey> {\n  // Derive the address (public key) of a hello world account from the program so that it's easy to find later.\n  return await PublicKey.createWithSeed(\n    payer.publicKey,\n    GREETING_SEED,\n    programId\n  );\n}\n\n/**\n * Check if the hello world BPF program has been deployed\n */\nexport async function checkProgram(\n  connection: Connection,\n  payer: Keypair,\n  programId: PublicKey,\n  accountPubkey: PublicKey\n): Promise<void> {\n  // Check if the program has been deployed\n  const programInfo = await connection.getAccountInfo(programId);\n  if (programInfo === null) {\n    if (existsSync(PROGRAM_SO_PATH)) {\n      throw new Error(\n        'Program needs to be deployed with `solana program deploy dist/program/helloworld.so`'\n      );\n    } else {\n      throw new Error('Program needs to be built and deployed');\n    }\n  } else if (!programInfo.executable) {\n    throw new Error(`Program is not executable`);\n  }\n  console.log(`Using program ${programId.toBase58()}`);\n\n  // Check if the hello world account has already been created\n  const greetedAccount = await connection.getAccountInfo(accountPubkey);\n  if (greetedAccount === null) {\n    await createAccount(connection, payer, programId, accountPubkey);\n  }\n}\n\nexport async function createAccount(\n  connection: Connection,\n  payer: Keypair,\n  programId: PublicKey,\n  accountPubkey: PublicKey\n): Promise<void> {\n  console.log(\n    `Creating account ${accountPubkey.toBase58()} to say hello to...`\n  );\n  const lamports = await connection.getMinimumBalanceForRentExemption(\n    ACCOUNT_SIZE\n  );\n\n  const transaction = new Transaction().add(\n    SystemProgram.createAccountWithSeed({\n      fromPubkey: payer.publicKey,\n      basePubkey: payer.publicKey,\n      seed: GREETING_SEED,\n      newAccountPubkey: accountPubkey,\n      lamports,\n      space: ACCOUNT_SIZE,\n      programId\n    })\n  );\n  await sendAndConfirmTransaction(connection, transaction, [payer]);\n}\n\n/**\n * Say hello\n */\nexport async function sayHello(\n  connection: Connection,\n  payer: Keypair,\n  programId: PublicKey,\n  accountPubkey: PublicKey\n): Promise<void> {\n  console.log(`Saying hello to: ${accountPubkey.toBase58()}`);\n  const instruction = new TransactionInstruction({\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.alloc(0) // All instructions are hellos\n  });\n  await sendAndConfirmTransaction(\n    connection,\n    new Transaction().add(instruction),\n    [payer]\n  );\n}\n\n/**\n * Report the number of times the hello world account has been said hello to\n */\nexport async function reportGreetings(\n  connection: Connection,\n  accountPubkey: PublicKey\n): Promise<void> {\n  const accountInfo = await connection.getAccountInfo(accountPubkey);\n  if (accountInfo === null) {\n    throw 'Error: cannot find the hello world account';\n  }\n  const greeting = borsh.deserialize(\n    HelloWorldSchema,\n    HelloWorldAccount,\n    accountInfo.data\n  );\n  console.log(\n    `${accountPubkey.toBase58()} has been said \"hello\" to ${\n      greeting.counter\n    } time(s)`\n  );\n}\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/main.ts",
    "content": "import {\n  establishConnection,\n  establishPayer,\n  checkProgram,\n  sayHello,\n  reportGreetings,\n  getProgramId,\n  getAccountPubkey\n} from './hello_world';\n\nasync function main() {\n  console.log(\"Let's say hello to a Solana account...\");\n\n  // Establish connection to the cluster\n  const connection = await establishConnection();\n\n  // Get the program ID of the hello world program\n  const programId = await getProgramId();\n\n  // Determine who pays for the fees\n  const payer = await establishPayer(connection);\n\n  const accountPubkey = await getAccountPubkey(payer, programId);\n\n  // Check if the program has been deployed\n  await checkProgram(connection, payer, programId, accountPubkey);\n\n  // Say hello to an account\n  await sayHello(connection, payer, programId, accountPubkey);\n\n  // Find out how many times that account has been greeted\n  await reportGreetings(connection, accountPubkey);\n\n  console.log('Success');\n}\n\nmain().then(\n  () => process.exit(),\n  err => {\n    console.error(err);\n    process.exit(-1);\n  }\n);\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/client/utils.ts",
    "content": "import { homedir } from 'os';\nimport { readFile } from 'fs/promises';\nimport { resolve } from 'path';\nimport { parse } from 'yaml';\nimport { Keypair } from '@solana/web3.js';\n\n/**\n * @private\n */\nasync function getConfig(): Promise<any> {\n  // Path to Solana CLI config file\n  const CONFIG_FILE_PATH = resolve(\n    homedir(),\n    '.config',\n    'solana',\n    'cli',\n    'config.yml'\n  );\n  const configYml = await readFile(CONFIG_FILE_PATH, { encoding: 'utf8' });\n  return parse(configYml);\n}\n\n/**\n * Load and parse the Solana CLI config file to determine which RPC url to use\n */\nexport async function getRpcUrl(): Promise<string> {\n  try {\n    const config = await getConfig();\n    if (!config.json_rpc_url) throw new Error('Missing RPC URL');\n    return config.json_rpc_url;\n  } catch (err) {\n    console.warn(\n      'Failed to read RPC url from CLI config file, falling back to localhost'\n    );\n    return 'http://127.0.0.1:8899';\n  }\n}\n\n/**\n * Load and parse the Solana CLI config file to determine which payer to use\n */\nexport async function getPayer(): Promise<Keypair> {\n  try {\n    const config = await getConfig();\n    if (!config.keypair_path) throw new Error('Missing keypair path');\n    return await createKeypairFromFile(config.keypair_path);\n  } catch (err) {\n    console.warn(\n      'Failed to create keypair from CLI config file, falling back to new random keypair'\n    );\n    return Keypair.generate();\n  }\n}\n\n/**\n * Create a Keypair from a secret key stored in file as bytes' array\n */\nexport async function createKeypairFromFile(\n  filePath: string\n): Promise<Keypair> {\n  const secretKeyString = await readFile(filePath, { encoding: 'utf8' });\n  const secretKey = Uint8Array.from(JSON.parse(secretKeyString));\n  return Keypair.fromSecretKey(secretKey);\n}\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/.gitignore",
    "content": "/target/\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/Cargo.toml",
    "content": "\n[package]\nname = \"solana-sbf-helloworld\"\nversion = \"0.0.1\"\ndescription = \"Hello World program written in Rust\"\nauthors = [\n  \"Solana Maintainers <maintainers@solana.com>\",\n  \"Shaun Hamilton <shauhami020@gmail.com>\",\n]\nrepository = \"https://github.com/freeCodeCamp/solana-curriculum\"\nlicense = \"Apache-2.0\"\nhomepage = \"https://web3.freecodecamp.org/\"\nedition = \"2021\"\n\n[features]\nno-entrypoint = []\n\n[dependencies]\nborsh = \"=1.2.1\"\nborsh-derive = \"=1.2.1\"\nsolana-program = \"=1.17.18\"\n\n[dev-dependencies]\nsolana-sdk = \"=1.17.18\"\n\n[lib]\nname = \"helloworld\"\ncrate-type = [\"cdylib\", \"lib\"]\n"
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/src/program-rust/src/lib.rs",
    "content": ""
  },
  {
    "path": "learn-how-to-set-up-solana-by-building-a-hello-world-smart-contract/tsconfig.json",
    "content": "{\n  \"extends\": \"@tsconfig/recommended/tsconfig.json\",\n  \"ts-node\": {\n    \"compilerOptions\": {\n      \"module\": \"commonjs\"\n    }\n  },\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"moduleResolution\": \"node\",\n    \"module\": \"es2015\"\n  },\n  \"include\": [\"src/client/**/*\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "learn-solanas-token-program-by-minting-a-fungible-token/.gitkeep",
    "content": ""
  },
  {
    "path": "learn-solanas-token-program-by-minting-a-fungible-token/package.json",
    "content": "{\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",
    "content": "import { Keypair, PublicKey } from '@solana/web3.js';\n\nconst secretKey = (\n  await import('./wallet.json', {\n    assert: { type: 'json' }\n  })\n).default;\n\nexport const payer = Keypair.fromSecretKey(new Uint8Array(secretKey));\n\nconst MINT_ADDRESS_58 = '';\n// For simplicity, the mint authority is the payer.\nconst MINT_AUTHORITY_58 = payer.publicKey.toBase58();\nconst TOKEN_ACCOUNT_58 = '';\n\n// export const mintAddress = new PublicKey(MINT_ADDRESS_58);\nexport const mintAuthority = new PublicKey(MINT_AUTHORITY_58);\n// export const tokenAccount = new PublicKey(TOKEN_ACCOUNT_58);\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/package.json",
    "content": "{\n  \"name\": \"learn-the-metaplex-sdk-by-minting-an-nft\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@solana/spl-token\": \"0.3.7\",\n    \"@solana/web3.js\": \"1.87.7\"\n  },\n  \"scripts\": {\n    \"solana:dump\": \"solana program dump -u mainnet-beta $(npm pkg get env.METAPLEX_TOKEN_METADATA_PROGRAM_ID | tr -d \\\\\\\") ./mlp_token.so\",\n    \"solana:airdrop\": \"solana airdrop 10 ./wallet.json\",\n    \"start:validator\": \"solana-test-validator --bpf-program $(npm pkg get env.METAPLEX_TOKEN_METADATA_PROGRAM_ID | tr -d \\\\\\\") ./mlp_token.so --reset\",\n    \"start:server\": \"node server.js\"\n  },\n  \"env\": {\n    \"METAPLEX_TOKEN_METADATA_PROGRAM_ID\": \"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s\",\n    \"MINT_ACCOUNT_ADDRESS\": \"\",\n    \"TOKEN_ACCOUNT_ADDRESS\": \"\",\n    \"WALLET_ADDRESS\": \"\"\n  },\n  \"devDependencies\": {\n    \"express\": \"4.20.0\"\n  }\n}\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/server.js",
    "content": "import express from 'express';\n\nconst app = express();\n\napp.use(express.json());\n\nconst metadatas = {};\n\napp.get('/meta/:id', (req, res) => {\n  const metadata = metadatas[req.params.id];\n  if (!metadata) {\n    return res.status(404).end();\n  }\n\n  console.log('GET', req.params.id);\n\n  return res.send(Buffer.from(metadata));\n});\napp.put('/meta/:id', (req, res) => {\n  console.log('POST', req.params.id);\n  metadatas[req.params.id] = req.body;\n  res.status(200).end();\n});\n\napp.get('/status/ping', (_req, res) => {\n  res.status(200).send('pong');\n});\n\napp.listen(3001, () => {\n  console.log('Server started');\n});\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/create-mint-account.js",
    "content": "import { Connection } from '@solana/web3.js';\nimport { payer } from './utils.js';\nimport { createMint } from '@solana/spl-token';\n\nconst connection = new Connection('http://127.0.0.1:8899');\n\nconst mintAuthority = payer.publicKey;\nconst freezeAuthority = payer.publicKey;\n\nconst mint = await createMint(\n  connection,\n  payer,\n  mintAuthority,\n  freezeAuthority,\n  9\n);\n\nconsole.log('Token Unique Identifier:', mint.toBase58());\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/create-token-account.js",
    "content": "import { Connection } from '@solana/web3.js';\nimport { payer, mintAddress } from './utils.js';\nimport { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';\n\nconst connection = new Connection('http://127.0.0.1:8899');\nconst tokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  payer.publicKey\n);\n\nconsole.log('Token Account Address:', tokenAccount.address.toBase58());\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/get-token-account.js",
    "content": "import { Connection, PublicKey } from '@solana/web3.js';\nimport { getAssociatedTokenAddress, getAccount } from '@solana/spl-token';\nimport { mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst userPublicKey = new PublicKey(process.argv[2]);\n\nconst tokenAddress = await getAssociatedTokenAddress(\n  mintAddress,\n  userPublicKey\n);\n\nconst tokenAccount = await getAccount(connection, tokenAddress);\n\nconsole.log('Token Account:', tokenAccount);\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/get-token-info.js",
    "content": "import { Connection } from '@solana/web3.js';\nimport { getMint } from '@solana/spl-token';\nimport { mintAddress } from './utils.js';\n\nconst connection = new Connection('http://127.0.0.1:8899');\n\nconst mint = await getMint(connection, mintAddress);\n\nconsole.log(mint);\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/mint.js",
    "content": "import { Connection } from '@solana/web3.js';\nimport { mintTo } from '@solana/spl-token';\nimport { payer, mintAddress, tokenAccount, mintAuthority } from './utils.js';\n\nconst connection = new Connection('http://127.0.0.1:8899');\n\nawait mintTo(\n  connection,\n  payer,\n  mintAddress,\n  tokenAccount,\n  mintAuthority,\n  1_000_000_000\n);\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/package.json",
    "content": "{\n  \"name\": \"fungi-token\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A fungible token on Solana\",\n  \"scripts\": {\n    \"serve\": \"npx serve client\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@solana/spl-token\": \"0.3.7\",\n    \"@solana/web3.js\": \"1.87.7\"\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/transfer.js",
    "content": "import { Connection, PublicKey, Keypair } from '@solana/web3.js';\nimport {\n  getOrCreateAssociatedTokenAccount,\n  getAccount,\n  transfer\n} from '@solana/spl-token';\nimport { payer, mintAddress } from './utils.js';\n\nconst connection = new Connection('http://localhost:8899');\n\nconst fromTokenAccountPublicKey = new PublicKey(process.argv[2]);\n\nconst toWallet = Keypair.generate();\n\nconst toTokenAccount = await getOrCreateAssociatedTokenAccount(\n  connection,\n  payer,\n  mintAddress,\n  toWallet.publicKey\n);\n\nconst fromWallet = await getAccount(connection, fromTokenAccountPublicKey);\n\nconst owner = fromWallet.owner;\n\nconst amount = Number(process.argv[3]);\n\nawait transfer(\n  connection,\n  payer,\n  fromTokenAccountPublicKey,\n  toTokenAccount.address,\n  owner,\n  amount\n);\n\nconsole.log(\n  `Transferred ${amount} tokens from ${fromTokenAccountPublicKey.toBase58()} to ${toTokenAccount.address.toBase58()}`\n);\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/spl-program/utils.js",
    "content": "import { PublicKey, Keypair } from '@solana/web3.js';\nimport { pkg } from '../utils.js';\n\nconst secretKey = (\n  await import('../wallet.json', {\n    assert: { type: 'json' }\n  })\n).default;\n\nexport const payer = Keypair.fromSecretKey(new Uint8Array(secretKey));\n\nconst MINT_ADDRESS_58 = pkg.env.MINT_ACCOUNT_ADDRESS;\n// For simplicity, the mint authority is the payer.\nconst MINT_AUTHORITY_58 = payer.publicKey.toBase58();\nconst TOKEN_ACCOUNT_58 = pkg.env.TOKEN_ACCOUNT_ADDRESS;\n\nexport const mintAddress = new PublicKey(MINT_ADDRESS_58 || '1'.repeat(32));\nexport const mintAuthority = new PublicKey(MINT_AUTHORITY_58);\nexport const tokenAccount = new PublicKey(TOKEN_ACCOUNT_58 || '1'.repeat(32));\n"
  },
  {
    "path": "learn-the-metaplex-sdk-by-minting-an-nft/utils.js",
    "content": "import { Keypair } from '@solana/web3.js';\nimport { readFile } from 'fs/promises';\n\nconst t = (\n  await import('./wallet.json', {\n    assert: { type: 'json' }\n  })\n).default;\n// solana address -k wallet.json\nexport const WALLET_KEYPAIR = Keypair.fromSecretKey(new Uint8Array(t));\n\nconst file = await readFile('./package.json', 'utf8');\nexport const pkg = JSON.parse(file);\n\nexport function localStorage(options) {\n  return {\n    install(metaplex) {\n      metaplex.storage().setDriver(new LocalStorageDriver(options));\n    }\n  };\n}\n\nclass LocalStorageDriver {\n  constructor(options) {\n    if (!options.baseUrl) {\n      throw new Error('Missing baseUrl option');\n    }\n    this.baseUrl = options.baseUrl;\n    this.costPerByte = 2;\n  }\n  async getUploadPrice(bytes) {\n    const { amount } = await import('@metaplex-foundation/js');\n    return amount(this.costPerByte * bytes, { symbol: 'SOL', decimals: 9 });\n  }\n  async upload(file) {\n    const uri = `${this.baseUrl}meta/${file.uniqueName}`;\n    await fetch(uri, {\n      method: 'PUT',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify(file.buffer)\n    });\n    return uri;\n  }\n  async download(uri) {\n    const { toMetaplexFile } = await import('@metaplex-foundation/js');\n    const res = await fetch(uri);\n    if (!res) {\n      throw new Error(`URI not found: ${uri}`);\n    }\n    const buffer = await res.arrayBuffer();\n    const metaplexFile = toMetaplexFile(buffer, uri);\n    return metaplexFile;\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"solana-curriculum\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Root package.json\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/freeCodeCamp/solana-curriculum\"\n  },\n  \"author\": \"Shaun Hamilton\",\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@babel/parser\": \"7.24.5\",\n    \"@freecodecamp/freecodecamp-os\": \"1.10.0\",\n    \"@metaplex-foundation/js\": \"0.20.1\",\n    \"@solana/spl-token\": \"0.4.6\",\n    \"@solana/web3.js\": \"1.91.8\",\n    \"babeliser\": \"0.6.0\",\n    \"borsh\": \"2.0.0\"\n  }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"local>freeCodeCamp/renovate-config\"\n  ]\n}\n"
  },
  {
    "path": "tooling/camper-info.js",
    "content": "/**\n * @file Provides command-line output of useful debugging information\n * @example\n *\n * ```bash\n * node tooling/camper-info.js --history --directory\n * ```\n */\n\nimport {\n  getProjectConfig,\n  getConfig,\n  getState\n} from '../node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/env.js';\nimport __helpers from '../node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/test-utils.js';\nimport { Logger } from 'logover';\nimport { readdir, readFile } from 'fs/promises';\nimport { join } from 'path';\nimport os from 'os';\n\nconst logover = new Logger({ level: 'debug', timestamp: null });\n\nconst FLAGS = process.argv;\n\nasync function main() {\n  try {\n    const handleFlag = {\n      '--history': printCommandHistory,\n      '--directory': printDirectoryTree\n    };\n    const projectConfig = await getProjectConfig();\n    const config = await getConfig();\n    const state = await getState();\n\n    const { currentProject } = state;\n    const { currentLesson } = projectConfig;\n    const { version } = config;\n\n    const devContainerFile = await readFile(\n      '.devcontainer/devcontainer.json',\n      'utf-8'\n    );\n    const devConfig = JSON.parse(devContainerFile);\n    const coursesVersion = devConfig.extensions?.find(e =>\n      e.match('freecodecamp-courses')\n    );\n\n    const { stdout } = await __helpers.getCommandOutput('git log -1 --oneline');\n\n    const osInfo = `\n    Architecture: ${os.arch()}\n    Platform: ${os.platform()}\n    Release: ${os.release()}\n    Type: ${os.type()}\n    `;\n\n    logover.info('Project: ', currentProject);\n    logover.info('Lesson Number: ', currentLesson);\n    logover.info('Curriculum Version: ', version);\n    logover.info('freeCodeCamp - Courses: ', coursesVersion);\n    logover.info('Commit: ', stdout);\n    logover.info('OS Info:', osInfo);\n\n    for (const arg of FLAGS) {\n      await handleFlag[arg]?.();\n    }\n    async function printDirectoryTree() {\n      const files = await readdir('.', { withFileTypes: true });\n      let depth = 0;\n      for (const file of files) {\n        if (file.isDirectory() && file.name === currentProject) {\n          await recurseDirectory(file.name, depth);\n        }\n      }\n    }\n\n    async function printCommandHistory() {\n      const historyCwd = await readFile('.logs/.history_cwd.log', 'utf-8');\n      logover.info('Command History:\\n', historyCwd);\n    }\n  } catch (e) {\n    logover.error(e);\n  }\n}\n\nmain();\n\nconst IGNORE = [\n  'node_modules',\n  'target',\n  'test-ledger',\n  'store',\n  '.cargo',\n  '.DS_Store'\n];\nasync function recurseDirectory(path, depth) {\n  logover.info(`|${' '.repeat(depth * 2)}|-- ${path}`);\n  depth++;\n  const files = await readdir(path, { withFileTypes: true });\n  for (const file of files) {\n    if (!IGNORE.includes(file.name)) {\n      if (file.isDirectory()) {\n        await recurseDirectory(join(path, file.name), depth);\n      } else {\n        logover.info(`|${' '.repeat(depth * 2)}|-- ${file.name}`);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "tooling/helpers.js",
    "content": "import __helpers from '../node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/test-utils.js';\nimport { logover } from '../node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/logger.js';\nimport { ROOT } from '../node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/env.js';\nimport { writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { Babeliser as B } from 'babeliser';\nimport * as web3 from '@solana/web3.js';\nimport * as borsh from 'borsh';\n\nexport async function rustTest(path, filePath, test, cb) {\n  const PATH_TO_FILE = join(ROOT, filePath);\n  const T_ATTR = '#[test]';\n  const testString = `${T_ATTR}\\n${test}`;\n\n  const fileContents = await __helpers.getFile(filePath);\n\n  const fileWithTest = fileContents + '\\n\\n\\n' + testString;\n\n  let std;\n\n  try {\n    writeFileSync(PATH_TO_FILE, fileWithTest, 'utf-8');\n\n    std = await __helpers.getCommandOutput('cargo test --lib', path);\n  } catch (e) {\n    logover.debug(e);\n  } finally {\n    const ensureFileContents = fileContents.replace(testString, '');\n    writeFileSync(PATH_TO_FILE, ensureFileContents, 'utf-8');\n    await cb(std.stdout, std.stderr);\n  }\n}\n\nexport const Babeliser = B;\n\n// Test wallet: 8rK533RnqBtNPxwCHsPLZe8H89DwZxo3MhhEo4pKCfAw\n// Program ID: FxcSjVwaWZPkNndA6RS9yZTjomF69AS1JZs6kvuEEp8v\n// ProgramData Address: 34QiTA3zqEQ72mfmtANBu5M4zguBs9Xa4QCCyXcZQKnG\n// Program Data Account: 3nyLatY115wbvw1FfafD3Q7Djuz2J2iY6Md6VNsBMFYp\nexport async function getCamperKeypair() {\n  const secretKeyString = await __helpers.getFile(join(__loc, 'wallet.json'));\n  const secretKey = JSON.parse(secretKeyString);\n  const int8secretKey = new Uint8Array(secretKey);\n  return web3.Keypair.fromSecretKey(int8secretKey);\n}\n\nexport async function getSOFile() {\n  const dir = await __helpers.getDirectory(join(__loc, 'dist', 'program'));\n  for (const file of dir) {\n    if (file.endsWith('.so')) {\n      return file;\n    }\n  }\n}\nexport async function getProgramJSONFile() {\n  const dir = await __helpers.getDirectory(join(__loc, 'dist', 'program'));\n  for (const file of dir) {\n    if (file.endsWith('.json')) {\n      return file;\n    }\n  }\n}\nexport async function getProgramKeypair() {\n  const jsonFile = await getProgramJSONFile();\n  const json = await __helpers.getFile(\n    join(__loc, 'dist', 'program', jsonFile)\n  );\n  const keypair = JSON.parse(json);\n  const int8keypair = new Uint8Array(keypair);\n  return web3.Keypair.fromSecretKey(int8keypair);\n}\nexport async function getDataAccountPublicKey() {\n  const keypair = await getCamperKeypair();\n  const programKeypair = await getProgramKeypair();\n  const programId = programKeypair.publicKey;\n  const dataAccount = await web3.PublicKey.createWithSeed(\n    keypair.publicKey,\n    'fcc-seed',\n    programId\n  );\n  return dataAccount;\n}\nexport function establishConnection() {\n  return new web3.Connection('http://localhost:8899');\n}\n\nclass MessageAccount {\n  constructor(fields) {\n    this.message = fields?.message || ' '.repeat(280);\n  }\n}\n\nconst MessageSchema = new Map([\n  [MessageAccount, { kind: 'struct', fields: [['message', 'string']] }]\n]);\n\nexport async function setMessage(\n  connection,\n  payer,\n  programId,\n  accountPubkey,\n  message\n) {\n  const transaction = {\n    keys: [{ pubkey: accountPubkey, isSigner: false, isWritable: true }],\n    programId,\n    data: Buffer.from(message)\n  };\n  const instruction = new web3.TransactionInstruction(transaction);\n  await web3.sendAndConfirmTransaction(\n    connection,\n    new web3.Transaction().add(instruction),\n    [payer]\n  );\n}\n\nexport async function getMessage(connection, accountPubkey) {\n  const accountInfo = await connection.getAccountInfo(accountPubkey);\n  const message = borsh.deserialize(\n    MessageSchema,\n    MessageAccount,\n    accountInfo.data\n  );\n  return message.message;\n}\n"
  },
  {
    "path": "tooling/rejig.js",
    "content": "import { readFile, writeFile, readdir } from 'fs/promises';\nimport { join } from 'path';\n\nconst PATH = process.argv[2]?.trim();\n\nconst CURRICULUM_PATH = 'curriculum/locales/english';\n\n/**\n * Ensures all lessons are incremented by 1\n */\nasync function rejigFile(fileName) {\n  const filePath = join(CURRICULUM_PATH, fileName);\n  const file = await readFile(filePath, 'utf-8');\n  let lessonNumber = 0;\n  const newFile = file.replace(/## \\d+/g, () => {\n    lessonNumber++;\n    return `## ${lessonNumber}`;\n  });\n  await writeFile(filePath, newFile, 'utf-8');\n}\n\ntry {\n  const rejiggedFiles = [];\n  if (PATH) {\n    await rejigFile(PATH);\n    rejiggedFiles.push(PATH);\n  } else {\n    const files = await readdir(CURRICULUM_PATH);\n    for (const file of files) {\n      console.log(`Rejigging '${file}'`);\n      await rejigFile(file);\n      rejiggedFiles.push(file);\n    }\n  }\n  console.info('Successfully rejigged: ', rejiggedFiles);\n} catch (e) {\n  console.error(e);\n  console.log('Usage: npm run rejig <CURRICULUM_FILE_NAME>');\n  console.log('Curriculum file name MUST include the `.md` extension.');\n}\n"
  },
  {
    "path": "tooling/seed.js",
    "content": "/**\n * Copies the seed from the previous file match to the given lesson\n *\n * @example\n * node tooling/seed.js <LESSON_NUMBER> <FILE>\n */\n\nimport clipboardy from 'clipboardy';\nimport { join } from 'path';\nimport {\n  getFilesWithSeed,\n  getLessonFromFile,\n  getLessonSeed\n} from '../node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/parser.js';\n\nconst PATH =\n  './curriculum/locales/english/learn-how-to-interact-with-on-chain-programs.md';\n\nconst LESSON_NUMBER = Number(process.argv[2]);\nconst FILE = join(process.argv[3]);\n\n// Get seed from latest matching file\n\nlet lessonToCheck = LESSON_NUMBER - 1;\nlet match = null;\nwhile (!match) {\n  const lesson = await getLessonFromFile(PATH, lessonToCheck);\n  const seed = getLessonSeed(lesson);\n  const filesWithSeed = getFilesWithSeed(seed);\n  match = filesWithSeed.find(([filePath]) => join(filePath) === FILE);\n  lessonToCheck--;\n}\n\n// Get seed from lesson\nconst [_, seed] = match;\n\n// Add seed to clipboard\nawait clipboardy.write(seed);\n"
  }
]