Utilisation de jetons dans des jeux sur Solana
Les tokens sur Solana peuvent servir à diverses fins, comme des récompenses dans les jeux, des incitations ou d'autres applications. Par exemple, vous pouvez créer des jetons et les distribuer aux joueurs lorsqu'ils accomplissent des actions spécifiques dans le jeu.
Créer, Mint et Brûler des jetons avec Anchor
Dans ce tutoriel, nous allons créer un jeu en utilisant Anchor pour introduire les bases de l'interaction avec le Programme de Jeton (Token Program) de Solana. Le jeu s'articulera autour de quatre actions principales : créer une nouvelle adresse de mint pour le jeton, initialiser les comptes des joueurs, récompenser les joueurs pour avoir vaincu des ennemis et permettre aux joueurs de se soigner en brûlant des jetons.
Le programme est composé de 4 instructions :
create_mint
- Cette instruction crée une nouvelle adresse de mint pour le jeton avec une Adresse Dérivée de Programme (PDA) en tant qu'autorité de mint (mint authority) et crée le compte de métadonnées de jeton. Nous ajouterons une contrainte pour que seul un "admin" puisse invoquer cette instructioninit_player
- Cette instruction initialise un nouveau compte de joueur avec une santé de départ de 100kill_enemy
- Cette instruction déduit 10 points de vie du compte du joueur lorsqu'il "vainc un ennemi" et mint 1 jeton en guise de récompense pour le joueurheal
- Cette instruction permet à un joueur de brûler un jeton pour rétablir sa santé à 100 %.
Pour une vue d'ensemble de la relation entre les portefeuilles utilisateurs, les adresses de mint de jeton, les comptes de jetons et les comptes de métadonnées de jetons, il est possible d'explorer cette partie de la documentation de Metaplex.
Pour commencer
Pour commencer à créer le programme, suivez les étapes suivantes :
Visitez le Solana Playground et créez un nouveau projet Anchor. Si vous êtes nouveau sur Solana Playground, vous devrez également créer un portefeuille Playground.
Après avoir créé un nouveau projet, remplacez le code par défaut par le code ci-dessous :
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
metadata::{create_metadata_accounts_v3, CreateMetadataAccountsV3, Metadata},
token::{burn, mint_to, Burn, Mint, MintTo, Token, TokenAccount},
};
use mpl_token_metadata::{pda::find_metadata_account, state::DataV2};
use solana_program::{pubkey, pubkey::Pubkey};
declare_id!("11111111111111111111111111111111");
#[program]
pub mod anchor_token {
use super::*;
}
Ici, nous mettons simplement en place les crates et les modules correspondants que nous utiliserons dans le cadre de ce programme. Nous allons utiliser les crates anchor_spl
et mpl_token_metadata
pour nous aider à interagir avec le Programme de Jeton et le Programme de Métadonnées des Jetons (Token Metadata program).
Instruction de création d'un jeton
Tout d'abord, implémentons une instruction pour créer un nouveau jeton et son compte de métadonnées. Les métadonnées du jeton qui seront stockées sur la chaîne, notamment le nom, le symbole et l'URI, seront fournies en tant que paramètres de l'instruction.
De plus, nous n'autoriserons qu'un "admin" à invoquer cette instruction en définissant une constante ADMIN_PUBKEY
et en l'utilisant comme contrainte. Veillez à remplacer ADMIN_PUBKEY
par la clé publique de votre portefeuille Solana Playground.
L'instruction create_mint
nécessite les comptes suivants :
admin
- leADMIN_PUBKEY
qui signe la transaction et paie pour l'initialisation des comptesreward_token_mint
- la nouvelle adresse de mint de jeton que nous initialisons, avec un PDA comme adresse de mint et comme autorité de mintmetadata_account
- le compte de métadonnées que nous initialisons pour le jetontoken_program
- nécessaire pour interagir avec les instructions du Programme de Jetontoken_metadata_program
- compte nécessaire pour interagir avec les instructions du Programme de Métadonnées des Jetonssystem_program
- compte obligatoire lors de la création d'un nouveau compterent
- Sysvar Rent, un compte nécessaire lors de la création du compte de métadonnées
// Only this public key can call this instruction
const ADMIN_PUBKEY: Pubkey = pubkey!("REPLACE_WITH_YOUR_WALLET_PUBKEY");
#[program]
pub mod anchor_token {
use super::*;
// Create new token mint with PDA as mint authority
pub fn create_mint(
ctx: Context<CreateMint>,
uri: String,
name: String,
symbol: String,
) -> Result<()> {
// PDA seeds and bump to "sign" for CPI
let seeds = b"reward";
let bump = *ctx.bumps.get("reward_token_mint").unwrap();
let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]];
// On-chain token metadata for the mint
let data_v2 = DataV2 {
name: name,
symbol: symbol,
uri: uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
};
// CPI Context
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMetadataAccountsV3 {
metadata: ctx.accounts.metadata_account.to_account_info(), // the metadata account being created
mint: ctx.accounts.reward_token_mint.to_account_info(), // the mint account of the metadata account
mint_authority: ctx.accounts.reward_token_mint.to_account_info(), // the mint authority of the mint account
update_authority: ctx.accounts.reward_token_mint.to_account_info(), // the update authority of the metadata account
payer: ctx.accounts.admin.to_account_info(), // the payer for creating the metadata account
system_program: ctx.accounts.system_program.to_account_info(), // the system program account
rent: ctx.accounts.rent.to_account_info(), // the rent sysvar account
},
signer,
);
create_metadata_accounts_v3(
cpi_ctx, // cpi context
data_v2, // token metadata
true, // is_mutable
true, // update_authority_is_signer
None, // collection details
)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(
mut,
address = ADMIN_PUBKEY
)]
pub admin: Signer<'info>,
// The PDA is both the address of the mint account and the mint authority
#[account(
init,
seeds = [b"reward"],
bump,
payer = admin,
mint::decimals = 9,
mint::authority = reward_token_mint,
)]
pub reward_token_mint: Account<'info, Mint>,
///CHECK: Using "address" constraint to validate metadata account address
#[account(
mut,
address=find_metadata_account(&reward_token_mint.key()).0
)]
pub metadata_account: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub token_metadata_program: Program<'info, Metadata>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
}
L'instruction create_mint
crée une nouvelle adresse de mint de jeton avec une Adresse Dérivée de Programme (PDA) comme adresse de mint et comme autorité de mint. L'instruction prend en paramètre un URI (métadonnées hors chaîne), un nom et un symbole.
Cette instruction crée ensuite un compte de métadonnées de jeton par le biais d'une Invocation de Programme Croisé (CPI) appelant l'instruction create_metadata_accounts_v3
du Programme de Métadonnées des Jetons.
Le PDA est utilisé pour "signer" le CPI puiqu'il est l'autorité de mint, qui est un signataire obligatoire lors de la création du compte de métadonnées de jeton. Les données d'instruction (URI, nom, symbole) sont incluses dans la structure DataV2
pour spécifier les métadonnées du nouveau jeton.
Nous vérifions également que l'adresse du compte admin
signant la transaction correspond à la valeur de la constante ADMIN_PUBKEY
afin de s'assurer que seul le portefeuille souhaité peut invoquer cette instruction.
const ADMIN_PUBKEY: Pubkey = pubkey!("REPLACE_WITH_YOUR_WALLET_PUBKEY");
Instruction d'initialisation du joueur
Ensuite, implémentons l'instruction init_player
qui crée un nouveau compte de joueur avec une santé initiale de 100. La constante MAX_HEALTH
est fixée à 100 pour représenter la santé de départ.
L'instruction init_player
nécessite les comptes suivants :
player_data
- le nouveau compte de joueur que nous initialisons, qui stockera la santé du joueurplayer
- l'utilisateur qui signe la transaction et paie pour l'initialisation du comptesystem_program
- compte obligatoire lors de la création d'un nouveau compte
// Player max health
const MAX_HEALTH: u8 = 100;
#[program]
pub mod anchor_token {
use super::*;
...
// Create new player account
pub fn init_player(ctx: Context<InitPlayer>) -> Result<()> {
ctx.accounts.player_data.health = MAX_HEALTH;
Ok(())
}
}
...
#[derive(Accounts)]
pub struct InitPlayer<'info> {
#[account(
init,
payer = player,
space = 8 + 8,
seeds = [b"player".as_ref(), player.key().as_ref()],
bump,
)]
pub player_data: Account<'info, PlayerData>,
#[account(mut)]
pub player: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct PlayerData {
pub health: u8,
}
Le compte player_data
est initialisé à l'aide d'une Adresse Dérivée de Programme (PDA) dont la clé publique player
est l'une des seeds. Cela garantit que chaque compte player_data
est unique et associé à un player
, ce qui permet à chaque joueur de créer son propre compte player_data
.
Instruction pour tuer un ennemi
Implémentons ensuite l'instruction kill_enemy
qui réduit la santé du joueur de 10 et lui rapporte 1 jeton sur son compte de jetons en guise de récompense.
L'instruction kill_enemy
nécessite les comptes suivants :
player
- le joueur qui reçoit le jetonplayer_data
- le compte de données du joueur stockant l'état de santé actuel du joueurplayer_token_account
- le compte de jetons associé du joueur où les jetons seront mintreward_token_mint
- l'adresse de mint du jeton, spécifiant le type de jeton qui sera minttoken_program
- nécessaire pour interagir avec les instructions du Programme de Jetonassociated_token_program
- nécessaire lorsque l'on travaille avec des comptes de jetons associéssystem_program
- compte obligatoire lors de la création d'un nouveau compte
#[program]
pub mod anchor_token {
use super::*;
...
// Mint token to player token account
pub fn kill_enemy(ctx: Context<KillEnemy>) -> Result<()> {
// Check if player has enough health
if ctx.accounts.player_data.health == 0 {
return err!(ErrorCode::NotEnoughHealth);
}
// Subtract 10 health from player
ctx.accounts.player_data.health = ctx.accounts.player_data.health.checked_sub(10).unwrap();
// PDA seeds and bump to "sign" for CPI
let seeds = b"reward";
let bump = *ctx.bumps.get("reward_token_mint").unwrap();
let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]];
// CPI Context
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.reward_token_mint.to_account_info(),
to: ctx.accounts.player_token_account.to_account_info(),
authority: ctx.accounts.reward_token_mint.to_account_info(),
},
signer,
);
// Mint 1 token, accounting for decimals of mint
let amount = (1u64)
.checked_mul(10u64.pow(ctx.accounts.reward_token_mint.decimals as u32))
.unwrap();
mint_to(cpi_ctx, amount)?;
Ok(())
}
}
...
#[derive(Accounts)]
pub struct KillEnemy<'info> {
#[account(mut)]
pub player: Signer<'info>,
#[account(
mut,
seeds = [b"player".as_ref(), player.key().as_ref()],
bump,
)]
pub player_data: Account<'info, PlayerData>,
// Initialize player token account if it doesn't exist
#[account(
init_if_needed,
payer = player,
associated_token::mint = reward_token_mint,
associated_token::authority = player
)]
pub player_token_account: Account<'info, TokenAccount>,
#[account(
mut,
seeds = [b"reward"],
bump,
)]
pub reward_token_mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}
#[error_code]
pub enum ErrorCode {
#[msg("Not enough health")]
NotEnoughHealth,
}
La santé du joueur est réduite de 10 pour représenter le "combat avec l'ennemi". Nous allons également vérifier la santé actuelle du joueur et renvoyer une erreur Anchor personnalisée si la santé du joueur est à 0.
L'instruction utilise ensuite une Invocation de Programme Croisé (CPI) pour appeler l'instruction mint_to
du Programme de Jeton et mint 1 jeton reward_token_mint
dans player_token_account
en guise de récompense pour avoir tué l'ennemi.
Étant donné que l'autorité de mint du jeton est une Adresse Dérivée de Programme (PDA), nous pouvons mint des jetons directement en appelant cette instruction sans signataire supplémentaire. En effet, le programme peut "signer" au nom du PDA, ce qui permet de mint des jetons sans exiger explicitement des signataires supplémentaires.
Instruction de guérison
Implémentons ensuite l'instruction heal
qui permet à un joueur de brûler 1 jeton et de restaurer sa santé à sa valeur maximale.
L'instruction heal
nécessite les comptes suivants :
player
- le joueur qui exécute l'action de guérisonplayer_data
- le compte de données du joueur stockant l'état de santé actuel du joueurplayer_token_account
- le compte de jetons associé du joueur où les jetons seront brûlésreward_token_mint
- l'adresse de mint du jeton, spécifiant le type de jeton qui sera brûlétoken_program
- nécessaire pour interagir avec les instructions du Programme de Jetonassociated_token_program
- nécessaire lorsque l'on travaille avec des comptes de jetons associés
#[program]
pub mod anchor_token {
use super::*;
...
// Burn token to health player
pub fn heal(ctx: Context<Heal>) -> Result<()> {
ctx.accounts.player_data.health = MAX_HEALTH;
// CPI Context
let cpi_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Burn {
mint: ctx.accounts.reward_token_mint.to_account_info(),
from: ctx.accounts.player_token_account.to_account_info(),
authority: ctx.accounts.player.to_account_info(),
},
);
// Burn 1 token, accounting for decimals of mint
let amount = (1u64)
.checked_mul(10u64.pow(ctx.accounts.reward_token_mint.decimals as u32))
.unwrap();
burn(cpi_ctx, amount)?;
Ok(())
}
}
...
#[derive(Accounts)]
pub struct Heal<'info> {
#[account(mut)]
pub player: Signer<'info>,
#[account(
mut,
seeds = [b"player".as_ref(), player.key().as_ref()],
bump,
)]
pub player_data: Account<'info, PlayerData>,
#[account(
mut,
associated_token::mint = reward_token_mint,
associated_token::authority = player
)]
pub player_token_account: Account<'info, TokenAccount>,
#[account(
mut,
seeds = [b"reward"],
bump,
)]
pub reward_token_mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
La santé du joueur est rétablie à sa valeur maximale à l'aide de l'instruction heal
. L'instruction utilise ensuite une Invocation de Programme Croisé (CPI) pour appeler l'instruction burn
du Programme de Jeton, qui brûle 1 token du player_token_account
pour guérir le joueur.
Compilation et Déploiement
Bravo ! Vous avez terminé le programme ! Vous pouvez le compiler et le déployer à l'aide du Solana Playground. Votre programme final devrait ressembler à ceci :
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
metadata::{create_metadata_accounts_v3, CreateMetadataAccountsV3, Metadata},
token::{burn, mint_to, Burn, Mint, MintTo, Token, TokenAccount},
};
use mpl_token_metadata::{pda::find_metadata_account, state::DataV2};
use solana_program::{pubkey, pubkey::Pubkey};
declare_id!("CCLnXJAJYFjCHLCugpBCEQKrpiSApiRM4UxkBUHJRrv4");
const ADMIN_PUBKEY: Pubkey = pubkey!("REPLACE_WITH_YOUR_WALLET_PUBKEY");
const MAX_HEALTH: u8 = 100;
#[program]
pub mod anchor_token {
use super::*;
// Create new token mint with PDA as mint authority
pub fn create_mint(
ctx: Context<CreateMint>,
uri: String,
name: String,
symbol: String,
) -> Result<()> {
// PDA seeds and bump to "sign" for CPI
let seeds = b"reward";
let bump = *ctx.bumps.get("reward_token_mint").unwrap();
let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]];
// On-chain token metadata for the mint
let data_v2 = DataV2 {
name: name,
symbol: symbol,
uri: uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
};
// CPI Context
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMetadataAccountsV3 {
metadata: ctx.accounts.metadata_account.to_account_info(), // the metadata account being created
mint: ctx.accounts.reward_token_mint.to_account_info(), // the mint account of the metadata account
mint_authority: ctx.accounts.reward_token_mint.to_account_info(), // the mint authority of the mint account
update_authority: ctx.accounts.reward_token_mint.to_account_info(), // the update authority of the metadata account
payer: ctx.accounts.admin.to_account_info(), // the payer for creating the metadata account
system_program: ctx.accounts.system_program.to_account_info(), // the system program account
rent: ctx.accounts.rent.to_account_info(), // the rent sysvar account
},
signer,
);
create_metadata_accounts_v3(
cpi_ctx, // cpi context
data_v2, // token metadata
true, // is_mutable
true, // update_authority_is_signer
None, // collection details
)?;
Ok(())
}
// Create new player account
pub fn init_player(ctx: Context<InitPlayer>) -> Result<()> {
ctx.accounts.player_data.health = MAX_HEALTH;
Ok(())
}
// Mint tokens to player token account
pub fn kill_enemy(ctx: Context<KillEnemy>) -> Result<()> {
// Check if player has enough health
if ctx.accounts.player_data.health == 0 {
return err!(ErrorCode::NotEnoughHealth);
}
// Subtract 10 health from player
ctx.accounts.player_data.health = ctx.accounts.player_data.health.checked_sub(10).unwrap();
// PDA seeds and bump to "sign" for CPI
let seeds = b"reward";
let bump = *ctx.bumps.get("reward_token_mint").unwrap();
let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]];
// CPI Context
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.reward_token_mint.to_account_info(),
to: ctx.accounts.player_token_account.to_account_info(),
authority: ctx.accounts.reward_token_mint.to_account_info(),
},
signer,
);
// Mint 1 token, accounting for decimals of mint
let amount = (1u64)
.checked_mul(10u64.pow(ctx.accounts.reward_token_mint.decimals as u32))
.unwrap();
mint_to(cpi_ctx, amount)?;
Ok(())
}
// Burn Token to health player
pub fn heal(ctx: Context<Heal>) -> Result<()> {
ctx.accounts.player_data.health = MAX_HEALTH;
// CPI Context
let cpi_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Burn {
mint: ctx.accounts.reward_token_mint.to_account_info(),
from: ctx.accounts.player_token_account.to_account_info(),
authority: ctx.accounts.player.to_account_info(),
},
);
// Burn 1 token, accounting for decimals of mint
let amount = (1u64)
.checked_mul(10u64.pow(ctx.accounts.reward_token_mint.decimals as u32))
.unwrap();
burn(cpi_ctx, amount)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(
mut,
address = ADMIN_PUBKEY
)]
pub admin: Signer<'info>,
// The PDA is both the address of the mint account and the mint authority
#[account(
init,
seeds = [b"reward"],
bump,
payer = admin,
mint::decimals = 9,
mint::authority = reward_token_mint,
)]
pub reward_token_mint: Account<'info, Mint>,
///CHECK: Using "address" constraint to validate metadata account address
#[account(
mut,
address=find_metadata_account(&reward_token_mint.key()).0
)]
pub metadata_account: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub token_metadata_program: Program<'info, Metadata>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct InitPlayer<'info> {
#[account(
init,
payer = player,
space = 8 + 8,
seeds = [b"player".as_ref(), player.key().as_ref()],
bump,
)]
pub player_data: Account<'info, PlayerData>,
#[account(mut)]
pub player: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct KillEnemy<'info> {
#[account(mut)]
pub player: Signer<'info>,
#[account(
mut,
seeds = [b"player".as_ref(), player.key().as_ref()],
bump,
)]
pub player_data: Account<'info, PlayerData>,
// Initialize player token account if it doesn't exist
#[account(
init_if_needed,
payer = player,
associated_token::mint = reward_token_mint,
associated_token::authority = player
)]
pub player_token_account: Account<'info, TokenAccount>,
#[account(
mut,
seeds = [b"reward"],
bump,
)]
pub reward_token_mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Heal<'info> {
#[account(mut)]
pub player: Signer<'info>,
#[account(
mut,
seeds = [b"player".as_ref(), player.key().as_ref()],
bump,
)]
pub player_data: Account<'info, PlayerData>,
#[account(
mut,
associated_token::mint = reward_token_mint,
associated_token::authority = player
)]
pub player_token_account: Account<'info, TokenAccount>,
#[account(
mut,
seeds = [b"reward"],
bump,
)]
pub reward_token_mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
#[account]
pub struct PlayerData {
pub health: u8,
}
#[error_code]
pub enum ErrorCode {
#[msg("Not enough health")]
NotEnoughHealth,
}
Débuter avec le Client
Dans cette section, nous allons vous présenter une implémentation simple côté client pour interagir avec le programme. Pour commencer, naviguez jusqu'au fichier client.ts
dans Solana Playground, supprimez le code par défaut et ajoutez les extraits de code des sections suivantes.
Commencez par ajouter le code suivant pour la configuration.
import { Metaplex } from "@metaplex-foundation/js";
import { getMint, getAssociatedTokenAddressSync } from "@solana/spl-token";
// metaplex token metadata program ID
const TOKEN_METADATA_PROGRAM_ID = new web3.PublicKey(
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
);
// metaplex setup
const metaplex = Metaplex.make(pg.connection);
// token metadata
const metadata = {
uri: "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json",
name: "Solana Gold",
symbol: "GOLDSOL",
};
// reward token mint PDA
const [rewardTokenMintPda] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("reward")],
pg.PROGRAM_ID
);
// player data account PDA
const [playerPDA] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("player"), pg.wallet.publicKey.toBuffer()],
pg.PROGRAM_ID
);
// reward token mint metadata account address
const rewardTokenMintMetadataPDA = await metaplex
.nfts()
.pdas()
.metadata({ mint: rewardTokenMintPda });
// player token account address
const playerTokenAccount = getAssociatedTokenAddressSync(
rewardTokenMintPda,
pg.wallet.publicKey
);
Ensuite, ajoutez les deux fonctions d'aide suivantes. Ces fonctions seront utilisées pour confirmer les transactions et récupérer les données du compte.
async function logTransaction(txHash) {
const { blockhash, lastValidBlockHeight } =
await pg.connection.getLatestBlockhash();
await pg.connection.confirmTransaction({
blockhash,
lastValidBlockHeight,
signature: txHash,
});
console.log(`Use 'solana confirm -v ${txHash}' to see the logs`);
}
async function fetchAccountData() {
const [playerBalance, playerData] = await Promise.all([
pg.connection.getTokenAccountBalance(playerTokenAccount),
pg.program.account.playerData.fetch(playerPDA),
]);
console.log("Player Token Balance: ", playerBalance.value.uiAmount);
console.log("Player Health: ", playerData.health);
}
Ensuite, invoquez l'instruction createMint
pour créer une nouvelle adresse de mint de jeton s'il n'existe pas déjà.
let txHash;
try {
const mintData = await getMint(pg.connection, rewardTokenMintPda);
console.log("Mint Already Exists");
} catch {
txHash = await pg.program.methods
.createMint(metadata.uri, metadata.name, metadata.symbol)
.accounts({
rewardTokenMint: rewardTokenMintPda,
metadataAccount: rewardTokenMintMetadataPDA,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
})
.rpc();
await logTransaction(txHash);
}
console.log("Token Mint: ", rewardTokenMintPda.toString());
Appelez ensuite l'instruction initPlayer
pour créer un nouveau compte de joueur s'il n'en existe pas déjà un.
try {
const playerData = await pg.program.account.playerData.fetch(playerPDA);
console.log("Player Already Exists");
console.log("Player Health: ", playerData.health);
} catch {
txHash = await pg.program.methods
.initPlayer()
.accounts({
playerData: playerPDA,
player: pg.wallet.publicKey,
})
.rpc();
await logTransaction(txHash);
console.log("Player Account Created");
}
Ensuite, invoquez l'instruction killEnemy
.
txHash = await pg.program.methods
.killEnemy()
.accounts({
playerData: playerPDA,
playerTokenAccount: playerTokenAccount,
rewardTokenMint: rewardTokenMintPda,
})
.rpc();
await logTransaction(txHash);
console.log("Enemy Defeated");
await fetchAccountData();
Puis, invoquez l'instruction heal
.
txHash = await pg.program.methods
.heal()
.accounts({
playerData: playerPDA,
playerTokenAccount: playerTokenAccount,
rewardTokenMint: rewardTokenMintPda,
})
.rpc();
await logTransaction(txHash);
console.log("Player Healed");
await fetchAccountData();
Enfin, exécutez le client en cliquant sur le bouton "Run" dans Solana Playground. Vous pouvez copier l'adresse de mint de jeton affichée dans la console et vérifier sur Solana Explorer que le jeton a maintenant des métadonnées. Le résultat devrait être similaire à ce qui suit :
Running client...
client.ts:
Use 'solana confirm -v 3AWnpt2Wy6jQckue4QeKsgDNKhKkhpewPmRtxvJpzxGgvK9XK9KEpTiUzAQ5vSC6CUoUjc6xWZCtrihVrFy8sACC' to see the logs
Token Mint: 3eS7hdyeVX5g8JGhn3Z7qFXJaewoJ8hzgvubovQsPm4S
Use 'solana confirm -v 63jbBr5U4LG75TiiHfz65q7yKJfHDhGP2ocCiDat5M2k4cWtUMAx9sHvxhnEguLDKXMbDUQKUt1nhvyQkXoDhxst' to see the logs
Player Account Created
Use 'solana confirm -v 2ziK41WLoxfEHvtUgc5c1SyKCAr5FvAS54ARBJrjqh9GDwzYqu7qWCwHJCgMZyFEVovYK5nUZhDRHPTMrTjq1Mm6' to see the logs
Enemy Defeated
Player Token Balance: 1
Player Health: 90
Use 'solana confirm -v 2QoAH22Q3xXz9t2TYRycQMqpEmauaRvmUfZ7ZNKUEoUyHWqpjW972VD3eZyeJrXsviaiCC3g6TE54oKmKbFQf2Q7' to see the logs
Player Healed
Player Token Balance: 0
Player Health: 100