Odak modu
Our movie review program is decent, but it's not very web3. All we're doing right now is using Solana as a database. Let's spice it up by giving people tokens for using our app! We'll mint tokens to someone every time they review a movie or leave a comment. Think of it like StackOverflow but with tokens instead of upvotes.
You can pick up where you left off in your last local environment, or set up a new one by copying this or:
git clone https://github.com/buildspace/solana-movie-program/
cd solana-movie-program
git checkout solution-add-comments
npm i
We'll be using the SPL token program to make all of this magic happen so go ahead and update the dependencies in Cargo.toml
:
[dependencies]
solana-program = "~1.10.29"
borsh = "0.9.3"
thiserror = "1.0.31"
spl-token = { version="3.2.0", features = [ "no-entrypoint" ] }
spl-associated-token-account = { version="=1.0.5", features = [ "no-entrypoint" ] }
Let's quickly test out that everything builds with these new dependencies with cargo build-bpf
.
We're ready to get building!
We'll start by creating a token mint. Reminder: a token mint is a special account that holds data about our token.
This is a new instruction, so we'll follow the same steps as we did when we added support for comments:
process_instruction
funcitonFrom the top in instruction.rs
, we've got the enum update:
pub enum MovieInstruction {
AddMovieReview {
title: String,
rating: u8,
description: String,
},
UpdateMovieReview {
title: String,
rating: u8,
description: String,
},
AddComment {
comment: String,
},
InitializeMint,
}
We won't need any fields here - all it takes to call that function is addresses!
Next, we'll update the unpack function:
impl MovieInstruction {
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
let (&variant, rest) = input
.split_first()
.ok_or(ProgramError::InvalidInstructionData)?;
Ok(match variant {
0 => {
let payload = MovieReviewPayload::try_from_slice(rest).unwrap();
Self::AddMovieReview {
title: payload.title,
rating: payload.rating,
description: payload.description,
}
}
1 => {
let payload = MovieReviewPayload::try_from_slice(rest).unwrap();
Self::UpdateMovieReview {
title: payload.title,
rating: payload.rating,
description: payload.description,
}
}
2 => {
let payload = CommentPayload::try_from_slice(rest).unwrap();
Self::AddComment {
comment: payload.comment,
}
}
// New variant added here
3 => Self::InitializeMint,
_ => return Err(ProgramError::InvalidInstructionData),
})
}
}
You'll immediately notice an error in the process_instruction
match statement in processor.rs
since we're not handling all the cases. Let's fix this by bringing in our new SPL imports and adding to the match statement:
// Update imports at the top
use solana_program::{
//Existing imports within solana_program
sysvar::{rent::Rent, Sysvar, rent::ID as RENT_PROGRAM_ID},
native_token::LAMPORTS_PER_SOL,
system_program::ID as SYSTEM_PROGRAM_ID
}
use spl_associated_token_account::get_associated_token_address;
use spl_token::{instruction::initialize_mint, ID as TOKEN_PROGRAM_ID};
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = MovieInstruction::unpack(instruction_data)?;
match instruction {
MovieInstruction::AddMovieReview {
title,
rating,
description,
} => add_movie_review(program_id, accounts, title, rating, description),
MovieInstruction::UpdateMovieReview {
title,
rating,
description,
} => update_movie_review(program_id, accounts, title, rating, description),
MovieInstruction::AddComment { comment } => add_comment(program_id, accounts, comment),
// New instruction handled here to initialize the mint account
MovieInstruction::InitializeMint => initialize_token_mint(program_id, accounts),
}
}
// Rest of the file remains the same
Finally, we can implement the initialize_token_mint
account at the bottom of processor.rs
after the add_comment
function:
pub fn initialize_token_mint(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
// The order of accounts is not arbitrary, the client will send them in this order
// Whoever sent in the transaction
let initializer = next_account_info(account_info_iter)?;
// Token mint PDA - derived on the client
let token_mint = next_account_info(account_info_iter)?;
// Token mint authorirty (this should be you)
let mint_auth = next_account_info(account_info_iter)?;
// System program to create a new account
let system_program = next_account_info(account_info_iter)?;
// Solana Token program address
let token_program = next_account_info(account_info_iter)?;
// System account to calcuate the rent
let sysvar_rent = next_account_info(account_info_iter)?;
// Derive the mint PDA again so we can validate it
// The seed is just "token_mint"
let (mint_pda, mint_bump) = Pubkey::find_program_address(&[b"token_mint"], program_id);
// Derive the mint authority so we can validate it
// The seed is just "token_auth"
let (mint_auth_pda, _mint_auth_bump) =
Pubkey::find_program_address(&[b"token_auth"], program_id);
msg!("Token mint: {:?}", mint_pda);
msg!("Mint authority: {:?}", mint_auth_pda);
// Validate the important accounts passed in
if mint_pda != *token_mint.key {
msg!("Incorrect token mint account");
return Err(ReviewError::IncorrectAccountError.into());
}
if *token_program.key != TOKEN_PROGRAM_ID {
msg!("Incorrect token program");
return Err(ReviewError::IncorrectAccountError.into());
}
if *mint_auth.key != mint_auth_pda {
msg!("Incorrect mint auth account");
return Err(ReviewError::IncorrectAccountError.into());
}
if *system_program.key != SYSTEM_PROGRAM_ID {
msg!("Incorrect system program");
return Err(ReviewError::IncorrectAccountError.into());
}
if *sysvar_rent.key != RENT_PROGRAM_ID {
msg!("Incorrect rent program");
return Err(ReviewError::IncorrectAccountError.into());
}
// Calculate the rent
let rent = Rent::get()?;
// We know the size of a mint account is 82 (remember it lol)
let rent_lamports = rent.minimum_balance(82);
// Create the token mint PDA
invoke_signed(
&system_instruction::create_account(
initializer.key,
token_mint.key,
rent_lamports,
82, // Size of the token mint account
token_program.key,
),
// Accounts we're reading from or writing to
&[
initializer.clone(),
token_mint.clone(),
system_program.clone(),
],
// Seeds for our token mint account
&[&[b"token_mint", &[mint_bump]]],
)?;
msg!("Created token mint account");
// Initialize the mint account
invoke_signed(
&initialize_mint(
token_program.key,
token_mint.key,
mint_auth.key,
Option::None, // Freeze authority - we don't want anyone to be able to freeze!
9, // Number of decimals
)?,
// Which accounts we're reading from or writing to
&[token_mint.clone(), sysvar_rent.clone(), mint_auth.clone()],
// The seeds for our token mint PDA
&[&[b"token_mint", &[mint_bump]]],
)?;
msg!("Initialized token mint");
Ok(())
}
At a high level, here's what's happening here:
Go over the code comments, I added context wherever I could!
You'll now be getting an error since we're calling a new error without declaring it. Pop open error.rs
and add IncorrectAccountError
to the ReviewError
enum:
#[derive(Debug, Error)]
pub enum ReviewError {
#[error("Account not initialized yet")]
UninitializedAccount,
#[error("PDA derived does not equal PDA passed in")]
InvalidPDA,
#[error("Input data exceeds max length")]
InvalidDataLength,
#[error("Rating greater than 5 or less than 1")]
InvalidRating,
// New error added
#[error("Accounts do not match")]
IncorrectAccountError,
}
Pretty self-explanatory :)
Open up the target folder in your file explorer and get rid of the keypair in the deploy folder.
In your console:
cargo build-bpf
Copy and paste the deploy command that's printed out.
If you get insufficient funds
just run solana airdrop 2
.
Once you've deployed locally, it's time to test! We're going to use a local client script to test out initializing the account. Here's what you'll need to set up:
git clone https://github.com/buildspace/solana-movie-token-client
cd solana-movie-token-client
npm install
Before you can run the script you'll need to:
PROGRAM_ID
in index.ts
const connection = new web3.Connection("http://localhost:8899");
solana logs PROGRAM_ID_HERE
in a second console windowYou should now have a console logging all the outputs for this program and are ready to run the script!
If you run npm start
you should see the logs for the mint account creation :D
Sektörde en çok aranan yazılım becerilerini kazan
Yapay zeka desteği, birebir mentörlük saatleri, canlı dersler ve senin için özel hazırlanmış içeriklerle eksiklerini tamamla, düzenli geri bildirimler al ve öğrenme sürecini en verimli hale getir.
Yunus Emre Kabakcı
Patika+ mezunuPatika+ Fullstack Web Development Bootcamp mezunumuz Yunus Emre,
3 ay içinde Katar’dan aldığı teklif ile, global bir şirket olan Pavo Group’da işe başladı!
“İçerik zenginliği, mentor desteği, ileriye dönük bir network sağlaması ve dünyada en çok tercih edilen frameworkler üzerinden bir eğitim veriyor olması Patika+’ı tercih etmemin temel sebepleri oldu!“
Yorum yapabilmek için derse kayıt olmalısın!