Συναλλαγές και Εντολές
Στο Solana, οι χρήστες στέλνουν συναλλαγές για να αλληλεπιδράσουν με το δίκτυο. Οι συναλλαγές περιέχουν μία ή περισσότερες εντολές που καθορίζουν τις λειτουργίες προς επεξεργασία. Η λογική εκτέλεσης για τις εντολές αποθηκεύεται σε προγράμματα που έχουν αναπτυχθεί στο δίκτυο Solana, όπου κάθε πρόγραμμα ορίζει το δικό του σύνολο εντολών.
Παρακάτω είναι βασικές λεπτομέρειες σχετικά με την επεξεργασία συναλλαγών του Solana:
- Αν μια συναλλαγή περιλαμβάνει πολλαπλές εντολές, οι εντολές εκτελούνται με τη σειρά που προστέθηκαν στη συναλλαγή.
- Οι συναλλαγές είναι "ατομικές" - όλες οι εντολές πρέπει να επεξεργαστούν επιτυχώς, αλλιώς ολόκληρη η συναλλαγή αποτυγχάνει και δεν πραγματοποιούνται αλλαγές.
Μια συναλλαγή είναι ουσιαστικά ένα αίτημα για επεξεργασία μίας ή περισσότερων εντολών.
Απλοποιημένη Συναλλαγή
Μια συναλλαγή είναι σαν ένας φάκελος που περιέχει έντυπα. Κάθε έντυπο είναι μια εντολή που λέει στο δίκτυο τι να κάνει. Η αποστολή της συναλλαγής είναι σαν να ταχυδρομείτε τον φάκελο για να επεξεργαστούν τα έντυπα.
Βασικά Σημεία
- Οι συναλλαγές Solana περιλαμβάνουν εντολές που επικαλούνται προγράμματα στο δίκτυο.
- Οι συναλλαγές είναι ατομικές - αν οποιαδήποτε εντολή αποτύχει, ολόκληρη η συναλλαγή αποτυγχάνει και δεν πραγματοποιούνται αλλαγές.
- Οι εντολές σε μια συναλλαγή εκτελούνται με διαδοχική σειρά.
- Το όριο μεγέθους συναλλαγής είναι 1232 bytes.
- Κάθε εντολή απαιτεί τρία κομμάτια πληροφοριών:
- Τη διεύθυνση του προγράμματος προς επίκληση
- Τους λογαριασμούς από τους οποίους η εντολή διαβάζει ή στους οποίους γράφει
- Τυχόν επιπλέον δεδομένα που απαιτούνται από την εντολή (π.χ., παραμέτρους συνάρτησης)
Παράδειγμα Μεταφοράς SOL
Το παρακάτω διάγραμμα αναπαριστά μια συναλλαγή με μία μόνο εντολή για τη μεταφορά SOL από έναν αποστολέα σε έναν παραλήπτη.
Στο Solana, τα "πορτοφόλια" είναι λογαριασμοί που ανήκουν στο System Program. Μόνο ο ιδιοκτήτης του προγράμματος μπορεί να αλλάξει τα δεδομένα ενός λογαριασμού, οπότε η μεταφορά SOL απαιτεί την αποστολή μιας συναλλαγής για την επίκληση του System Program.
Μεταφορά SOL
Ο λογαριασμός του αποστολέα πρέπει να υπογράψει (is_signer
) τη συναλλαγή για
να επιτρέψει στο System Program να αφαιρέσει το υπόλοιπο των lamport. Οι
λογαριασμοί του αποστολέα και του παραλήπτη πρέπει να είναι εγγράψιμοι
(is_writable
) καθώς τα υπόλοιπα των lamport τους αλλάζουν.
Μετά την αποστολή της συναλλαγής, το System Program επεξεργάζεται την εντολή μεταφοράς. Στη συνέχεια, το System Program ενημερώνει τα υπόλοιπα των lamport τόσο του λογαριασμού του αποστολέα όσο και του παραλήπτη.
Διαδικασία Μεταφοράς SOL
Τα παρακάτω παραδείγματα δείχνουν πώς να στείλετε μια συναλλαγή που μεταφέρει SOL από έναν λογαριασμό σε έναν άλλο.
import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners} from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";// Create a connection to clusterconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();const LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Fund sender with airdropawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: sender.address,lamports: lamports(LAMPORTS_PER_SOL), // 1 SOLcommitment: "confirmed"});// Check balance before transferconst { value: preBalance1 } = await rpc.getBalance(sender.address).send();const { value: preBalance2 } = await rpc.getBalance(recipient.address).send();// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount // 0.01 SOL in lamports});// Add the transfer instruction to a new transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();const transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));// Send the transaction to the networkconst signedTransaction =await signTransactionMessageWithSigners(transactionMessage);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });const transactionSignature = getSignatureFromTransaction(signedTransaction);// Check balance after transferconst { value: postBalance1 } = await rpc.getBalance(sender.address).send();const { value: postBalance2 } = await rpc.getBalance(recipient.address).send();console.log("Sender prebalance:",Number(preBalance1) / Number(LAMPORTS_PER_SOL));console.log("Recipient prebalance:",Number(preBalance2) / Number(LAMPORTS_PER_SOL));console.log("Sender postbalance:",Number(postBalance1) / Number(LAMPORTS_PER_SOL));console.log("Recipient postbalance:",Number(postBalance2) / Number(LAMPORTS_PER_SOL));console.log("Transaction Signature:", transactionSignature);
Οι βιβλιοθήκες πελάτη συχνά αφαιρούν τις λεπτομέρειες για τη δημιουργία εντολών προγράμματος. Αν δεν υπάρχει διαθέσιμη βιβλιοθήκη, μπορείτε να δημιουργήσετε χειροκίνητα την εντολή. Αυτό απαιτεί να γνωρίζετε τις λεπτομέρειες υλοποίησης της εντολής.
Τα παρακάτω παραδείγματα δείχνουν πώς να δημιουργήσετε χειροκίνητα την εντολή
μεταφοράς. Η καρτέλα Expanded Instruction
είναι λειτουργικά ισοδύναμη με την
καρτέλα Instruction
.
- Kit
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount * LAMPORTS_PER_SOL});
- Legacy
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = SystemProgram.transfer({fromPubkey: sender.publicKey,toPubkey: receiver.publicKey,lamports: transferAmount * LAMPORTS_PER_SOL});
- Rust
let transfer_amount = LAMPORTS_PER_SOL / 100; // 0.01 SOLlet transfer_instruction =system_instruction::transfer(&sender.pubkey(), &recipient.pubkey(), transfer_amount);
Στις παρακάτω ενότητες, θα αναλύσουμε λεπτομερώς τις συναλλαγές και τις εντολές.
Instructions
Μια instruction σε ένα Solana πρόγραμμα μπορεί να θεωρηθεί ως μια δημόσια συνάρτηση που μπορεί να κληθεί από οποιονδήποτε χρησιμοποιεί το δίκτυο Solana.
Η κλήση μιας εντολής προγράμματος απαιτεί τρία βασικά στοιχεία πληροφοριών:
- Program ID: Το πρόγραμμα με τη λογική εκτέλεσης για την εντολή
- Accounts: Λίστα λογαριασμών που χρειάζεται η εντολή
- Instruction Data: Πίνακας byte που καθορίζει την εντολή που θα εκτελεστεί στο πρόγραμμα και τυχόν παραμέτρους που απαιτούνται από την εντολή
pub struct Instruction {/// Pubkey of the program that executes this instruction.pub program_id: Pubkey,/// Metadata describing accounts that should be passed to the program.pub accounts: Vec<AccountMeta>,/// Opaque data passed to the program for its own interpretation.pub data: Vec<u8>,}
Transaction Instruction
AccountMeta
Κάθε λογαριασμός που απαιτείται από μια εντολή πρέπει να παρέχεται ως AccountMeta που περιέχει:
pubkey
: Η διεύθυνση του λογαριασμούis_signer
: Εάν ο λογαριασμός πρέπει να υπογράψει τη συναλλαγήis_writable
: Εάν η εντολή τροποποιεί τα δεδομένα του λογαριασμού
pub struct AccountMeta {/// An account's public key.pub pubkey: Pubkey,/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.pub is_signer: bool,/// True if the account data or metadata may be mutated during program execution.pub is_writable: bool,}
AccountMeta
Καθορίζοντας εκ των προτέρων ποιους λογαριασμούς διαβάζει ή γράφει μια εντολή, οι συναλλαγές που δεν τροποποιούν τους ίδιους λογαριασμούς μπορούν να εκτελεστούν παράλληλα.
Παράδειγμα δομής εντολής
Εκτελέστε τα παρακάτω παραδείγματα για να δείτε τη δομή μιας εντολής μεταφοράς SOL.
import { generateKeyPairSigner, lamports } from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();// Define the amount to transferconst LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount});console.log(JSON.stringify(transferInstruction, null, 2));
Τα παρακάτω παραδείγματα δείχνουν την έξοδο από τα προηγούμενα αποσπάσματα κώδικα. Η ακριβής μορφή διαφέρει ανάλογα με το SDK, αλλά κάθε εντολή Solana απαιτεί τις ακόλουθες πληροφορίες:
- Program ID: Η διεύθυνση του προγράμματος που θα εκτελέσει την εντολή.
- Accounts: Μια λίστα λογαριασμών που απαιτούνται από την εντολή. Για κάθε λογαριασμό, η εντολή πρέπει να καθορίζει τη διεύθυνσή του, αν πρέπει να υπογράψει τη συναλλαγή, και αν θα γίνει εγγραφή σε αυτόν.
- Data: Ένα buffer byte που υποδεικνύει στο πρόγραμμα ποια εντολή να εκτελέσει και περιλαμβάνει τυχόν απαιτούμενα ορίσματα για την εντολή.
{"accounts": [{"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC","role": 3,"signer": {"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC","keyPair": {"privateKey": {},"publicKey": {}}}},{"address": "2mBY6CTgeyJNJDzo6d2Umipw2aGUquUA7hLdFttNEj7p","role": 1}],"programAddress": "11111111111111111111111111111111","data": {"0": 2,"1": 0,"2": 0,"3": 0,"4": 128,"5": 150,"6": 152,"7": 0,"8": 0,"9": 0,"10": 0,"11": 0}}
Συναλλαγές
Μια συναλλαγή Solana transaction αποτελείται από:
- Signatures: Ένας πίνακας υπογραφών που περιλαμβάνονται στη συναλλαγή.
- Message: Λίστα εντολών που θα επεξεργαστούν ατομικά.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Μορφή Συναλλαγής
Η δομή ενός μηνύματος συναλλαγής αποτελείται από:
- Message Header: Καθορίζει τον αριθμό των λογαριασμών υπογραφόντων και μόνο για ανάγνωση.
- Account Addresses: Ένας πίνακας διευθύνσεων λογαριασμών που απαιτούνται από τις εντολές στη συναλλαγή.
- Recent Blockhash: Λειτουργεί ως χρονοσφραγίδα για τη συναλλαγή.
- Instructions: Ένας πίνακας εντολών προς εκτέλεση.
pub struct Message {/// The message header, identifying signed and read-only `account_keys`.pub header: MessageHeader,/// All the account keys used by this transaction.#[serde(with = "short_vec")]pub account_keys: Vec<Pubkey>,/// The id of a recent ledger entry.pub recent_blockhash: Hash,/// Programs that will be executed in sequence and committed in/// one atomic transaction if all succeed.#[serde(with = "short_vec")]pub instructions: Vec<CompiledInstruction>,}
Μήνυμα Συναλλαγής
Μέγεθος Συναλλαγής
Οι συναλλαγές Solana έχουν όριο μεγέθους 1232 bytes. Αυτό το όριο προέρχεται από το μέγεθος Maximum Transmission Unit (MTU) του IPv6 που είναι 1280 bytes, μείον 48 bytes για επικεφαλίδες δικτύου (40 bytes IPv6 + 8 bytes επικεφαλίδα τμήματος).
Το συνολικό μέγεθος μιας συναλλαγής (υπογραφές και μήνυμα) πρέπει να παραμένει κάτω από αυτό το όριο και περιλαμβάνει:
- Signatures: 64 bytes το καθένα
- Message: Header (3 bytes), κλειδιά λογαριασμών (32 bytes το καθένα), πρόσφατο blockhash (32 bytes), και εντολές
Μορφή Συναλλαγής
Επικεφαλίδα Μηνύματος
Η επικεφαλίδα μηνύματος χρησιμοποιεί τρία bytes για να ορίσει τα προνόμια λογαριασμού.
- Απαιτούμενες υπογραφές
- Αριθμός λογαριασμών μόνο για ανάγνωση με υπογραφή
- Αριθμός λογαριασμών μόνο για ανάγνωση χωρίς υπογραφή
pub struct MessageHeader {/// The number of signatures required for this message to be considered/// valid. The signers of those signatures must match the first/// `num_required_signatures` of [`Message::account_keys`].pub num_required_signatures: u8,/// The last `num_readonly_signed_accounts` of the signed keys are read-only/// accounts.pub num_readonly_signed_accounts: u8,/// The last `num_readonly_unsigned_accounts` of the unsigned keys are/// read-only accounts.pub num_readonly_unsigned_accounts: u8,}
Κεφαλίδα Μηνύματος
Μορφή Συμπαγούς Πίνακα
Ένας συμπαγής πίνακας σε ένα μήνυμα συναλλαγής είναι ένας πίνακας που σειριοποιείται στην ακόλουθη μορφή:
- Το μήκος του πίνακα (κωδικοποιημένο ως compact-u16)
- Τα στοιχεία του πίνακα καταχωρημένα το ένα μετά το άλλο
Μορφή συμπαγούς πίνακα
Αυτή η μορφή χρησιμοποιείται για την κωδικοποίηση των μηκών των πινάκων Διευθύνσεις Λογαριασμών και Οδηγίες στα μηνύματα συναλλαγών.
Πίνακας Διευθύνσεων Λογαριασμών
Ένα μήνυμα συναλλαγής περιέχει έναν πίνακα διευθύνσεων λογαριασμών που απαιτούνται από τις οδηγίες του. Ο πίνακας ξεκινά με έναν αριθμό compact-u16 που υποδεικνύει πόσες διευθύνσεις περιέχει. Οι διευθύνσεις ταξινομούνται στη συνέχεια με βάση τα προνόμιά τους, όπως καθορίζονται από την κεφαλίδα του μηνύματος.
- Λογαριασμοί που είναι εγγράψιμοι και υπογράφοντες
- Λογαριασμοί που είναι μόνο για ανάγνωση και υπογράφοντες
- Λογαριασμοί που είναι εγγράψιμοι και όχι υπογράφοντες
- Λογαριασμοί που είναι μόνο για ανάγνωση και όχι υπογράφοντες
Συμπαγής πίνακας διευθύνσεων λογαριασμών
Πρόσφατο Blockhash
Κάθε συναλλαγή απαιτεί ένα πρόσφατο blockhash που εξυπηρετεί δύο σκοπούς:
- Λειτουργεί ως χρονική σφραγίδα
- Αποτρέπει τις διπλές συναλλαγές
Ένα blockhash λήγει μετά από 150 μπλοκ (περίπου 1 λεπτό με την υπόθεση χρόνων μπλοκ 400ms), μετά το οποίο η συναλλαγή δεν μπορεί να επεξεργαστεί.
Μπορείτε να χρησιμοποιήσετε τη μέθοδο RPC
getLatestBlockhash
για να λάβετε το
τρέχον blockhash και το τελευταίο ύψος μπλοκ στο οποίο το blockhash θα είναι
έγκυρο. Εδώ είναι ένα παράδειγμα στο
Solana Playground.
Πίνακας Οδηγιών
Ένα μήνυμα συναλλαγής περιέχει έναν πίνακα οδηγιών στον τύπο CompiledInstruction. Οι οδηγίες μετατρέπονται σε αυτόν τον τύπο όταν προστίθενται σε μια συναλλαγή.
Όπως ο πίνακας διευθύνσεων λογαριασμών στο μήνυμα, ξεκινά με ένα μήκος compact-u16 ακολουθούμενο από τα δεδομένα οδηγιών. Κάθε οδηγία περιέχει:
- Δείκτης ID Προγράμματος: Ένας δείκτης u8 που δείχνει στη διεύθυνση του προγράμματος στον πίνακα διευθύνσεων λογαριασμών. Αυτό καθορίζει το πρόγραμμα που θα επεξεργαστεί την εντολή.
- Δείκτες Λογαριασμών: Ένας πίνακας δεικτών u8 που δείχνουν στις διευθύνσεις λογαριασμών που απαιτούνται για αυτήν την εντολή.
- Instruction Data: Ένας πίνακας byte που καθορίζει ποια εντολή θα εκτελεστεί στο πρόγραμμα και οποιαδήποτε πρόσθετα δεδομένα απαιτούνται από την εντολή (π.χ. ορίσματα συνάρτησης).
pub struct CompiledInstruction {/// Index into the transaction keys array indicating the program account that executes this instruction.pub program_id_index: u8,/// Ordered indices into the transaction keys array indicating which accounts to pass to the program.#[serde(with = "short_vec")]pub accounts: Vec<u8>,/// The program input data.#[serde(with = "short_vec")]pub data: Vec<u8>,}
Συμπαγής πίνακας Εντολών
Παράδειγμα Δομής Συναλλαγής
Εκτελέστε τα παρακάτω παραδείγματα για να δείτε τη δομή μιας συναλλαγής με μία μόνο εντολή μεταφοράς SOL.
import {createSolanaRpc,generateKeyPairSigner,lamports,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstructions,pipe,signTransactionMessageWithSigners,getCompiledTransactionMessageDecoder} from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";const rpc = createSolanaRpc("http://localhost:8899");const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();// Define the amount to transferconst LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount});// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Decode the messageBytesconst compiledTransactionMessage =getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);console.log(JSON.stringify(compiledTransactionMessage, null, 2));
Τα παρακάτω παραδείγματα δείχνουν την έξοδο του μηνύματος συναλλαγής από τα προηγούμενα αποσπάσματα κώδικα. Η ακριβής μορφή διαφέρει ανάλογα με το SDK, αλλά περιλαμβάνει τις ίδιες πληροφορίες.
{"version": 0,"header": {"numSignerAccounts": 1,"numReadonlySignerAccounts": 0,"numReadonlyNonSignerAccounts": 1},"staticAccounts": ["HoCy8p5xxDDYTYWEbQZasEjVNM5rxvidx8AfyqA4ywBa","5T388jBjovy7d8mQ3emHxMDTbUF8b7nWvAnSiP3EAdFL","11111111111111111111111111111111"],"lifetimeToken": "EGCWPUEXhqHJWYBfDirq3mHZb4qDpATmYqBZMBy9TBC1","instructions": [{"programAddressIndex": 2,"accountIndices": [0, 1],"data": {"0": 2,"1": 0,"2": 0,"3": 0,"4": 128,"5": 150,"6": 152,"7": 0,"8": 0,"9": 0,"10": 0,"11": 0}}]}
Όταν ανακτάτε μια συναλλαγή χρησιμοποιώντας την υπογραφή της μετά την αποστολή της στο δίκτυο, θα λάβετε μια απάντηση με την ακόλουθη δομή.
Το πεδίο message
περιέχει τα ακόλουθα πεδία:
-
header
: Καθορίζει προνόμια ανάγνωσης/εγγραφής και υπογραφής για διευθύνσεις στον πίνακαaccountKeys
-
accountKeys
: Πίνακας όλων των διευθύνσεων λογαριασμών που χρησιμοποιούνται στις εντολές της συναλλαγής -
recentBlockhash
: Blockhash που χρησιμοποιείται για τη χρονοσήμανση της συναλλαγής -
instructions
: Πίνακας εντολών προς εκτέλεση. Κάθεaccount
καιprogramIdIndex
σε μια εντολή αναφέρεται στον πίνακαaccountKeys
με δείκτη. -
signatures
: Πίνακας που περιλαμβάνει υπογραφές για όλους τους λογαριασμούς που απαιτούνται ως υπογράφοντες από τις εντολές στη συναλλαγή. Μια υπογραφή δημιουργείται υπογράφοντας το μήνυμα συναλλαγής χρησιμοποιώντας το αντίστοιχο ιδιωτικό κλειδί για έναν λογαριασμό.
{"blockTime": 1745196488,"meta": {"computeUnitsConsumed": 150,"err": null,"fee": 5000,"innerInstructions": [],"loadedAddresses": {"readonly": [],"writable": []},"logMessages": ["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"postBalances": [989995000, 10000000, 1],"postTokenBalances": [],"preBalances": [1000000000, 0, 1],"preTokenBalances": [],"rewards": [],"status": {"Ok": null}},"slot": 13049,"transaction": {"message": {"header": {"numReadonlySignedAccounts": 0,"numReadonlyUnsignedAccounts": 1,"numRequiredSignatures": 1},"accountKeys": ["8PLdpLxkuv9Nt8w3XcGXvNa663LXDjSrSNon4EK7QSjQ","7GLg7bqgLBv1HVWXKgWAm6YoPf1LoWnyWGABbgk487Ma","11111111111111111111111111111111"],"recentBlockhash": "7ZCxc2SDhzV2bYgEQqdxTpweYJkpwshVSDtXuY7uPtjf","instructions": [{"accounts": [0, 1],"data": "3Bxs4NN8M2Yn4TLb","programIdIndex": 2,"stackHeight": null}],"indexToProgramIds": {}},"signatures": ["3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX"]},"version": "legacy"}
Is this page helpful?