Challenge #3
Introduction To Spl Tokens
This guide will teach you how to build and deploy the Solana program and connect to the UI for a basic SPl token mint and transfer dApp. This d-App will allow you to mint and transfer SPL tokens on Solana.
What you will learn
- Setting up your environment
- Anchor program development
- How to build CPI to spl token Program
- Building a solana program
- Deploying a Solana program
- Connecting an on-chain program to UI
Prerequisites
For this guide, you will need to have your local development environment setup with a few tools:
What we are building
We are developing an SPL token mint and Transfer program that creates spl token mint and transfers tokens to another user. In this dapp, we will utilize both types of tokens fungible and NFT on the Solana DevNet network.
Live At: spldapp_live
Setting up the project
Clone this repository into your local system
git clone git@github.com:solana-based-quests/S-Sol-tokens.git
Now change the directory to
cd s-sol-tokens
Then head over to the client folder to run this d-app
cd solana-client
Update project dependencies
npm install
And Run
npm run dev
Dapp accounts overview
In the above diagram, the user's wallet is a signer that will pay for all account creations. The mint account includes the decimal, mint authority and freezes authority over tokens. The system program facilitates the creation of the Mint account. The token account is used for creating an Associated token account for holding SPL tokens. All other Solana ecosystem accounts facilitate the minting and transferring operation of SPL Token.
Anchor program development
If you're new to Anchor, The Anchor Book and Anchor Examples are great references to help you learn.
In s-sol-tokens, navigate to spltokens-sol-program/programs/src/lib.rs.
use anchor_lang::prelude::*;
pub mod instructions;
pub use instructions::*;
declare_id!("C2eEY8eeediwD2YXvZZGQ74G9kPB5PeRNFGUzSiFadcW");
#[program]
pub mod spltokens {
use super::*;
pub fn mint_nft(ctx: Context<MintNFT>, name: String, symbol: String, uri: String) -> Result<()>
{
mint_nft::mint_nft(ctx, name, symbol, uri)
}
pub fn mint_token(ctx: Context<MintToken>, _decimals:u8, name: String, symbol: String, uri: String, amount: u64) -> Result<()>
{
mint_token::mint_token(ctx, _decimals, name, symbol, uri, amount)
}
pub fn transfer_tokens(ctx: Context<TransferToken>, amount: u64) -> Result<()>
{
transfer_token::transfer_tokens(ctx, amount)
}
}
In the code above:
- We import all instruction and state modules.
- This file serves as a registry module for the Solana program.
- Within this file, we invoke each instruction handler separately, providing context and other arguments for each instruction call.
Create Mint NFT instruction
In s-sol-tokens, navigate to spltokens-sol-program/programs/src/instrcutions/mint_nft.rs.
use anchor_lang::prelude::*;
use anchor_spl::{ associated_token::AssociatedToken, metadata::
{create_master_edition_v3, create_metadata_accounts_v3, CreateMasterEditionV3, CreateMetadataAccountsV3, Metadata}, token::{mint_to, Mint, MintTo, Token, TokenAccount }};
use mpl_token_metadata::{ pda::{ find_master_edition_account, find_metadata_account}, state::DataV2 };
pub fn mint_nft(ctx: Context<MintNFT>, name: String, symbol: String, uri: String) -> Result<()>
{
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.associated_token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
},
);
mint_to(cpi_context, 1)?;
let cpi_context = CpiContext::new(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMetadataAccountsV3 {
metadata: ctx.accounts.metadata_account.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
mint_authority: ctx.accounts.signer.to_account_info(),
update_authority: ctx.accounts.signer.to_account_info(),
payer: ctx.accounts.signer.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
);
let data_v2 = DataV2 {
name,
symbol,
uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
};
create_metadata_accounts_v3(cpi_context, data_v2, false, true, None)?;
let cpi_context = CpiContext::new(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMasterEditionV3 {
edition: ctx.accounts.master_edition_account.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
update_authority: ctx.accounts.signer.to_account_info(),
mint_authority: ctx.accounts.signer.to_account_info(),
payer: ctx.accounts.signer.to_account_info(),
metadata: ctx.accounts.metadata_account.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
);
create_master_edition_v3(cpi_context, None)?;
Ok(())
}
#[derive(Accounts)]
pub struct MintNFT<'info>
{
/// CHECK: signer check
#[account(mut, signer)]
signer: AccountInfo<'info>,
#[account(
init,
payer = signer,
mint::decimals = 0,
mint::authority = signer.key(),
mint::freeze_authority = signer.key()
)]
mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = signer,
associated_token::mint = mint,
associated_token::authority = signer
)]
pub associated_token_account: Account<'info, TokenAccount>,
/// CHECK:
#[account(mut, address = find_metadata_account(&mint.key()).0)]
pub metadata_account: AccountInfo<'info>,
/// CHECK:
#[account(mut, address = find_master_edition_account(&mint.key()).0)]
pub master_edition_account: AccountInfo<'info>,
pub token_program: Program<'info, Token>,
pub rent: Sysvar<'info, Rent>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_metadata_program: Program<'info, Metadata>,
pub system_program: Program<'info, System>,
}
let’s break down to see what is happening here
In the above code,
Imports-
We imports anchor_lang, anchor_spland mpl_token_metadata.
- anchor_lang imports all things from the anchor framework.
- anchor_spl imports all related things from spl tokens.
- AssociatedToken type used for creating an associated token account
- metadata module provides functionalities to handle operations related to token metadata like create_master_edition_v3, create_metadata_accounts_v3, CreateMasterEditionV3, CreateMetadataAccountsV3
- token module provides functionalities to handle operations related to tokens like mint_to, Mint, MintTo, Token, and TokenAccount.
- mpl_token_metadata provides functionalities to handle operations related to Metaplex like find_master_edition_account, and find_metadata_account.
- In last, we import state::DataV2 for creating metadata arguments.
Instruction handler logic-
In The mint_nft function
- First, it takes the account context as its first and name, symbol and URI as other arguments.
- It creates a new CPI context for the CPI to
SPL token program
to mint the token into the signer’s associated token account. that’s how you can do cpi to spl token library. - It calls SPL's token mint_to() function by providing cpi context and the amount of token to mint into the signer’s ATA. this fn call mints one NFT into the signer’s associated token account.
- Next, it creates a new CPI context for the CPI to
token_metadata_program
and sets data into theCreateMetadataAccountsV3
struct.
CreateMetadataAccountsV3 {
metadata: ctx.accounts.metadata_account.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
mint_authority: ctx.accounts.signer.to_account_info(),
update_authority: ctx.accounts.signer.to_account_info(),
payer: ctx.accounts.signer.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
- By utilizing Datav2 struct it sets token metadata information
let data_v2 = DataV2 {
name,
symbol,
uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
};
- It calls the create_metadata_accounts_v3() function by providing the cpi context and the metadata information. this fn call updates the metadata of the minted token.
- Next, it creates a new CPI context for the CPI to token_metadata_program and sets data into the CreateMasterEditionV3 struct.
CreateMasterEditionV3 {
edition: ctx.accounts.master_edition_account.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
update_authority: ctx.accounts.signer.to_account_info(),
mint_authority: ctx.accounts.signer.to_account_info(),
payer: ctx.accounts.signer.to_account_info(),
metadata: ctx.accounts.metadata_account.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
- It calls the create_master_edition_v3() function by providing the cpi context and max supply in our case None. This fn call creates a master edition account of the NFT so that you can use it to print multiple editions (e.g., a limited supply of 10) of your NFT later.
- finally, the instruction handler returns OK(()) for successful operation and through an error if any process fails.
Account creation and validation-
In Anchor Various types of constraints can be applied using the #[account(..)] attribute.
In the signer Account
- #[account(mut, signer)] In this attribute macro mut keyword defines this account as mutable and the signer keyword defines this account as a signer.
In the Mint account, The init constraint creates the account through a Cross-Program Invocation (CPI) to the system program and initializes it by setting its account discriminator. Additionally, it applies specific constraints:
- payer = signer the account responsible for covering the account creation costs.
- mint::decimals = 0 specifies a decimal value of 0, which is standard for NFTs.
- mint::authority = signer.key() and mint::freeze_authority = signer.key() assigns the signer as both the mint authority and freeze authority, for control over minting and freezing the mint account.
In the associated_token_account, The init_if_needed has the same functionality as the init. However, it only runs if the account does not exist yet. Additionally, it applies specific constraints:
- associated_token::mint = mint sets mint for this associated token account.
- associated_token::authority = signer sets authority as a signer for this associated token account.
Other ecosystem accounts for handling this instruction logic:
metadata_account: is responsible for setting up metadata for the token.
master_edition_account: is responsible for setting up an edition for the NFT.
token_program: is a type of Token
rent: is a type of Rent
associated_token_program: is a type of AssociatedToken
token_metadata_program: is a type of Metadata
system_program: is a type of System
Now, At this stage, we created three cpi calls for:
- Mint NFT into signer’s Ata
- Set up NFT metadata
- Set up NFT master edition.
Create Fungible Mint instruction
In s-sol-tokens, navigate to spltokens-sol-program/programs/src/instrcutions/mint_token.rs.
use anchor_lang::prelude::*;
use anchor_spl::{ associated_token::AssociatedToken, metadata::{ create_metadata_accounts_v3, CreateMetadataAccountsV3 }, token::{mint_to, Mint, MintTo, Token, TokenAccount }};
use mpl_token_metadata::{ pda::find_metadata_account, state::DataV2 };
pub fn mint_token(ctx: Context<MintToken>, _decimals: u8, name: String, symbol: String, uri: String, amount: u64) -> Result<()>
{
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.associated_token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
},
);
mint_to(cpi_context, amount)?;
let cpi_context = CpiContext::new(
ctx.accounts.token_metadata_program.to_account_info(),
CreateMetadataAccountsV3 {
metadata: ctx.accounts.metadata_account.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
mint_authority: ctx.accounts.signer.to_account_info(),
update_authority: ctx.accounts.signer.to_account_info(),
payer: ctx.accounts.signer.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
);
let data_v2 = DataV2 {
name,
symbol,
uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
};
create_metadata_accounts_v3(cpi_context, data_v2, false, true, None)?;
Ok(())
}
#[derive(Accounts)]
#[instruction(decimals: u8)]
pub struct MintToken<'info>
{
/// CHECK: signer check
#[account(mut, signer)]
signer: AccountInfo<'info>,
#[account(
init,
payer = signer,
mint::decimals = decimals,
mint::authority = signer.key(),
mint::freeze_authority = signer.key(),
)]
mint: Account<'info, Mint>,
/// CHECK:
#[account(mut, address = find_metadata_account(&mint.key()).0)]
pub metadata_account: AccountInfo<'info>,
#[account(
init_if_needed,
payer = signer,
associated_token::mint = mint,
associated_token::authority = signer,
)]
pub associated_token_account: Account<'info, TokenAccount>,
/// CHECK: account constraint checked in account trait
#[account(address = mpl_token_metadata::id())]
pub token_metadata_program: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub rent: Sysvar<'info, Rent>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>
}
let’s break down to see what is happening here
In the above code,
Imports-
we imports anchor_lang, anchor_spland mpl_token_metadata.
- anchor_lang imports all things from the anchor framework.
- anchor_spl imports all related things from spl tokens.
- AssociatedToken type used for creating an associated token account
- metadata module provides functionalities to handle operations related to token metadata like create_metadata_accounts_v3 and CreateMetadataAccountsV3.
- token module provides functionalities to handle operations related to tokens like mint_to, Mint, MintTo, Token, and TokenAccount.
- mpl_token_metadata provides functionalities to handle operations related to Metaplex like find_metadata_account.
- In last, we import state::DataV2 for creating metadata arguments.
Instruction handler logic-
The mint_token function
- First, it takes the account context as its first and _decimals, name, symbol and URI as other arguments.
- It creates a new CPI context for the CPI to SPL token program to mint the token into the signer’s associated token account.
- It calls SPL's token mint_to() function by providing cpi context and the amount of token to mint into the signer’s ATA. this fn call mints a specified amount of tokens into the signer’s associated token account.
- Next, it creates a new CPI context for the CPI to token_metadata_program and sets data into the CreateMetadataAccountsV3 struct.
CreateMetadataAccountsV3 {
metadata: ctx.accounts.metadata_account.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
mint_authority: ctx.accounts.signer.to_account_info(),
update_authority: ctx.accounts.signer.to_account_info(),
payer: ctx.accounts.signer.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
rent: ctx.accounts.rent.to_account_info(),
},
- By utilizing the Datav2 struct it sets token metadata information
let data_v2 = DataV2 {
name,
symbol,
uri,
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
};
- It calls the create_metadata_accounts_v3() function by providing the cpi context and the metadata information. this fn call updates the metadata of the minted tokens.
- finally, the instruction handler returns OK(()) for successful operation and through an error if any process fails.
Account creation and validation-
-
In MintToken struct, we can access the instruction’s arguments with the #[instruction(..)] attribute.
In the signer Account
-
#[account(mut, signer)] In this attribute macro mut keyword defines this account as mutable and the signer keyword defines this account as a signer.
In the Mint account, The init
constraint creates the account through a Cross-Program Invocation(CPI) to the system program and initializes it by setting its account discriminator. Additionally, it applies specific constraints:
- payer = signer the account responsible for covering the account creation costs.
- mint::decimals = decimal specifies a decimal value for tokens.
- mint::authority = signer.key() and mint::freeze_authority = signer.key() assigns the signer as both the mint authority and freeze authority, for control over minting and freezing the mint account.
In the associated_token_account, The init_if_needed has the same functionality as the init. However, it only runs if the account does not exist yet. Additionally, it applies specific constraints:
- associated_token::mint = mint sets mint for this associated token account.
- associated_token::authority = signer sets authority as a signer for this associated token account.
Other ecosystem accounts for handling this instruction logic:
token_metadata_program: is responsible for setting up metadata for the token.
token_program: is a type of Token
rent: is a type of Rent
associated_token_program: is a type of AssociatedToken
system_program: is a type of System
Now, At this stage, we created two cpi calls for:
- Mint tokens into signer’s Ata
- Set up token metadata.
Create SPL Token Transfer instruction
use anchor_lang::prelude::*;
use anchor_spl::{ token::{self, Transfer}, token::{ Token, TokenAccount }};
pub fn transfer_tokens(ctx: Context<TransferToken>, amount: u64) -> Result<()>
{
let cpi_accounts = Transfer
{
from: ctx.accounts.from_ata.to_account_info(),
to: ctx.accounts.to_ata.to_account_info(),
authority: ctx.accounts.from.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
token::transfer(CpiContext::new(cpi_program, cpi_accounts), amount)?;
Ok(())
}
#[derive(Accounts)]
pub struct TransferToken<'info>
{
pub from: Signer<'info>,
#[account(mut)]
pub from_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub to_ata: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>
}
Imports-
we imports anchor_lang, anchor_spl.
Instruction handler logic-
In The transfer_tokens function
- First, it takes the account context as its first and token amount as its second argument.
- Then it creates a new Cpi context to do Cpi on the SPL token program. for this first, it sets cpi accounts and fetches the token program from the account context in cpi_program.
- finally, it calls token::transfer() fn to transfer spl tokens to another user’s ATA to_ata.
- Lastly, fn returns ok(()) for completion otherwise it through an error if any process fails.
Account creation and validation-
- from is the signer account itself.
- from_ata account is the signer’s associated token account.
- to_ata account can be another user’s associated token account.
- token_program account is the type of Token Program to create a cpi.
At this stage, we created one cpi call to transfer spl tokens into the user’s associated token accounts.
Build and deploy Anchor program
In s-sol-tokens, navigate to spltokens-sol-program and run
anchor build
This will build your anchor program and generate a target folder(we will use generated IDL and types for the Solana program to connect our UI to this program).
To deploy this program run
anchor deploy
After this, the program will deployed to an address. Pick this program address and update this address to the following files.
- spltokens-sol-program/Anchor.toml
- spltokens-sol-program/programs/src/lib.rs
At this stage, we are done with the SPL token Mint and Transfer Solana program development.
Connecting Solana program to UI
Solana-app-scaffold already sets up a UI with prebuilt Hooks and a wallet connector for you. All you need to do is simply modify it to fit your newly created program.
Since our SPl token mint and transfer has four instructions, we will need components in the UI that will be able to call each of these instructions:
- Create NFT Mint
- Transfer NFT to another user
- Create a Fungible Token Mint
- Transfer Fungible Token to another user
In your project folder open s-sol-tokens/solana-client and add the generated IDL file at the root level from s-sol-tokens/spltokens-sol-program/target/idl/spltokens.json
let’s head over to call your instructions from the client side.
Create NFT Mint
In your project folder open s-sol-tokens/solana-client/src/components/MintNFTButton.tsx
Here is how you get your programID from IDL.
const programId = new PublicKey(idl.metadata.address);
you can generate a program API to call each instruction in the program
const getProgram = () => {
/* create the provider and return it to the caller */
const provider = new AnchorProvider(connection, wallet as any, opts);
/* create the program interface combining the idl, program ID, and provider */
const program = new Program(idl as Idl, programId, provider);
return program;
};
const program = getProgram();
Set up the umi client and fetch metadata and master edition PDA account using the mint account.
const umi = createUmi("https://api.devnet.solana.com").use(walletAdapterIdentity(wallet)).use(mplTokenMetadata());
//const mint = anchor.web3.Keypair.generate();
const associatedTokenAccount = wallet.publicKey !== null && getAssociatedTokenAddressSync(
mintKeypair.publicKey,
wallet.publicKey
);
let metadataAccount = findMetadataPda(umi, {
mint: publicKey(mintKeypair.publicKey),
})[0];
let masterEditionAccount = findMasterEditionPda(umi, {
mint: publicKey(mintKeypair.publicKey),
})[0];
const MetaData = {
name: "oggggg",
symbol: "Oggy",
uri: "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json",
};
Call mintNft fn to mint NFT into Minter’s wallet.
const mintNft = async (e) => {
setLoading(true);
try {
e.preventDefault();
const metaplex = Metaplex.make(connection);
const tx = await program.methods
.mintNft(MetaData.name, MetaData.symbol, MetaData.uri)
.accounts({
signer: wallet.publicKey,
mint: mintKeypair.publicKey,
associatedTokenAccount,
metadataAccount,
masterEditionAccount,
tokenProgram: TOKEN_PROGRAM_ID,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
tokenMetadataProgram: MPL_TOKEN_METADATA_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
})
.signers([mintKeypair])
.rpc();
console.log(`mint nft tx: https://explorer.solana.com/tx/${tx}?cluster=devnet`);
console.log(`minted nft: https://explorer.solana.com/address/${mintKeypair.publicKey}?cluster=devnet`);
setTxSig(tx);
let metadata = await metaplex
.nfts()
.findByMint({ mintAddress: mintKeypair.publicKey, tokenOwner: wallet.publicKey });
setNftDetails(metadata);
notify({ message: "NFT Minted" });
} catch (err) {
console.log(err);
notify({ message: err.message });
}
setLoading(false);
};
Transfer NFT to another user
In your project folder open s-sol-tokens/solana-client/src/components/nft/NFTCard.tsx and call the transferNFT fn to transfer NFT to buyer’s associated token account.
const transferNFT = async () => {
setLoading(true);
try {
const transaction = new Transaction();
const sellerTokenAccount = await getAssociatedTokenAddress(
mint.publicKey,
publicKey,
false,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
);
const buyerPublicKey = new anchor.web3.PublicKey(buyer);
// Derive wallet's associated token account address for mint
const buyerTokenAccount = await getAssociatedTokenAddress(
mint.publicKey,
buyerPublicKey,
false,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
);
try {
await getAccount(connection, buyerTokenAccount);
} catch (e) {
transaction.add(
createAssociatedTokenAccountInstruction(
publicKey,
buyerTokenAccount,
buyerPublicKey,
mint.publicKey,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
)
);
transaction.feePayer = wallet.publicKey;
const tx1 = await sendTransaction(transaction, connection);
console.log("tx1", tx1)
}
const tx = await program.methods
.transferTokens(new BN(1))
.accounts({
from: publicKey,
fromAta: sellerTokenAccount,
toAta: buyerTokenAccount,
tokenProgram: TOKEN_PROGRAM_ID
})
.rpc({ skipPreflight: true });
setTxSig(tx);
notify({ message: "NFT Transferred" });
} catch (err) {
console.log(err);
notify({ message: err.message });
}
setLoading(false);
};
Create Fungible Token Mint
In your project folder open s-sol-tokens/solana-client/src/components/fungible/FungibleTokenMint.tsx and call the createMint fn to create a fungible token mint and mint tokens into minter’s wallet.
const createMint = async (event) => {
event.preventDefault();
setLoading(true);
if (!publicKey) {
notify({ type: "error", message: `Wallet not connected!` });
console.log("error", `Send Transaction: Wallet not connected!`);
return;
}
// creating metadata address
const metaplex = Metaplex.make(connection);
const metadataAddress = await metaplex.nfts().pdas().metadata({ mint: mintKeypair.publicKey });
const associatedTokenAccount = wallet.publicKey !== null && getAssociatedTokenAddressSync(
mintKeypair.publicKey,
wallet.publicKey
);
// create mint transaction
try {
const createMintTransaction = await program.methods
.mintToken(
3, // 0 decimals for NFT
tokenTitle, // NFT name
tokenSymbol, // NFT symbol
tokenUri, // NFT URI
new anchor.BN(9999999999999)
)
.accounts({
signer: wallet.publicKey,
mint: mintKeypair.publicKey,
associatedTokenAccount,
metadataAccount: metadataAddress,
tokenMetadataProgram: MPL_TOKEN_METADATA_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([mintKeypair])
.rpc();
console.log("Your transaction signature", createMintTransaction);
let mintAccount = await getMint(connection, mintKeypair.publicKey);
console.info("mintAccount", mintAccount.address.toString());
setMint(mintAccount.address.toString());
setTxSig(createMintTransaction);
} catch (error) {
notify({ type: "error", message: `Transaction failed!`, description: error?.message });
console.log("error", `Transaction failed! ${error?.message}`);
return;
}
setLoading(false);
};
Transfer Fungible Token to another user
In your project folder open s-sol-tokens/solana-client/src/components/fungible/TransfertoOtherWallet.tsx and call the transferfungible fn to transfer fungible tokens to the receiver’s Associated token account.
const transferfungible = async (event) => {
event.preventDefault();
setLoading(true);
if (!connection || !publicKey) {
return;
}
const transaction = new web3.Transaction();
const mintPubKey = new web3.PublicKey(event.target.mint.value);
const recipientPubKey = new web3.PublicKey(event.target.recipient.value);
let amount = event.target.amount.value;
amount = amount * 10 ** 9;
const senderAta = await getAssociatedTokenAddress(
mintPubKey,
publicKey,
false,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
);
const receiverAta = await getAssociatedTokenAddress(
mintPubKey,
recipientPubKey,
false,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
);
try {
await getAccount(connection, receiverAta);
} catch (e) {
transaction.add(
createAssociatedTokenAccountInstruction(
publicKey,
receiverAta,
recipientPubKey,
mintPubKey,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
)
);
}
amount = new anchor.BN(amount);
const transferTx = await program.methods
.transferTokens(amount)
.accounts({
from: publicKey,
fromAta: senderAta,
toAta: receiverAta,
tokenProgram: TOKEN_PROGRAM_ID
})
.instruction();
transaction.add(transferTx);
const signature = await sendTransaction(transaction, connection);
await connection.confirmTransaction(signature, "confirmed");
setTxSig(signature);
setTokenAccount(receiverAta.toString());
const account = await getAccount(connection, receiverAta);
setBalance(account.amount.toString());
setLoading(false);
};
Resources-:
github : spldapp_github
vercel : spldapp_vercel