# CORE 5 ## Section 4 ### 5.4.1 Anchor Staking Program and UI ⚓⚓ It's time to convert the NFT Staking Program and UI to Anchor! The buildoor project you've been working on is awesome as is, but it'll be simpler to work with moving forward if it's in Anchor. Go ahead and use what you've learned to:1. Rewrite the program from scratch using Anchor. Add some solid test coverage to make sure you aren't letting security risks slip through the cracks3. Replace complex UI code with calls to the Anchor method builder You should spend a fair amount of time trying to do this independently. It's not a simple task, but you've got this. After a few hours if you feel stuck feel free to watch the video walkthroughs of our solution. ### 5.4.2 Ship Walkthrough - Anchor Staking Program Stake Instruction 🚢 🚩 Let's do this and go over the shipped product. We will build out the staking program we've been working on, but instead of adding new functionality, we'll go through and replace it all with Anchor. Let's create a new project by running `anchor init anchor-nft-staking`, or a name of your own choosing. Hop into the `Anchor.toml` file and set seeds to `true`, and the cluster to `devnet`. Then hop over to `/programs/anchor-nft-staking/Cargo.toml`, add the following dependencies. ``` anchor-lang = { version="0.25.0", features = ["init-if-needed"] } anchor-spl = "0.25.0" mpl-token-metadata = { version="1.4.1", features=["no-entrypoint"] } ``` Alright, open up the `lib.rs` file, let's build out our basic scaffolding. Let's add the following imports, it'll become clear as we go along why each is needed. ``` rust use anchor_lang::solana_program::program::invoke_signed; use anchor_spl::token; use anchor_spl::{ associated_token::AssociatedToken, token::{Approve, Mint, MintTo, Revoke, Token, TokenAccount}, }; use mpl_token_metadata::{ instruction::{freeze_delegated_account, thaw_delegated_account}, ID as MetadataTokenId, }; ``` Let's change the name of the deafult function to `stake`, and its related Context to type `Stake`. Then add a function called `redeem` with Context type of `Redeem`. Finally, do the same for `unstake` with Context type of `Unstake`. Those are the items we'll be building out, we'll first work on the struct for Stake. We will need a PDA for the UserStakeInfo, and a StakeState enum for one of the fields of the PDA. ``` rust #[account] pub struct UserStakeInfo { pub token_account: Pubkey, pub stake_start_time: i64, pub last_stake_redeem: i64, pub user_pubkey: Pubkey, pub stake_state: StakeState, pub is_initialized: bool, } #[derive(Debug, PartialEq, AnchorDeserialize, AnchorSerialize, Clone)] pub enum StakeState { Unstaked, Staked, } ``` Let's also add a default value for StakeState which will set the value to unstaked. ``` rust impl Default for StakeState { fn default() -> Self { StakeState::Unstaked } } ``` We will be using the Metadata program. Since this is relatively new, there isn't a type for it in the Anchor program. In order to use it like our other programs (e.g. System Program, Token Program, etc), we'll create a struct for it, and add an implementation called `id` that returns a `Pubkey`, which is the `MetadataTokenId`. ``` rust #[derive(Clone)] pub struct Metadata; impl anchor_lang::Id for Metadata { fn id() -> Pubkey { MetadataTokenId } } ``` Ok, we can now get working on the staking bit. The struct needs a total of nine accounts, which you'll see below. A few things of note. You'll notice the `nft_edition` is an `Unchecked` account, that's because there isn't a type for the edition account already created in the system. All unchecked accounts need to have a note so the system knows you'll add manual security checks, you'll see that below as `CHECK: Manual validation`. As a reminder, the attributes on each account are security checks to ensure the account is of the right type, and can perform certain functions. As the user has to pay, and the NFT token account will be making changes, both have the `mut` attribute. Some accounts also need seeds as you'll notice below. All the other accounts without any attributed have their own requisite security checks in Anchor, so we don't have to add any attributes. ``` rust #[derive(Accounts)] pub struct Stake<'info> { #[account(mut)] pub user: Signer<'info>, #[account( mut, associated_token::mint=nft_mint, associated_token::authority=user )] pub nft_token_account: Account<'info, TokenAccount>, pub nft_mint: Account<'info, Mint>, /// CHECK: Manual validation #[account(owner=MetadataTokenId)] pub nft_edition: UncheckedAccount<'info>, #[account( init_if_needed, payer=user, space = std::mem::size_of::<UserStakeInfo>() + 8, seeds = [user.key().as_ref(), nft_token_account.key().as_ref()], bump )] pub stake_state: Account<'info, UserStakeInfo>, /// CHECK: Manual validation #[account(mut, seeds=["authority".as_bytes().as_ref()], bump)] pub program_authority: UncheckedAccount<'info>, pub token_program: Program<'info, Token>, pub system_program: Program<'info, System>, pub metadata_program: Program<'info, Metadata>, } ``` Before we move on, let's run anchor build so our first build gets going. Remember, this is our first build, so it'll generate our Program ID. While that's running, create a new folder in the `tests` directory called `utils`. In there, create a file called `setupNft.ts`. Paste in the code below. ``` typescript import { bundlrStorage, keypairIdentity, Metaplex, } from "@metaplex-foundation/js" import { createMint, getAssociatedTokenAddress } from "@solana/spl-token" import * as anchor from "@project-serum/anchor" export const setupNft = async (program, payer) => { const metaplex = Metaplex.make(program.provider.connection) .use(keypairIdentity(payer)) .use(bundlrStorage()) const nft = await metaplex .nfts() .create({ uri: "", name: "Test nft", sellerFeeBasisPoints: 0, }) .run() console.log("nft metadata pubkey: ", nft.metadataAddress.toBase58()) console.log("nft token address: ", nft.tokenAddress.toBase58()) const [delegatedAuthPda] = await anchor.web3.PublicKey.findProgramAddress( [Buffer.from("authority")], program.programId ) const [stakeStatePda] = await anchor.web3.PublicKey.findProgramAddress( [payer.publicKey.toBuffer(), nft.tokenAddress.toBuffer()], program.programId ) console.log("delegated authority pda: ", delegatedAuthPda.toBase58()) console.log("stake state pda: ", stakeStatePda.toBase58()) const [mintAuth] = await anchor.web3.PublicKey.findProgramAddress( [Buffer.from("mint")], program.programId ) const mint = await createMint( program.provider.connection, payer, mintAuth, null, 2 ) console.log("Mint pubkey: ", mint.toBase58()) const tokenAddress = await getAssociatedTokenAddress(mint, payer.publicKey) return { nft: nft, delegatedAuthPda: delegatedAuthPda, stakeStatePda: stakeStatePda, mint: mint, mintAuth: mintAuth, tokenAddress: tokenAddress, } } ``` Next run `npm install @metaplex-foundation/js`. Then hop into `anchor-nft-staking.ts` in the `tests` directory. This is a default file created by Anchor. Change the default line for the provider to two parts, so we have access to the provider when we need it later. ``` typescript const provider = anchor.AnchorProvider.env() anchor.setProvider(provider) ``` Let's add wallet, which will allow us to expose a payer for our transactions. ``` typescript const wallet = anchor.workspace.AnchorNftStaking.provider.wallet ``` Check in on your build, if it is completed, run `anchor deploy`, if it fails, you may need to airdrop some SOL to yourself. After the build is done, run `anchor keys list` and get the program ID to put into `lib.rs` and `Anchor.toml` files. You may need to come back to this if the build takes a while. Back to the test file. Let's declare a few variable types we'll be needing for testing. ``` typescript let delegatedAuthPda: anchor.web3.PublicKey let stakeStatePda: anchor.web3.PublicKey let nft: any let mintAuth: anchor.web3.PublicKey let mint: anchor.web3.PublicKey let tokenAddress: anchor.web3.PublicKey ``` Now let's add a `before` function, which will get called before tests run. You'll notice the ";" syntax, it is what will destructure the return value and set all of these values. ``` typescript before(async () => { ;({ nft, delegatedAuthPda, stakeStatePda, mint, mintAuth, tokenAddress } = await setupNft(program, wallet.payer)) }) ``` Hop into the default test, change it so it says `it("Stakes", `. At first, we're just making sure the function gets called. We don't have our adtual stake function built out yet, so there's no logic being tested here. ``` typescript it("Stakes", async () => { // Add your test here. await program.methods .stake() .accounts({ nftTokenAccount: nft.tokenAddress, nftMint: nft.mintAddress, nftEdition: nft.masterEditionAddress, metadataProgram: METADATA_PROGRAM_ID, }) .rpc() }) ``` And now, run `anchor test`. Assuming it passes, it means we're passing the validation for accounts created in the Stake struct. Back to our logic, here's a step-by-step of what needs to happen for staking to work. We need to get access to the clock, make sure the stake state is already initialized, and make sure it is not already staked. In the stake function, let's first get the clock. ``` rust let clock = Clock::get().unwrap(); ``` Next we create a CPI to delegate this program as the authority to freeze or thaw our NFT. First we set the CPI, then identify which accounts we are using, and finally set the authority. ``` rust msg!("Approving delegate"); let cpi_approve_program = ctx.accounts.token_program.to_account_info(); let cpi_approve_accounts = Approve { to: ctx.accounts.nft_token_account.to_account_info(), delegate: ctx.accounts.program_authority.to_account_info(), authority: ctx.accounts.user.to_account_info(), }; let cpi_approve_ctx = CpiContext::new(cpi_approve_program, cpi_approve_accounts); token::approve(cpi_approve_ctx, 1)?; ``` Next we move onto actually freezing the token. First we set the authority bump, then we call invoke_signed and pass in all the necessary accounts, and an array of account infos, and lastly the seed and bump. ``` rust msg!("Freezing token account"); let authority_bump = *ctx.bumps.get("program_authority").unwrap(); invoke_signed( &freeze_delegated_account( ctx.accounts.metadata_program.key(), ctx.accounts.program_authority.key(), ctx.accounts.nft_token_account.key(), ctx.accounts.nft_edition.key(), ctx.accounts.nft_mint.key(), ), &[ ctx.accounts.program_authority.to_account_info(), ctx.accounts.nft_token_account.to_account_info(), ctx.accounts.nft_edition.to_account_info(), ctx.accounts.nft_mint.to_account_info(), ctx.accounts.metadata_program.to_account_info(), ], &[&[b"authority", &[authority_bump]]], )?; ``` And now, we set the data on our stake account. ``` rust ctx.accounts.stake_state.token_account = ctx.accounts.nft_token_account.key(); ctx.accounts.stake_state.user_pubkey = ctx.accounts.user.key(); ctx.accounts.stake_state.stake_state = StakeState::Staked; ctx.accounts.stake_state.stake_start_time = clock.unix_timestamp; ctx.accounts.stake_state.last_stake_redeem = clock.unix_timestamp; ctx.accounts.stake_state.is_initialized = true; ``` Ah, let's hop to the top and add a security check, which also needs a custom error. Both bits of code are below, but put the custom error code at the bottom of the file, out of the way of the logic and structs. ``` rust require!( ctx.accounts.stake_state.stake_state == StakeState::Unstaked, StakeError::AlreadyStaked ); ``` ``` rust #[error_code] pub enum StakeError { #[msg("NFT already staked")] AlreadyStaked, } ``` Don't forget to restock your SOL before run a test again. Alright, that's it, let's go back into the test and add a bit more functionality to our stake test to see if the staking state is correct. ``` typescript const account = await program.account.userStakeInfo.fetch(stakeStatePda) expect(account.stakeState === "Staked") ``` Run the test one more time and hope for the best!! 🤞 That's about it, our first instruction is working. In the next section, we'll work on the other two instructions, and then finally get working on client side stuff. ### 5.4.3 Ship Walkthrough - Anchor Staking Program Redeem Instruction 🛳️ 🎁 Back into the `lib.rs` file, down to the Redeem struct. It's quite similar to Stake, so we'll paste that code and edit as need be. The ones we don't need are nft_mint, nft_edition, and program_authority. We need to change the constraints on nft_token_account to make the token authority be 'user' since we're not passing in the mint. For the stake_state account, it no longer needs to be initialized, so we just need the seeds and bump, and make it mutable. Let's add a couple of manual contraints for it as well. ``` rust constraint = *user.key == stake_state.user_pubkey, constraint = nft_token_account.key() == stake_state.token_account ``` Let's add a few other accounts, one is stake_mint, which needs to be mutable. This is the reward mint. ``` rust #[account(mut)] pub stake_mint: Account<'info, Mint>, ``` Another is stake_authority, which will be another Unchecked account, so let's add this check. ` #[account(seeds = ["mint".as_bytes().as_ref()], bump)]` The user_stake_ata which is a TokenAccount, with these constraints. ``` rust #[account( init_if_needed, payer=user, associated_token::mint=stake_mint, associated_token::authority=user )] pub user_stake_ata: Account<'info, TokenAccount>, ``` The associated_token_program which is an AssociatedToken. ``` rust pub associated_token_program: Program<'info, AssociatedToken>, ``` And finally, replace metadata_program with rent. ``` rust pub rent: Sysvar<'info, Rent>, ``` Bringing our total number of accounts to 10. Here's all the code in one snippet. ``` rust #[derive(Accounts)] pub struct Redeem<'info> { #[account(mut)] pub user: Signer<'info>, #[account( mut, token::authority=user )] pub nft_token_account: Account<'info, TokenAccount>, #[account( mut, seeds = [user.key().as_ref(), nft_token_account.key().as_ref()], bump, constraint = *user.key == stake_state.user_pubkey, constraint = nft_token_account.key() == stake_state.token_account )] pub stake_state: Account<'info, UserStakeInfo>, #[account(mut)] pub stake_mint: Account<'info, Mint>, /// CHECK: manual check #[account(seeds = ["mint".as_bytes().as_ref()], bump)] pub stake_authority: UncheckedAccount<'info>, #[account( init_if_needed, payer=user, associated_token::mint=stake_mint, associated_token::authority=user )] pub user_stake_ata: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, pub rent: Sysvar<'info, Rent>, } ``` Back over to the test file, write up a simple test to make sure the function fires. This should look quite similar to our stake test, just with different accounts passed in. Remember, a bunch of the accounts can just be inferred for testing, so we don't have to pass them all in. ``` typescript it("Redeems", async () => { await program.methods .redeem() .accounts({ nftTokenAccount: nft.tokenAddress, stakeMint: mint, userStakeAta: tokenAddress, }) .rpc() ``` ...and run `anchor test`, and if all is ok and the two tests pass, let's hop into the function and write out the logic for redeem. First things first, let's do a couple of checks, one to see if it is initialized. The other is to make sure it is already staked. We will need to add custom errors for both of these at the bottom of the file. ``` rust require!( ctx.accounts.stake_state.is_initialized, StakeError::UninitializedAccount ); require!( ctx.accounts.stake_state.stake_state == StakeState::Staked, StakeError::InvalidStakeState ); ... #[msg("State account is uninitialized")] UninitializedAccount, #[msg("Stake state is invalid")] InvalidStakeState, ``` Next, let's get our clock. `let clock = Clock::get()?;` Now we can add a couple of messages to keep track of things, and declare our time and redeem amount. ``` rust msg!( "Stake last redeem: {:?}", ctx.accounts.stake_state.last_stake_redeem ); msg!("Current time: {:?}", clock.unix_timestamp); let unix_time = clock.unix_timestamp - ctx.accounts.stake_state.last_stake_redeem; msg!("Seconds since last redeem: {}", unix_time); let redeem_amount = (10 * i64::pow(10, 2) * unix_time) / (24 * 60 * 60); msg!("Elligible redeem amount: {}", redeem_amount); ``` Ok, now we'll actually mint the rewards. First we need our CpiContext with our program. Then we pass it accounts in the MintTo object, which includes the mint, who it is going to, and the authority. Lastly, we add our seeds, and the amount. ``` rust msg!("Minting staking rewards"); token::mint_to( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), MintTo { mint: ctx.accounts.stake_mint.to_account_info(), to: ctx.accounts.user_stake_ata.to_account_info(), authority: ctx.accounts.stake_authority.to_account_info(), }, &[&[ b"mint".as_ref(), &[*ctx.bumps.get("stake_authority").unwrap()], ]], ), redeem_amount.try_into().unwrap(), )?; ``` That's all set, now we need to set our last stake redeem time, if we don't set this, they'll keep getting more rewards than they should. ``` rust ctx.accounts.stake_state.last_stake_redeem = clock.unix_timestamp; msg!( "Updated last stake redeem time: {:?}", ctx.accounts.stake_state.last_stake_redeem ); ``` Hop back into the test for redeem, and add this. ``` typescript const account = await program.account.userStakeInfo.fetch(stakeStatePda) expect(account.stakeState === "Unstaked") const tokenAccount = await getAccount(provider.connection, tokenAddress) ``` You can definitely add more tests to make it more robust, for now we just want to get the basic functionality up and tested. Assuming all is good, we can move onto the unstake instruction. ### 5.4.4 Ship Walkthrough - Anchor Staking Program Unstake Instruction 🚀 🔙 Now that redeem and stake are complete, let's hop into unstake. The Unstake account struct will have 14 total accounts, a combination of what was in stake and redeem, so it is pasted below. Make sure the order is the same. ``` rust #[derive(Accounts)] pub struct Unstake<'info> { #[account(mut)] pub user: Signer<'info>, #[account( mut, token::authority=user )] pub nft_token_account: Account<'info, TokenAccount>, pub nft_mint: Account<'info, Mint>, /// CHECK: Manual validation #[account(owner=MetadataTokenId)] pub nft_edition: UncheckedAccount<'info>, #[account( mut, seeds = [user.key().as_ref(), nft_token_account.key().as_ref()], bump, constraint = *user.key == stake_state.user_pubkey, constraint = nft_token_account.key() == stake_state.token_account )] pub stake_state: Account<'info, UserStakeInfo>, /// CHECK: manual check #[account(mut, seeds=["authority".as_bytes().as_ref()], bump)] pub program_authority: UncheckedAccount<'info>, #[account(mut)] pub stake_mint: Account<'info, Mint>, /// CHECK: manual check #[account(seeds = ["mint".as_bytes().as_ref()], bump)] pub stake_authority: UncheckedAccount<'info>, #[account( init_if_needed, payer=user, associated_token::mint=stake_mint, associated_token::authority=user )] pub user_stake_ata: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, pub rent: Sysvar<'info, Rent>, pub metadata_program: Program<'info, Metadata>, } ``` Easy enough on that one, let's write our basic test to make sure this works. We have to add the six accounts that are not inferred. ``` typescript it("Unstakes", async () => { await program.methods .unstake() .accounts({ nftTokenAccount: nft.tokenAddress, nftMint: nft.mintAddress, nftEdition: nft.masterEditionAddress, metadataProgram: METADATA_PROGRAM_ID, stakeMint: mint, userStakeAta: tokenAddress, }) .rpc() ``` Run `anchor test` to make sure our account validation is set up properly. Back to the actual function itself, this one is a bit larger than the last two. It is quite similar to redeem, to begin with, you can paste in that code, to save some typing. We start off with the same two require statements. After those two, we need to 'thaw' our account. This code is very similar to the invoke_signed for freezing the account, we just need to reverse a couple of steps. Assuming you pasted the redeem code, before you declare clock, add this. You'll notice it's nearly identical, but we're obviously calling the thawing function. ``` rust msg!("Thawing token account"); let authority_bump = *ctx.bumps.get("program_authority").unwrap(); invoke_signed( &thaw_delegated_account( ctx.accounts.metadata_program.key(), ctx.accounts.program_authority.key(), ctx.accounts.nft_token_account.key(), ctx.accounts.nft_edition.key(), ctx.accounts.nft_mint.key(), ), &[ ctx.accounts.program_authority.to_account_info(), ctx.accounts.nft_token_account.to_account_info(), ctx.accounts.nft_edition.to_account_info(), ctx.accounts.nft_mint.to_account_info(), ctx.accounts.metadata_program.to_account_info(), ], &[&[b"authority", &[authority_bump]]], )?; ``` Next we need to revoke the delegation, again, we can paste the code from before when we approved the delegate. Change the method from approve to revoke, and change the object. It will only require source and authority. Make sure to also change the varible names. Easier to look below and see that we're basically changing approve to revoke for the most part. ``` rust msg!("Revoking delegate"); let cpi_revoke_program = ctx.accounts.token_program.to_account_info(); let cpi_revoke_accounts = Revoke { source: ctx.accounts.nft_token_account.to_account_info(), authority: ctx.accounts.user.to_account_info(), }; let cpi_revoke_ctx = CpiContext::new(cpi_revoke_program, cpi_revoke_accounts); token::revoke(cpi_revoke_ctx)?; ``` The remaining code remains the same as for the redeem function (which we just pasted), so the all the redeeming happens. Next we need to change the state for staked, add this line at the bottom. ``` rust ctx.accounts.stake_state.stake_state = StakeState::Unstaked; ``` That wraps up unstaked, let' finish up the test by adding this check to make sure it's funcitoning properly. ``` typescript const account = await program.account.userStakeInfo.fetch(stakeStatePda) expect(account.stakeState === "Unstaked") ``` Again, we can add a lot more testing to ensure everything working as we intend. I'll leave that in your hands. That's it for our program for now. Hopefully it's pretty clear why working with Anchor is simpler and saves a lot of time. The front-end is next!! ### 5.4.5 Ship Walkthrough - Anchor Staking Frontend 📺🕹️ Now that you have the program working, let's hop into the front-end code and adjust it for Anchor. This set up will take a minute, bear with me as we alter a few things. First things first, let's bring in our IDL from our program. Simply copy and paste the entire file, both for the JSON and typescript format, into the `utils` folder. Next, create a new component file called `WorkspaceProvider.ts`. To save a little time, we are pasting in this code from the movie review front-end we built, then changing instaces of movie review to anchor nft staking. You'll notice that we are importing `PROGRAM_ID` from the constants folder, go in there and make sure the program ID is the new one for our anchor nft staking program (as opposed to the one from our solana native program). ``` typescript import { createContext, useContext } from "react" import { Program, AnchorProvider, Idl, setProvider, } from "@project-serum/anchor" import { AnchorNftStaking, IDL } from "../utils/anchor_nft_staking" import { Connection } from "@solana/web3.js" import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react" import { PROGRAM_ID } from "../utils/constants" const WorkspaceContext = createContext({}) const programId = PROGRAM_ID interface Workspace { connection?: Connection provider?: AnchorProvider program?: Program<AnchorNftStaking> } const WorkspaceProvider = ({ children }: any) => { const wallet = useAnchorWallet() || MockWallet const { connection } = useConnection() const provider = new AnchorProvider(connection, wallet, {}) setProvider(provider) const program = new Program(IDL as Idl, programId) const workspace = { connection, provider, program, } return ( <WorkspaceContext.Provider value={workspace}> {children} </WorkspaceContext.Provider> ) } const useWorkspace = (): Workspace => { return useContext(WorkspaceContext) } import { Keypair } from "@solana/web3.js" const MockWallet = { publicKey: Keypair.generate().publicKey, signTransaction: () => Promise.reject(), signAllTransactions: () => Promise.reject(), } export { WorkspaceProvider, useWorkspace } ``` Also copy over the mock wallet file from movie review, or create a new component called `MockWallet.ts`, and paste in this code. ``` typscript import { Keypair } from "@solana/web3.js" const MockWallet = { publicKey: Keypair.generate().publicKey, signTransaction: () => Promise.reject(), signAllTransactions: () => Promise.reject(), } export default MockWallet ``` Make sure to install project serum, `npm install @project-serum/anchor`. Now `npm run dev` and see if everything is working on localhost...if so, let's continue. Now that we're all set on imports and additional components, let's comb through the files and see where we can make things simpler since we're using Anchor now. Jump into (/pages/_app.tsx) and add our new WorkspaceProvider, do make sure it gets imported. ``` typescript function MyApp({ Component, pageProps }: AppProps) { return ( <ChakraProvider theme={theme}> <WalletContextProvider> <WorkspaceProvider> <Component {...pageProps} /> </WorkspaceProvider> </WalletContextProvider> </ChakraProvider> ) } ``` Hop over to `StakeOptionsDisplay.ts` in the components folder. Let's import anchor. `import * as anchor from @project-serum/anchor` Under the declaration of the two state variables, let's define workspace. `let workspace = useWorkspace()` Then inside `checkStakingStatus` add this additional check, along with our other checks, to make sure we have `!workspace.program`. ``` typescript if ( !walletAdapter.connected || !walletAdapter.publicKey || !nftTokenAccount || !workspace.program ) ``` Now hop over to `/utils/accounts.ts`. You can delete all the borsh code, and replace the `getStakeAccount` code with this. This is one of the beauties of working with Anchor, we don't need to worry about serialization and deserialization. ``` typescript export async function getStakeAccount( program: any, user: PublicKey, tokenAccount: PublicKey ): Promise<StakeAccount> { const [pda] = PublicKey.findProgramAddressSync( [user.toBuffer(), tokenAccount.toBuffer()], program.programId ) const account = await program.account.userStakeInfo.fetch(pda) return account } ``` It's already much simpler than before, right?!? Back over to `checkStakingStatus` in `StakeOptionsDisplay`, where `getStakeAccount` is called, change the first argument from `connection` to `workspace.program`. Go to your browser, make sure everything is still functional on localhost. Back into StakeOptionsDisplay, scroll down to the `handleStake` function. Again, first add a check for `!workspace.program`. We're gonna want to add this to our `handleUnstake` and `handleClaim` functions soon enough. You can immediately delete all this code from our previous work. ``` typescript const account = await connection.getAccountInfo(stakeAccount) if (!account) { transaction.add( createInitializeStakeAccountInstruction( walletAdapter.publicKey, nftTokenAccount, PROGRAM_ID ) ) } const stakeInstruction = createStakingInstruction( walletAdapter.publicKey, nftTokenAccount, nftData.mint.address, nftData.edition.address, TOKEN_PROGRAM_ID, METADATA_PROGRAM_ID, PROGRAM_ID ) ``` ... and simply replace it with: ``` typescript transaction.add( await workspace.program.methods .stake() .accounts({ nftTokenAccount: nftTokenAccount, nftMint: nftData.mint.address, nftEdition: nftData.edition.address, metadataProgram: METADATA_PROGRAM_ID, }) .instruction() ) ``` This also means a bunch of code we created in the instructions.ts file is no longer necessary. Again, hop over to the browser and test things out. Assuming things are functioning as expected, let's tackle the `handleUnstake` code. Let's dump this code as this is all handled by the program now. ``` typescript const account = await connection.getAccountInfo(userStakeATA) const transaction = new Transaction() if (!account) { transaction.add( createAssociatedTokenAccountInstruction( walletAdapter.publicKey, userStakeATA, walletAdapter.publicKey, STAKE_MINT ) ) } ``` Then inside the `transaction.add` get rid of the `createUnstakeInstruction` and replace it with this: ``` typescript transaction.add( await workspace.program.methods .unstake() .accounts({ nftTokenAccount: nftTokenAccount, nftMint: nftData.mint.address, nftEdition: nftData.edition.address, metadataProgram: METADATA_PROGRAM_ID, stakeMint: STAKE_MINT, userStakeAta: userStakeATA, }) .instruction() ) ``` You'll notice it's the same accounts as `handleStake` plus a couple more for the stake mint and user ATA. Finally, down to `handleClaim`, follow the same pattern of deleting, and the new transaction.add should look like this: ``` typescript transaction.add( await workspace.program.methods .redeem() .accounts({ nftTokenAccount: nftTokenAccount, stakeMint: STAKE_MINT, userStakeAta: userStakeATA, }) .instruction() ) ``` You can now just delete the `instructions.ts` files all together. Do it!!! :) Feel free to clean up your unused imports to clean up your file. One more thing we need to do, inside the tokens directory, where we've made our reward token, we need to reinitialize this with the new program ID. In the `bld/index.ts` file, when `await createBldToken` is called, that's where the new program ID needs to be replaced. Then re-run `npm run create-bld-token` script. If we don't do this, our redeem won't work. This will create a new mint program ID, which you need to add to your environment variables. That's it, we have some functionality working on the front-end. Next week, we'll do a ton more shipping using Anchor, for now we just wanted to show how much easier it is, and get the basic functions up and running. ### 5.4.6 Time to ship Core 5! 🚢 🛳️ 🚀 You are officially one week away from becoming a Solana 🐐. Don't forget to drop your shipment for this week!