पिछले ट्यूटोरियल में, हमने सीखा कि SPL tokens कैसे काम करते हैं। इस ट्यूटोरियल में, हम एक पूर्ण SPL token जीवनचक्र (lifecycle) को लागू करेंगे: दो दृष्टिकोणों (approaches) का उपयोग करके टोकन को create, mint, transfer और query करना:
- Anchor के साथ On-chain: हम Anchor के साथ एक Solana प्रोग्राम बनाएंगे जो एक पूर्व-निर्धारित (predefined) सप्लाई सीमा तक पहुँचने तक SPL tokens को mint करेगा।
- TypeScript के साथ Client-side: हम यह भी दिखाएंगे कि SPL mints, ATAs बनाने, टोकन mint करने, ट्रांसफर करने और बैलेंस पढ़ने के लिए TypeScript क्लाइंट से सीधे Token Program के साथ कैसे इंटरैक्ट किया जाए।
दो दृष्टिकोण (approaches) क्यों?
इन दोनों को करना जानना महत्वपूर्ण है क्योंकि:
- Anchor के साथ, हम एक SPL token के ऊपर कस्टम on-chain लॉजिक बना सकते हैं (जैसे, vesting schedules, conditional minting) या एक ऐसा SPL token बना सकते हैं जो किसी वॉलेट के बजाय हमारे प्रोग्राम द्वारा नियंत्रित होता है।
- TypeScript के साथ, हम SPL tokens ट्रांसफर करने या किसी delegate को ऑथराइज़/रिवोक (authorize/revoke) करने जैसी सरल गतिविधियों के लिए सीधे SPL प्रोग्राम के साथ इंटरैक्ट कर सकते हैं।
अब Anchor दृष्टिकोण के साथ शुरू करते हैं।
Anchor में SPL Tokens बनाना
पिछले SPL token ट्यूटोरियल से याद करें कि हर टोकन एक ही on-chain प्रोग्राम (एड्रेस TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA पर मौजूद SPL Token Program) का उपयोग करके mint accounts बनाता है और टोकन minting, transfers, approvals आदि करता है।
इस अनुभाग (section) में, हम एक Anchor प्रोग्राम बनाएंगे जो Token Program में Cross-Program Invocations (CPI) के माध्यम से SPL tokens को बनाता है और mint करता है।
हमारे प्रोग्राम में केवल दो फंक्शन होंगे:
- एक
create_and_mint_tokenफंक्शन जो mint account बनाएगा और Token Program में CPI के माध्यम से एक निर्दिष्ट associated token account (ATA) में प्रारंभिक (initial) सप्लाई को mint करेगा। - एक
transfer_tokensफंक्शन जो Token Program में एक CPI के माध्यम से टोकन को सोर्स (source) ATA से डेस्टिनेशन (destination) ATA में ले जाएगा।
अब, anchor init spl_token के साथ एक नया Anchor प्रोजेक्ट बनाएं। प्रोजेक्ट खोलें और programs/spl_token/src/lib.rs के कोड को निम्नलिखित से बदलें:
इस कोड में, हम:
- हमारी निर्भरताओं (dependencies) को इंपोर्ट करते हैं:
- Associated Token Accounts (ATAs) बनाने के लिए
anchor_spl::associated_token::AssociatedToken। - SPL Token Program के साथ काम करने के लिए
anchor_spl::token::{Mint, MintTo, Token, TokenAccount, Transfer}(ये वे इंस्ट्रक्शन और अकाउंट प्रकार हैं जिनकी हमें minting और ट्रांसफर के लिए आवश्यकता है)।
- Associated Token Accounts (ATAs) बनाने के लिए
- एक
create_and_mint_tokenफंक्शन को परिभाषित करते हैं जो:- दिए गए mint account और डेस्टिनेशन ATA (जहां mint किए गए टोकन जमा किए जाएंगे) का उपयोग करता है।
- एक CPI context बनाता है जो Token Program की ओर इशारा (point) करता है।
- ATA में 100 टोकन (9 दशमलव के साथ) mint करने के लिए Token Program के
mint_toइंस्ट्रक्शन को कॉल करता है। - टोकन mint होने के बाद सफलता (success) रिटर्न करता है।
use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken; // Needed for ATA creation
use anchor_spl::token::{self, Mint, MintTo, Token, TokenAccount, Transfer}; // Needed for mint account creation/handling
declare_id!("6zndm8QQsPxbjTRC8yh5mxqfjmUchTaJyu2yKbP7ZT2x");
#[program]
pub mod spl_token {
use super::*;
// This function deploys a new SPL token with decimal of 9 and mints 100 units of the token
pub fn create_and_mint_token(ctx: Context<CreateMint>) -> Result<()> {
let mint_amount = 100_000_000_000; // 100 tokens with 9 decimals
let mint = ctx.accounts.new_mint.clone();
let destination_ata = &ctx.accounts.new_ata;
let authority = ctx.accounts.signer.clone();
let token_program = ctx.accounts.token_program.clone();
let mint_to_instruction = MintTo {
mint: mint.to_account_info(),
to: destination_ata.to_account_info(),
authority: authority.to_account_info(),
};
let cpi_ctx = CpiContext::new(token_program.to_account_info(), mint_to_instruction);
token::mint_to(cpi_ctx, mint_amount)?;
Ok(())
}
}
CreateMint अकाउंट struct जोड़ें। इसमें निम्नलिखित अकाउंट शामिल हैं:
signer: वह अकाउंट जो ट्रांजेक्शन फीस का भुगतान करता है और mint authority के रूप में भी कार्य करता हैnew_mint: एक mint PDA अकाउंट जो 9 दशमलव स्थानों (decimal places) के साथ इनिशियलाइज़ होता है और signer का उपयोग mint और freeze authority दोनों के रूप में करता हैnew_ata: एक associated token account जो नए mint के लिए बनाया जाएगा और signer का उपयोग अपनी authority के रूप में करेगा (प्रभावी रूप से, वह अकाउंट जो signer का बैलेंस रखता है)- अंत में, हम Token Program, Associated Token Program, और System Program पास करते हैं। ये वे नेटिव प्रोग्राम हैं जिनके साथ हम CPI के माध्यम से इंटरैक्ट करते हैं।
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
init,
payer = signer,
mint::decimals = 9,
mint::authority = signer,
// Commenting out or removing this line permanently disables the freeze authority.
mint::freeze_authority = signer,
// When a token is created without a freeze authority, Solana prevents any future updates to it.
// This makes the token more decentralized, as no authority can freeze a user's ATA.
seeds = [b"my_mint", signer.key().as_ref()],
bump
)]
pub new_mint: Account<'info, Mint>,
#[account(
init,
payer = signer,
associated_token::mint = new_mint,
associated_token::authority = signer,
)]
pub new_ata: Account<'info, TokenAccount>,
// This represents the SPL Token Program (TokenkegQfeZ…)
// The same program we introduced in the previous article that owns and manages all mint and associated token account.
pub token_program: Program<'info, Token>,
// This represents the ATA program (ATokenGPvbdGV...)
// As mentioned in the previous tutorial, it is only in charge of creating the ATA.
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}
अब अपने Program ID को सिंक्रोनाइज़ करने के लिए anchor keys sync चलाएं।
इसके बाद, हमारे प्रोजेक्ट में एक निर्भरता (dependency) के रूप में anchor-spl crate को जोड़ने के लिए programs/spl_token/Cargo.toml फ़ाइल को अपडेट करें।
[package]
name = "spl_token"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
name = "spl_token"
[features]
default = []
cpi = ["no-entrypoint"]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] # added "anchor-spl/idl-build"
[dependencies]
anchor-lang = "0.31.0"
anchor-spl = "0.31.0" # added this
यह anchor-spl हमें SPL Token Program, ATA प्रोग्राम और उनके इंस्ट्रक्शन तक पहुँच प्रदान करता है।
अब, आइए परीक्षण करें कि प्रोग्राम कोड में क्या हो रहा है।
हम CreateMint struct से शुरू करते हैं।

signer
सबसे पहले, हम उस signer की घोषणा (declare) करते हैं जो टोकन डिप्लॉयमेंट ट्रांजेक्शन के लिए भुगतान करता है, जैसा कि नीचे बैंगनी (purple) रंग में हाइलाइट किया गया है।

mint
इसके बाद, हम एक new_mint अकाउंट घोषित करते हैं, जो उस SPL token का प्रतिनिधित्व करता है जिसे हम बनाना चाहते हैं (नीचे red में हाइलाइट किया गया है)। इसका अकाउंट प्रकार Mint है (नीचे पीले रंग में हाइलाइट किया गया है)। यह अकाउंट प्रकार Solana पर एक mint account का प्रतिनिधित्व करता है।

जैसा कि आप ऊपर दी गई छवि (image) में देख सकते हैं, हम इस नए mint account को एक Program Derived Address (PDA) के रूप में इनिशियलाइज़ करते हैं और इसके पैरामीटर सेट करते हैं: टोकन दशमलव (decimals), mint और freeze authorities, और PDA seeds। एक keypair अकाउंट का उपयोग करने के बजाय, हम mint को फिक्स्ड seeds और प्रोग्राम ID से PDA के रूप में डिराइव करते हैं, इसलिए keypair अकाउंट की तरह प्राइवेट की (private key) जेनरेट या मैनेज करने की कोई आवश्यकता नहीं है। हम मुख्य रूप से सुविधा के लिए mint PDA का उपयोग कर रहे हैं।
यदि आप इस बात से अपरिचित हैं कि PDA कैसे काम करते हैं या वे keypair अकाउंट से कैसे भिन्न हैं, तो हमारा लेख “PDA (Program Derived Address) vs Keypair Account in Solana.” पढ़ें।
अंत में, init constraint Anchor को बताता है कि जब create_and_mint_token चलता है तो वह स्वचालित (automatically) रूप से mint account को बनाए और इनिशियलाइज़ करे (हम इस फंक्शन को आगे समझाएंगे)।
इस init constraint के कारण, Anchor पर्दे के पीछे (behind the scenes) Token Program के InitializeMint इंस्ट्रक्शन के लिए एक CPI (cross-program invocation) करेगा। यह इंस्ट्रक्शन mint के दशमलव को 9 पर सेट करता है और mint और freeze authority दोनों को signer को असाइन करता है।
Associated Token Account
अगला है associated token account (ATA) जिसमें हम इस टोकन को mint करेंगे (नीचे पीले रंग में हाइलाइट किया गया है)।
नोट: किसी mint account को अस्तित्व में रहने के लिए ATA की आवश्यकता नहीं होती है। हम केवल यहां एक बना रहे हैं क्योंकि हम signer के लिए कुछ टोकन mint करना चाहते हैं।

ATA का प्रकार TokenAccount है, जो Solana पर एक ATA का प्रतिनिधित्व करता है। mint account की तरह, हम इसके पैरामीटर सेट करते हैं: ATA का mint उस नए टोकन पर सेट किया जाता है जिसे हम बना रहे हैं, और signer इसका authority बन जाता है। इसका मतलब है कि केवल signer ही उन इंस्ट्रक्शन को अधिकृत (authorize) कर सकता है जो ATA की स्थिति (state) को बदलते हैं। इन सेटिंग्स को लागू करने के लिए Anchor आंतरिक (internally) रूप से Token Program के InitializeAccount इंस्ट्रक्शन के लिए एक CPI करता है।
नोट: हम सुरक्षित रूप से यहाँ init का उपयोग केवल इसलिए कर सकते हैं क्योंकि mint account (new_mint) भी उसी इंस्ट्रक्शन में बनाया जा रहा है। यदि mint पहले से मौजूद था, तो ATA पर init का उपयोग विफल हो सकता है यदि किसी ने पहले से ही वह ATA बना लिया हो, जिससे denial-of-service की स्थिति पैदा हो सकती है। उन मामलों में जहाँ mint पहले से मौजूद हो सकता है, इसके बजाय init_if_needed का उपयोग करना अधिक सुरक्षित है। अन्यथा, कोई इंस्ट्रक्शन को फ्रंटरन (frontrun) कर सकता है और signer की ओर से एक ATA बना सकता है, और इस ट्रांजेक्शन को विफल कर सकता है।
नेटिव प्रोग्राम्स अकाउंट्स (native programs accounts)
अंत में, हम mint और associated token account बनाने के लिए आवश्यक नेटिव Solana प्रोग्राम घोषित करते हैं (नीचे हरे रंग में हाइलाइट किया गया है)। ये वो on-chain प्रोग्राम हैं जिनके साथ हमारा Anchor प्रोग्राम इंटरैक्ट करता है: mint बनाने और टोकन mint करने के लिए Token Program, यूजर का ATA बनाने के लिए Associated Token Account Program, और अकाउंट्स के लिए स्पेस (space) आवंटित करने और रेंट (rent) प्रबंधित करने के लिए System Program।

आपने देखा होगा कि ATA (new_ata अकाउंट) में mint account (new_mint) की तरह seed और bump नहीं है, ऐसा इसलिए है क्योंकि InitializeAccount इंस्ट्रक्शन स्टैण्डर्ड associated token account डेरिवेशन प्रक्रिया (derivation process) का उपयोग करता है, यानी, user_wallet_address + token_mint_address => associated_token_account_address। इसलिए हमें seed और bump पास करने की आवश्यकता नहीं है। यदि आप seed और bump पास करने का प्रयास करते हैं, तो Anchor यह एरर (error) देता है।

हम mint account और ATA के लिए space भी निर्दिष्ट नहीं करते हैं क्योंकि Anchor पर्दे के पीछे हमारे लिए स्पेस भी जोड़ता है। यह इस जानकारी को जानता है क्योंकि हमने निर्दिष्ट किया था कि प्रोग्राम AssociatedToken है। यदि हम उनमें से किसी के लिए भी space निर्दिष्ट करने का प्रयास करते हैं तो एरर उत्पन्न होता है।

mint और associated token account का वास्तविक आकार क्रमशः 82 और 165 बाइट्स (bytes) है।
अब जब हमने आवश्यक सभी अकाउंट घोषित कर दिए हैं, तो आइए SPL tokens को mint करने के लिए create_and_mint_token फंक्शन का परीक्षण करें।
SPL token को Mint करना
हम इस फंक्शन का उपयोग signer के नए बनाए गए ATA में हमारे द्वारा अभी बनाए गए 100 टोकन (9 दशमलव के साथ) mint करने के लिए करते हैं।

हम ऊपर दिए गए कोड में एक MintTo इंस्ट्रक्शन का निर्माण करते हैं। ये तीन फील्ड्स MintTo के व्यवहार (behavior) को परिभाषित करते हैं:
mint: हम कौन सा टोकन mint कर रहे हैं, जैसा कि mint account द्वारा निर्दिष्ट किया गया हैto: वह ATA जो mint किए गए टोकन प्राप्त करेगा।authority: वह अकाउंट जिसे इस mint के लिए टोकन mint करने की अनुमति है। हमारे प्रोग्राम में, हम mint authority को ट्रांजेक्शन signer (signer) पर सेट करते हैं, इसलिए minting को सफल होने के लिए signer को साइन (sign) करना चाहिए और mint की authority से मेल खाना चाहिए।
फिर हम इस इंस्ट्रक्शन (जैसा कि हरे रंग में हाइलाइट किया गया है) के साथ Token Program के लिए एक CPI बनाते हैं, जो हमारे टोकन की 100 इकाइयों (units) को associated token account में mint करता है।
इसके अलावा, जैसा कि पिछले ट्यूटोरियल में चर्चा की गई थी, MintTo इंस्ट्रक्शन को कॉल करने से पहले mint account और ATA दोनों का मौजूद होना आवश्यक है (यह Transfer पर भी लागू होता है)। इसीलिए हम #[account(init…)] constraint का उपयोग करते हैं; यह सुनिश्चित करता है कि इंस्ट्रक्शन के चलने से ठीक पहले ये अकाउंट बनाए गए हों।

नोट: Solana पर NFT बनाने के लिए, आप mint::decimals = 0 के साथ mint को इनिशियलाइज़ करते हैं, प्राप्तकर्ता (recipient) को ठीक 1 टोकन mint करते हैं, फिर mint authority को None पर सेट करके उसे निरस्त (revoke) करते हैं। यह सुनिश्चित करता है कि कोई और टोकन कभी mint नहीं किया जा सकता है और टोकन को अद्वितीय (unique) और नॉन-फंजिबल (non-fungible) बनाता है क्योंकि यह शून्य के दशमलव (decimal) के कारण आंशिक (fractional) नहीं है।
createAndMintToken फंक्शन को टेस्ट करना
अब, हम createAndMintToken फंक्शन का परीक्षण करेंगे।
tests/spl_token.ts में टेस्ट कोड को निम्नलिखित कोड से बदलें। टेस्ट को इस प्रकार से संरचित (structured) किया गया है।
- हम अपने Anchor प्रोग्राम में उपयोग किए गए समान seeds के साथ,
@coral-xyz/anchorलाइब्रेरी सेfindProgramAddressSyncका उपयोग करके ऑफ-चेन (off-chain) टोकन के mint account एड्रेस को डिराइव करते हैं। यह चरण (step) mint account को डिप्लॉय नहीं करता है, हम इसे पहले ही Anchor प्रोग्राम के अंदर हैंडल कर चुके हैं, जैसा कि पहले बताया गया है। - इसके बाद हम
getAssociatedTokenAddressSyncफंक्शन का उपयोग करके signer के ATA एड्रेस की गणना (compute) करते हैं। फिर से, यह अकाउंट को डिप्लॉय नहीं करता है। - हम उपयुक्त (appropriate) अकाउंट्स (signer, mint, ATA, Token Program, ATA Program और System Program) के साथ Anchor प्रोग्राम फंक्शन को कॉल करते हैं और ट्रांजेक्शन हैश (hash), टोकन एड्रेस और signer के ATA एड्रेस को प्रिंट करते हैं।
- अंत में, हम
@solana/spl-tokenलाइब्रेरी सेgetMintऔरgetAccountफंक्शन का उपयोग करके mint और ATA की जानकारी प्राप्त करते हैं, और यह assert करते हैं कि उनकी सामग्री (contents) उस चीज़ से मेल खाती है जिसे हमने पहले अपने Anchor Program में सेट किया था। हम टोकन के दशमलव, authorities, टोकन सप्लाई, टोकन के ATA बैलेंस आदि को assert करते हैं।
import * as anchor from "@coral-xyz/anchor";
import { Program, web3 } from "@coral-xyz/anchor";
import * as splToken from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import { assert } from 'chai';
import { SplToken } from "../target/types/spl_token";
describe("spl_token", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.splToken as Program<SplToken>;
const provider = anchor.AnchorProvider.env();
const signerKp = provider.wallet.payer;
const toKp = new web3.Keypair();
it("Creates a new mint and associated token account using CPI", async () => {
// Derive the mint address using the same seeds ("my_mint" + signer public key) we used when the mint was created in our Anchor program
const [mint] = PublicKey.findProgramAddressSync(
[Buffer.from("my_mint"), signerKp.publicKey.toBuffer()],
program.programId
);
// Get the associated token account address
// The boolean value here indicates whether the authority of the ATA is an "off-curve" address (i.e., a PDA).
// A value of false means the owner is a normal wallet address.
// `signerKp` is the owner here and it is a normal wallet address, so we use false.
const ata = splToken.getAssociatedTokenAddressSync(mint, signerKp.publicKey, false)
// Call the create_mint instruction
const tx = await program.methods
.createAndMintToken()
.accounts({
signer: signerKp.publicKey,
newMint: mint,
newAta: ata,
tokenProgram: splToken.TOKEN_PROGRAM_ID,
associatedTokenProgram: splToken.ASSOCIATED_TOKEN_PROGRAM_ID,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
console.log("Transaction signature:", tx);
console.log("Token (Mint Account) Address:", mint.toString());
console.log("Associated Token Account:", ata.toString());
/// Verify the token details
const mintInfo = await splToken.getMint(provider.connection, mint);
assert.equal(mintInfo.decimals, 9, "Mint decimals should be 9");
assert.equal(mintInfo.mintAuthority?.toString(), signerKp.publicKey.toString(), "Mint authority should be the signer");
assert.equal(mintInfo.freezeAuthority?.toString(), signerKp.publicKey.toString(), "Freeze authority should be the signer");
assert.equal(mintInfo.supply.toString(), "100000000000", "Supply should be 100 tokens (with 9 decimals)");
// Verify the ATA details
const tokenAccount = await splToken.getAccount(provider.connection, ata);
assert.equal(tokenAccount.mint.toString(), mint.toString(), "Token account mint should match the mint PDA");
assert.equal(tokenAccount.owner.toString(), signerKp.publicKey.toString(), "Token account owner should be the signer");
assert.equal(tokenAccount.amount.toString(), "100000000000", "Token balance should be 100 tokens (with 9 decimals)");
assert.equal(tokenAccount.delegate, null, "Token account should not have a delegate");
});
});
SPL token लाइब्रेरी को इंस्टॉल करने के लिए npm install @solana/spl-token चलाएं।
अब anchor test चलाएं और देखें कि टोकन और ATA दोनों सफलतापूर्वक डिप्लॉय हो गए हैं।

SPL Tokens को ट्रांसफर करना
टोकन ट्रांसफर करने के लिए, हम एक Transfer इंस्ट्रक्शन का निर्माण करते हैं और Token Program के लिए एक CPI बनाते हैं। यह ट्रांसफर निर्दिष्ट टोकन इकाई (unit) की मात्रा को सोर्स (source) associated token account से डेस्टिनेशन (destination) associated token account में ले जाकर काम करता है। इस ट्रांजेक्शन का signer सोर्स ATA का authority होना चाहिए।
अब अपने प्रोग्राम में निम्नलिखित फंक्शन जोड़ें। यह निम्न कार्य करता है:
- सोर्स associated token account (
from_ata) को लोड करें जहाँ से टोकन लिए जाएँगे। - डेस्टिनेशन associated token account (
to_ata) को लोड करें जहाँ टोकन भेजे जाएँगे (यह ATA हमारे टेस्ट कोड में बनाया जाएगा)। - उस authority अकाउंट (
from) को लोड करें जिसे ट्रांसफर पर साइन (sign) और अप्रूव (approve) करना चाहिए। - Token Program अकाउंट लोड करें जो ट्रांसफर को प्रोसेस करेगा।
- सोर्स, डेस्टिनेशन और authority अकाउंट के साथ
Transferइंस्ट्रक्शन बनाएँ। - एक CPI (Cross-Program Invocation) context बनाएँ जो Token Program और ट्रांसफर इंस्ट्रक्शन को रैप (wrap) करता है।
- CPI context और अमाउंट के साथ
token::transferफंक्शन को कॉल करें, जो टोकन को सोर्स ATA से डेस्टिनेशन ATA में ले जाता है।
pub fn transfer_tokens(ctx: Context<TransferSpl>, amount: u64) -> Result<()> {
let source_ata = &ctx.accounts.from_ata;
let destination_ata = &ctx.accounts.to_ata;
let authority = &ctx.accounts.from;
let token_program = &ctx.accounts.token_program;
// Transfer tokens from from_ata to to_ata
let cpi_accounts = Transfer { // Transfer instruction
from: source_ata.to_account_info().clone(),
to: destination_ata.to_account_info().clone(),
authority: authority.to_account_info().clone(),
};
let cpi_ctx = CpiContext::new(token_program.to_account_info(), cpi_accounts); // Create a CPI context
token::transfer(cpi_ctx, amount)?;
Ok(())
}
ERC-20 में, transfer msg.sender को टोकन का मालिक (owner) मानता है, और transferFrom किसी तीसरे पक्ष (एक delegate) को स्वीकृत (approved) होने पर किसी और की ओर से टोकन ले जाने की अनुमति देता है। SPL Token Program दोनों को एक transfer इंस्ट्रक्शन में जोड़ता है, लेकिन इसके लिए ट्रांसफर authority को स्पष्ट रूप से (explicitly) एक अकाउंट के रूप में (हमारे Anchor कोड में AccountInfo) पास करने की आवश्यकता होती है — यह Transfer.authority फील्ड में मैप होता है। यह authority वह signer है जिसे टोकन ले जाने की अनुमति है; यह टोकन का मालिक या कोई स्वीकृत delegate हो सकता है।
इसलिए, transfer इंस्ट्रक्शन में:
from: टोकन भेजने वाले का ATA हैto: टोकन प्राप्तकर्ता (recipient) का ATA हैauthority: वह signer है जिसके पासfromसे टोकन ले जाने की अनुमति है (मालिक या अप्रूवल वाला delegate हो सकता है)
अब नीचे TransferSpl अकाउंट struct जोड़ें, यह टोकन ट्रांसफर करने के लिए आवश्यक अकाउंट्स को परिभाषित करता है।
#[derive(Accounts)]
pub struct TransferSpl<'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>, // We are interacting with the Token Program
}
हम ट्रांजेक्शन signer, सोर्स और डेस्टिनेशन ATAs, और अंत में उस Token Program को पास करते हैं जिसके साथ हम इंटरैक्ट करेंगे।
इस टेस्ट को हमारी टेस्ट फ़ाइल में जोड़ें।
हम टेस्ट में निम्नलिखित कार्य करते हैं।
- सबसे पहले, हम उस टोकन (mint account) का एड्रेस डिराइव करते हैं जिसे हम
findProgramAddressSyncफंक्शन के साथ ट्रांसफर करना चाहते हैं। - इसके बाद हम
@solana/spl-tokenसेgetAssociatedTokenAddressSyncके साथ सोर्स (भेजने वाले का वॉलेट) और डेस्टिनेशन (प्राप्तकर्ता का वॉलेट) ATAs दोनों के ATA एड्रेस की गणना करते हैं, जो mint एड्रेस, संबंधित अकाउंट एड्रेस, और एक बुलियन (boolean) वैल्यू लेता है जो इंगित करता है कि ATA signer (signerKp) PDA है या नहीं। जो इस मामले में नहीं है। - हम
createAssociatedTokenAccountफंक्शन के साथ डेस्टिनेशन अकाउंट के लिए ATA बनाते हैं। हम signer के लिए ATA नहीं बनाते हैं क्योंकि यह पिछले टेस्ट केस में पहले ही किया जा चुका था। चूँकि सभी टेस्ट केस एक साथ चलते हैं, इसलिए अकाउंट बना रहता है (persists)। - अंत में, हम अपने प्रोग्राम में
transfer_tokensफंक्शन का उपयोग करके डेस्टिनेशन ATA में 10 टोकन ट्रांसफर करते हैं। फिर, हमgetTokenAccountBalanceफंक्शन के साथ डेस्टिनेशन ATA का टोकन बैलेंस प्राप्त करते हैं और यह assert करते हैं कि यह 10 (वह अमाउंट जो हमने भेजा था) है।
it("Transfers tokens using CPI", async () => {
// Derive the PDA for the mint
const [mint] = PublicKey.findProgramAddressSync(
[Buffer.from("my_mint"), signerKp.publicKey.toBuffer()],
program.programId
);
// Get the ATAs
const fromAta = splToken.getAssociatedTokenAddressSync(mint, signerKp.publicKey, false);
const toAta = splToken.getAssociatedTokenAddressSync(mint, toKp.publicKey, false);
// Create to_ata as it doesn't exist yet
try {
await splToken.createAssociatedTokenAccount(
provider.connection,
signerKp,
mint,
toKp.publicKey
);
} catch (error) {
throw new Error(error)
}
const transferAmount = new anchor.BN(10_000_000_000); // 10 tokens with 9 decimals
// Transfer tokens
const tx = await program.methods
.transferTokens(transferAmount)
.accounts({
from: signerKp.publicKey,
fromAta: fromAta,
toAta: toAta,
tokenProgram: splToken.TOKEN_PROGRAM_ID,
})
.rpc();
console.log("Transfer Transaction signature:", tx);
// Verify the transfer
const toBalance = await provider.connection.getTokenAccountBalance(toAta);
assert.equal(
toBalance.value.amount,
transferAmount.toString(),
"Recipient balance should match transfer amount"
);
});
अब टेस्ट चलाएं

टोकन बैलेंस प्राप्त करना (Retrieving token balances)
ATA टोकन बैलेंस प्राप्त करने के लिए इस फंक्शन को प्रोग्राम में जोड़ें
pub fn get_balance(ctx: Context<GetBalance>) -> Result<()> {
// Get the token account address, its owner & balance
let ata_pubkey = ctx.accounts.token_account.key();
let owner = ctx.accounts.token_account.owner; // the `owner` is a field in the ATA
let balance = ctx.accounts.token_account.amount; // the `amount` is a field in the ATA
// Print the balance information
msg!("Token Account Address: {}", ata_pubkey);
msg!("Token Account Owner: {}", owner);
msg!("Token Account Balance: {}", balance);
Ok(())
}
ATA में amount फील्ड टोकन बैलेंस रखता है। इस फंक्शन में, हम बैलेंस प्रिंट करने के लिए इसे सीधे ctx.accounts.token_account से एक्सेस करते हैं।
संबंधित (corresponding) context struct जोड़ें:
#[derive(Accounts)]
pub struct GetBalance<'info> {
#[account(mut)]
pub token_account: Account<'info, TokenAccount>,
}
टेस्ट को अपडेट करें
it("Reads token balance using CPI", async () => {
// Derive the PDA for the mint
const [mint] = PublicKey.findProgramAddressSync(
[Buffer.from("my_mint"), signerKp.publicKey.toBuffer()],
program.programId
);
// Get the associated token account address
const ata = splToken.getAssociatedTokenAddressSync(mint, signerKp.publicKey, false);
// Call the get_balance instruction
const tx = await program.methods
.getBalance()
.accounts({
tokenAccount: ata,
})
.rpc();
console.log("Get Balance Transaction signature:", tx);
// Verify balance through direct query
const balance = await provider.connection.getTokenAccountBalance(ata);
assert.isTrue(balance.value.uiAmount > 0, "Token balance should be greater than 0");
});
यदि हम वैलिडेटर (validator) चलाते हैं और लॉग्स (logs) चेक करते हैं, तो हमें देखना चाहिए कि signer का ATA बैलेंस 10 टोकन (100 से 90 तक) कम हो गया है। यह वह अमाउंट है जिसे हमने पिछले टेस्ट केस में ट्रांसफर किया था।

Typescript क्लाइंट से सीधे टोकन बनाना और ट्रांसफर करना
Solana प्रोग्राम के बिना केवल web3.js Typescript क्लाइंट का उपयोग करके SPL token बनाना और उसके साथ इंटरैक्ट करना भी संभव है।
यह तब उपयोगी होता है जब आपको कस्टम लॉजिक वाले on-chain प्रोग्राम की आवश्यकता नहीं होती है। यदि आप केवल टोकन mint कर रहे हैं, उन्हें ट्रांसफर कर रहे हैं, या बैलेंस पढ़ रहे हैं, तो इसे क्लाइंट से करना तेज़ और सस्ता है। कोई प्रोग्राम लिखने या डिप्लॉय करने की आवश्यकता नहीं है।
आइए नए टोकन और ATAs बनाएं, और उन्हें सीधे TypeScript से ट्रांसफर करें।
TypeScript में Mint और ATA बनाना
एक नया Anchor प्रोजेक्ट spl_token_ts बनाएं और टेस्ट को इस अनुभाग (section) में बाद में दिखाए गए TypeScript कोड ब्लॉक से बदलें।
यह TypeScript टेस्ट सूट प्रदर्शित करता है कि @solana/spl-token लाइब्रेरी का उपयोग करके सीधे SPL Token प्रोग्राम के साथ कैसे इंटरैक्ट किया जाए।
यह निम्न कार्य करता है:
- सबसे पहले, यह
splToken.createMintको कॉल करता है। यह फंक्शन एक नया SPL token mint account बनाने के लिए Token Program कोInitializeMintइंस्ट्रक्शन भेजता है। हम कनेक्शन (connection), payer (signerKp, हमारा डिफ़ॉल्ट लोकल signer), mint authority और freeze authority, और आवश्यक दशमलव की संख्या (इस मामले में 6) प्रदान करते हैं। यह नए बने mint की पब्लिक की (public key) रिटर्न करता है। - इसके बाद, यह नए बनाए गए mint के लिए
signerKpका ATA बनाने के लिएsplToken.createAssociatedTokenAccountका उपयोग करता है। यह@solana/spl-tokenTypeScript SDK का एक हेल्पर (helper) है। पर्दे के पीछे (under the hood), यह ATA एड्रेस को डिराइव करता है और Associated Token Account Program को create इंस्ट्रक्शन भेजता है। - फिर, नई टोकन इकाइयाँ (units) जारी करने के लिए
splToken.mintToको कॉल किया जाता है। इसके लिए कनेक्शन, ट्रांजेक्शन के लिए payer (हमsignerKpका उपयोग करते हैं), mint की पब्लिक की, डेस्टिनेशन ATA एड्रेस, mint authority की पब्लिक की (signerKp.PublicKey), और mint किए जाने वाले टोकन की मात्रा (हम दशमलव को ध्यान में रखते हैं) की आवश्यकता होती है। - अंत में, यह सेटअप को सत्यापित (verify) करता है।
splToken.getMintmint account के लिए on-chain डेटा प्राप्त करता है, और हम यह assert करते हैं कि दशमलव और authorities हमारे द्वारा निर्दिष्ट किए गए अनुसार मेल खाते हैं।splToken.getAccountATA का डेटा प्राप्त करता है, और हम यह assert करते हैं कि इसका टोकन बैलेंस हमारे द्वारा अभी mint किए गए अमाउंट से मेल खाता है।
import * as anchor from "@coral-xyz/anchor";
import * as splToken from "@solana/spl-token";
import * as web3 from "@solana/web3.js";
import { assert } from 'chai';
describe("TypeScript SPL Token Tests", () => {
const provider = anchor.AnchorProvider.env();
const signerKp = provider.wallet.payer;
const toKp = new web3.Keypair();
// Define mint parameters
const mintDecimals = 6;
const mintAuthority = provider.wallet.publicKey;
const freezeAuthority = provider.wallet.publicKey;
it("Creates a mint account and ATA using TypeScript", async () => {
// Create the Mint
const mintPublicKey = await splToken.createMint(
provider.connection,
signerKp,
mintAuthority,
freezeAuthority,
mintDecimals
);
console.log("Created Mint:", mintPublicKey.toString());
// Create ATA for the signer
const ataAddress = await splToken.createAssociatedTokenAccount(
provider.connection,
signerKp,
mintPublicKey,
signerKp.publicKey
);
console.log("Created ATA:", ataAddress.toString());
// Mint some tokens
const mintAmount = BigInt(1000 * (10 ** mintDecimals)); // 1000 tokens
await splToken.mintTo(
provider.connection,
signerKp,
mintPublicKey,
ataAddress,
mintAuthority,
mintAmount
);
// Verify the mint
const mintInfo = await splToken.getMint(provider.connection, mintPublicKey);
assert.equal(mintInfo.decimals, mintDecimals, "Mint decimals should match");
assert.equal(mintInfo.mintAuthority?.toString(), mintAuthority.toString(), "Mint authority should match");
assert.equal(mintInfo.freezeAuthority?.toString(), freezeAuthority.toString(), "Freeze authority should match");
// Verify the balance
const accountInfo = await splToken.getAccount(provider.connection, ataAddress);
assert.equal(accountInfo.amount.toString(), mintAmount.toString(), "Balance should match minted amount");
});
});
TypeScript में टोकन बैलेंस प्राप्त करना
अब, टोकन बैलेंस पढ़ने के लिए निम्नलिखित it टेस्ट ब्लॉक जोड़ें।
यह टेस्ट ब्लॉक पहले टेस्ट के समान ही है:
- यह
signerKpके लिए एक नया mint और उसका संबंधित ATA बनाता है और इस ATA (ataAddress) में प्रारंभिक मात्रा में टोकन (इस मामले में 1000) mint करता है। - यहां मुख्य बिंदु बैलेंस प्राप्त (retrieval) करने को प्रदर्शित करना है। हम इसे करने के दो तरीके दिखाते हैं:
splToken.getAccount: पूरे टोकन अकाउंट स्टेट (state) को प्राप्त करता है, जिससे हम.amountप्रॉपर्टी को एक्सेस कर सकते हैं।provider.connection.getTokenAccountBalance: यह विशेष रूप से किसी टोकन अकाउंट का बैलेंस प्राप्त करने के लिए एक अधिक डायरेक्ट RPC कॉल है। यह एक ऑब्जेक्ट रिटर्न करता है जिसमें अमाउंट होता है।
- चित्रण (illustration) उद्देश्यों के लिए, दोनों विधियों (methods) का उपयोग किया जाता है, और हम यह assert करते हैं कि प्राप्त किया गया बैलेंस mint किए गए अमाउंट से मेल खाता है।
it("Reads token balance using TypeScript", async () => {
// Create a new mint for this test
const mintPublicKey = await splToken.createMint(
provider.connection,
signerKp,
mintAuthority,
freezeAuthority,
mintDecimals
);
// Create ATA
const ataAddress = await splToken.createAssociatedTokenAccount(
provider.connection,
signerKp,
mintPublicKey,
signerKp.publicKey
);
// Mint tokens
const mintAmount = BigInt(1000 * (10 ** mintDecimals)); // 1000 tokens
await splToken.mintTo(
provider.connection,
signerKp,
mintPublicKey,
ataAddress,
mintAuthority,
mintAmount
);
// Read balance using getAccount
const accountInfo = await splToken.getAccount(provider.connection, ataAddress);
console.log("Token Balance:", accountInfo.amount.toString());
assert.equal(accountInfo.amount.toString(), mintAmount.toString(), "Balance should match minted amount");
// Alternative: Read balance using getTokenAccountBalance
const balance = await provider.connection.getTokenAccountBalance(ataAddress);
assert.equal(balance.value.amount, mintAmount.toString(), "Balance should match minted amount");
});
अकाउंट्स के बीच टोकन ले जाने का टेस्ट करना
अंत में, टोकन ट्रांसफर करने के लिए अंतिम it टेस्ट ब्लॉक जोड़ें।
यह टेस्ट ब्लॉक:
- एक नया mint बनाता है।
- इसके बाद यह इस mint के लिए दो ATAs बनाता है: एक सोर्स ATA (
signerKp) के लिए और एक डेस्टिनेशन ATA (toKp) के लिए। ध्यान दें किtoKpएक नया जेनरेट किया गया keypair है, जो किसी अन्य यूजर का प्रतिनिधित्व करता है। - टोकन की 1000 इकाइयाँ सोर्स ATA (
signerKpके ATA) में mint की जाती हैं। - इस टेस्ट का मुख्य भाग (core)
splToken.transferफंक्शन है। यह फंक्शन ATAs के बीच टोकन ले जाने के लिए ट्रांजेक्शन बनाता और भेजता है। इसे कनेक्शन, payer/signer (signerKp), सोर्स ATA, डेस्टिनेशन ATA, सोर्स ATA की authority (जोsignerKp.publicKeyहै), और ट्रांसफर किए जाने वाले अमाउंट (500 टोकन) की आवश्यकता होती है। - ट्रांसफर के बाद, यह
provider.connection.getTokenAccountBalanceका उपयोग करके सोर्स और डेस्टिनेशन ATAs दोनों के बैलेंस प्राप्त करके परिणाम को सत्यापित करता है। अंत में, हम यह assert करते हैं कि सोर्स बैलेंस ट्रांसफर अमाउंट के बराबर कम हो गया है और डेस्टिनेशन बैलेंस अब ट्रांसफर अमाउंट के बराबर है।
it("Transfers tokens using TypeScript", async () => {
// Create a new mint
const mintPublicKey = await splToken.createMint(
provider.connection,
signerKp,
mintAuthority,
freezeAuthority,
mintDecimals
);
// Create source ATA
const sourceAta = await splToken.createAssociatedTokenAccount(
provider.connection,
signerKp,
mintPublicKey,
signerKp.publicKey
);
// Create destination ATA
const destinationAta = await splToken.createAssociatedTokenAccount(
provider.connection,
signerKp,
mintPublicKey,
toKp.publicKey
);
// Mint tokens to source
const mintAmount = BigInt(1000 * (10 ** mintDecimals)); // 1000 tokens
await splToken.mintTo(
provider.connection,
signerKp,
mintPublicKey,
sourceAta,
mintAuthority,
mintAmount
);
// Read balance before transfer
const sourceBalanceBefore = await provider.connection.getTokenAccountBalance(sourceAta);
const destinationBalanceBefore = await provider.connection.getTokenAccountBalance(destinationAta);
console.log("Source Balance before transfer:", sourceBalanceBefore.value.amount);
console.log("Destination Balance before transfer:", destinationBalanceBefore.value.amount);
// Transfer tokens
const transferAmount = BigInt(500 * (10 ** mintDecimals)); // 500 tokens
await splToken.transfer(
provider.connection,
signerKp,
sourceAta,
destinationAta,
signerKp.publicKey,
transferAmount
);
// Read balance after transfer
const sourceBalanceAfter = await provider.connection.getTokenAccountBalance(sourceAta);
const destinationBalanceAfter = await provider.connection.getTokenAccountBalance(destinationAta);
console.log("Source Balance after transfer:", sourceBalanceAfter.value.amount);
console.log("Destination Balance after transfer:", destinationBalanceAfter.value.amount);
assert.equal(sourceBalanceAfter.value.amount, (mintAmount - transferAmount).toString(), "Source should have 500 tokens left");
assert.equal(destinationBalanceAfter.value.amount, transferAmount.toString(), "Destination should have received 500 tokens");
});
यह देखने के लिए कि सब कुछ अपेक्षित (expected) रूप से काम करता है या नहीं, हम पूरा टेस्ट चलाते हैं।

अभ्यास (Exercise): एक disable_mint_authority फंक्शन लिखें जो set_authority इंस्ट्रक्शन के माध्यम से mint authority को None पर सेट करता है। authority प्रकार को AuthorityType::MintTokens पर सेट करें। उसके बाद, फंक्शन को कॉल करने के लिए एक टेस्ट लिखें, फिर अधिक टोकन mint करने का प्रयास करें, यह “supply is fixed” एरर के साथ विफल होना चाहिए। यह भी जांचें कि mint authority अब null है।
आपको नीचे जैसा ही परिणाम मिलना चाहिए।

यह लेख Solana पर ट्यूटोरियल सीरीज़ (tutorial series on Solana) का हिस्सा है।