यह ट्यूटोरियल दिखाता है कि सीधे Solana web3 Javascript क्लाइंट से अकाउंट डेटा कैसे पढ़ा जाए ताकि एक वेब ऐप इसे फ्रंटएंड पर पढ़ सके।
पिछले ट्यूटोरियल में हमने अपने लिखे गए डेटा को पढ़ने के लिए solana account <account address> का उपयोग किया था, लेकिन यह काम नहीं करेगा यदि हम किसी वेबसाइट पर dApp बना रहे हैं।
इसके बजाय, हमें स्टोरेज अकाउंट के एड्रेस की गणना करनी होगी, डेटा पढ़ना होगा, और Solana web3 क्लाइंट से डेटा को डीसीरियलाइज़ (deserialize) करना होगा।
कल्पना करें कि Ethereum में हम पब्लिक वेरिएबल्स या व्यू फ़ंक्शंस का उपयोग करने से बचना चाहते हैं, लेकिन फिर भी फ्रंटएंड पर उनकी वैल्यू दिखाना चाहते हैं। स्टोरेज वेरिएबल्स को पब्लिक किए बिना या व्यू फ़ंक्शन जोड़े बिना उनकी वैल्यू देखने के लिए, हम इसके बजाय getStorageAt(contract_address, slot) API का उपयोग करेंगे। हम Solana में भी कुछ ऐसा ही करने जा रहे हैं, सिवाय इसके कि (contract_address, slot) पेयर पास करने के बजाय, हम केवल प्रोग्राम का एड्रेस पास करते हैं और इसके स्टोरेज अकाउंट(s) का एड्रेस प्राप्त (derive) करते हैं।
यहाँ पिछले ट्यूटोरियल का Rust कोड है। यह MyStorage को इनिशियलाइज़ करता है और set फ़ंक्शन का उपयोग करके x पर लिखता है। हम इस ट्यूटोरियल में इसे मॉडिफ़ाई नहीं करेंगे:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("GLKUcCtHx6nkuDLTz5TNFrR4tt4wDNuk24Aid2GrDLC6");
#[program]
pub mod basic_storage {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, new_x: u64) -> Result<()> {
ctx.accounts.my_storage.x = new_x;
Ok(())
}
}
#[derive(Accounts)]
pub struct Set<'info> {
#[account(mut, seeds = [], bump)]
pub my_storage: Account<'info, MyStorage>,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init,
payer = signer,
space=size_of::<MyStorage>() + 8,
seeds = [],
bump)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyStorage {
x: u64,
}
निम्नलिखित Typescript यूनिट टेस्ट है जो:
- अकाउंट को इनिशियलाइज़ करता है
- स्टोरेज में
170लिखता है fetchफ़ंक्शन का उपयोग करके वैल्यू को वापस पढ़ता है:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { BasicStorage } from "../target/types/basic_storage";
describe("basic_storage", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.BasicStorage as Program<BasicStorage>;
it("Is initialized!", async () => {
const seeds = []
const [myStorage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
console.log("the storage account address is", myStorage.toBase58());
await program.methods.initialize().accounts({myStorage: myStorage}).rpc();
await program.methods.set(new anchor.BN(170)).accounts({myStorage: myStorage}).rpc();
// ***********************************
// *** NEW CODE TO READ THE STRUCT ***
// ***********************************
let myStorageStruct = await program.account.myStorage.fetch(myStorage);
console.log("The value of x is:",myStorageStruct.x.toString());
});
});
Anchor में किसी अकाउंट को देखने का काम इसके साथ किया जा सकता है:
let myStorageStruct = await program.account.myStorage.fetch(myStorage);
console.log("The value of x is:", myStorageStruct.x.toString());
Anchor स्वचालित रूप से MyStorage अकाउंट के एड्रेस की गणना कर रहा है, इसे पढ़ रहा है, और इसे Typescript ऑब्जेक्ट के रूप में फ़ॉर्मेट कर रहा है।
यह समझने के लिए कि Anchor जादुई तरीके से Rust स्ट्रक्चर को Typescript स्ट्रक्चर में कैसे बदल रहा है, आइए target/idl/basic_storage.json में IDL पर एक नज़र डालें। JSON के निचले हिस्से की ओर, हम उस स्ट्रक्चर की परिभाषा देख सकते हैं जो हमारा प्रोग्राम बना रहा है:
"accounts": [
{
"name": "MyStorage",
"type": {
"kind": "struct",
"fields": [
{
"name": "x",
"type": "u64"
}
]
}
}
],
यह तरीका केवल उन अकाउंट्स के लिए काम करता है जिन्हें आपके प्रोग्राम या क्लाइंट ने इनिशियलाइज़ या बनाया है और जिनके लिए आपके पास IDL है, यह किसी भी रैंडम (arbitrary) अकाउंट के लिए काम नहीं करेगा।
यानी, यदि आप Solana पर कोई रैंडम अकाउंट चुनते हैं और उपरोक्त कोड का उपयोग करते हैं, तो डीसीरियलाइज़ेशन लगभग निश्चित रूप से विफल हो जाएगा। इस लेख में आगे हम अकाउंट को अधिक “raw” तरीके से पढ़ेंगे।
फ़ंक्शन fetch जादुई नहीं है। तो हम इसे उस अकाउंट के लिए कैसे करें जिसे हमने नहीं बनाया है?
Anchor Solana प्रोग्राम्स द्वारा बनाए गए अकाउंट्स से डेटा प्राप्त करना
यदि हम Anchor के साथ बनाए गए किसी अन्य प्रोग्राम का IDL जानते हैं, तो हम आसानी से उसका अकाउंट डेटा पढ़ सकते हैं।
आइए एक अन्य शेल में किसी दूसरे प्रोग्राम को anchor init करें, फिर उससे एक अकाउंट इनिशियलाइज़ कराएं, और फिर उस स्ट्रक्चर में एक सिंगल बुलियन वेरिएबल को true पर सेट करें। हम इस अन्य अकाउंट को other_program कहेंगे और उस स्ट्रक्चर को जो इसके बुलियन को स्टोर करता है, TrueOrFalse कहेंगे:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("4z4dduMSFKFJDnUAKaHnbhHySK8x1PwgArUBXzksjwa8");
#[program]
pub mod other_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn setbool(ctx: Context<SetFlag>, flag: bool) -> Result<()> {
ctx.accounts.true_or_false.flag = flag;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
signer: Signer<'info>,
system_program: Program<'info, System>,
#[account(init, payer = signer, space = size_of::<TrueOrFalse>() + 8, seeds=[], bump)]
true_or_false: Account<'info, TrueOrFalse>,
}
#[derive(Accounts)]
pub struct SetFlag<'info> {
#[account(mut)]
true_or_false: Account<'info, TrueOrFalse>,
}
#[account]
pub struct TrueOrFalse {
flag: bool,
}
Typescript कोड:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { OtherProgram } from "../target/types/other_program";
describe("other_program", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.OtherProgram as Program<OtherProgram>;
it("Is initialized!", async () => {
const seeds = []
const [TrueOrFalse, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
console.log("address: ", program.programId.toBase58());
await program.methods.initialize().accounts({trueOrFalse: TrueOrFalse}).rpc();
await program.methods.setbool(true).accounts({trueOrFalse: TrueOrFalse}).rpc();
});
});
एक स्थानीय वैलिडेटर के विरुद्ध किसी अन्य शेल में टेस्ट रन करें। जो programId प्रिंट होकर आता है, उसे नोट कर लें। हमें other_program के अकाउंट का एड्रेस प्राप्त करने के लिए इसकी आवश्यकता होगी।
read प्रोग्राम
एक अन्य शेल में, एक और प्रोग्राम को anchor init करें। हम इसे read कहेंगे। हम other_program के TrueOrFalse स्ट्रक्चर को पढ़ने के लिए केवल Typescript कोड का उपयोग करने जा रहे हैं, किसी Rust का उपयोग नहीं किया गया है। यह किसी अन्य प्रोग्राम के स्टोरेज अकाउंट से पढ़ने को सिमुलेट करता है।
हमारी डायरेक्टरी का लेआउट इस प्रकार है:
parent_dir/
∟ other_program/
∟ read/
निम्नलिखित कोड other_program से TrueOrFalse स्ट्रक्चर को पढ़ेगा। सुनिश्चित करें कि
otherProgramAddressऊपर प्रिंट किए गए एड्रेस से मेल खाता हो- सुनिश्चित करें कि आप
other_program.jsonIDL को सही फ़ाइल लोकेशन से पढ़ रहे हैं - सुनिश्चित करें कि टेस्ट को
--skip-local-validatorके साथ रन किया जाए ताकि यह पक्का हो सके कि यह कोड उसी अकाउंट को पढ़ता है जिसे दूसरे प्रोग्राम ने बनाया था
import * as anchor from "@coral-xyz/anchor";
describe("read", () => {
anchor.setProvider(anchor.AnchorProvider.env());
it("Read other account", async () => {
// the other program's programdId -- make sure the address is correct
const otherProgramAddress = "4z4dduMSFKFJDnUAKaHnbhHySK8x1PwgArUBXzksjwa8";
const otherProgramId = new anchor.web3.PublicKey(otherProgramAddress);
// load the other program's idl -- make sure the path is correct
const otherIdl = JSON.parse(
require("fs").readFileSync("../other_program/target/idl/other_program.json", "utf8")
);
const otherProgram = new anchor.Program(otherIdl, otherProgramId);
const seeds = []
const [trueOrFalseAcc, _bump] =
anchor.web3.PublicKey.findProgramAddressSync(seeds, otherProgramId);
let otherStorageStruct = await otherProgram.account.trueOrFalse.fetch(trueOrFalseAcc);
console.log("The value of flag is:", otherStorageStruct.flag.toString());
});
});
अपेक्षित आउटपुट इस प्रकार है:

फिर से, यह केवल तभी काम करता है जब दूसरा Solana प्रोग्राम Anchor के साथ बनाया गया हो। यह इस बात पर निर्भर करता है कि Anchor स्ट्रक्चर्स को कैसे सीरियलाइज़ करता है।
किसी भी रैंडम (arbitrary) अकाउंट के लिए डेटा प्राप्त करना
निम्नलिखित अनुभाग में हम दिखाते हैं कि बिना Anchor के जादू के डेटा कैसे पढ़ा जाए।
दुर्भाग्य से, Solana के Typescript क्लाइंट के लिए दस्तावेज़ बहुत सीमित हैं और इस विषय पर ट्यूटोरियल्स को अप्रचलित करने के लिए लाइब्रेरी को कई बार अपडेट किया गया है।
अपनी आवश्यकता के अनुसार Solana web3 Typescript फ़ंक्शन को खोजने का आपका सबसे अच्छा तरीका HTTP JSON RPC Methods को देखना है और ऐसा खोजना है जो आशाजनक लगे। हमारे मामले में, getAccountInfo आशाजनक लगता है (नीला तीर)।

इसके बाद हम उस मेथड को Solana web3 js में खोजने का प्रयास करना चाहते हैं। अधिमानतः, आपको ऑटोकंप्लीशन के साथ एक IDE का उपयोग करना चाहिए ताकि आप तब तक प्रयोग कर सकें जब तक कि आपको वह फ़ंक्शन न मिल जाए जैसा कि निम्नलिखित वीडियो दिखाता है:
नीचे हम टेस्ट को फिर से चलाने का अपेक्षित आउटपुट दिखाते हैं:

हेक्स aa बाइट के चारों ओर हरा बॉक्स दिखाता है कि हमने सफलतापूर्वक डेसीमल 170 वैल्यू को प्राप्त कर लिया है जिसे हमने set() फ़ंक्शन में स्टोर किया था।
अगला कदम डेटा बफ़र को पार्स करना है, जिसे हम यहाँ कवर नहीं करेंगे।
Solana अकाउंट में डेटा को सीरियलाइज़ करने का कोई “अनिवार्य” तरीका नहीं है। Anchor स्ट्रक्चर्स को अपने तरीके से सीरियलाइज़ करता है, लेकिन अगर किसी ने raw Rust (बिना Anchor के) में Solana प्रोग्राम लिखा है या अपने स्वयं के सीरियलाइज़ेशन एल्गोरिथम का उपयोग किया है, तो आपको उनके डेटा को सीरियलाइज़ करने के तरीके के आधार पर अपने डीसीरियलाइज़ेशन एल्गोरिथम को कस्टमाइज़ करना होगा।
Solana सीखना जारी रखें
आप हमारे बाकी के Solana कोर्स यहाँ देख सकते हैं।
मूल रूप से 26 फ़रवरी, 2024 को प्रकाशित