指令
指令是与 Solana 区块链交互的基本构建块。指令本质上是一个公共函数,任何使用 Solana 网络的人都可以调用。每个指令用于执行特定的操作。指令的执行逻辑存储在程序中,每个程序定义其自己的指令集。要与 Solana 网络交互,需要将一个或多个指令添加到交易中并发送到网络进行处理。
SOL 转账示例
下图展示了交易和指令如何协同工作,使用户能够与网络交互。在此示例中,SOL 从一个账户转移到另一个账户。
发送方账户的元数据表明它必须为交易签名。(这允许系统程序扣除lamports。)发送方和接收方账户都必须是可写的,以便其 lamport 余额发生变化。为了执行此指令,发送方的钱包发送包含其签名和包含 SOL 转账指令的消息的交易。
SOL 转账图示
交易发送后,系统程序处理转账指令并更新两个账户的 lamport 余额。
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);
Console
Click to execute the code.
指令
展示包含指令的交易及其三个组成部分的图示
一个
Instruction
包含以下信息:
Instruction struct
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>,}
程序 ID
指令的program_id
是包含指令业务逻辑的程序的公钥地址。
账户元数据
指令的 accounts
数组是一个
AccountMeta
结构体的数组。每个指令交互的账户都必须提供元数据。(这允许交易并行执行指令,只要它们不修改同一个账户。)
下图展示了一个包含单个指令的交易。指令的 accounts
数组包含两个账户的元数据。
一个包含一个指令的交易。指令包含两个 AccountMeta 结构体在其 accounts 数组中。
账户元数据包括以下信息:
- pubkey:账户的公钥地址
- is_signer:如果账户必须签署交易,则设置为
true
- is_writable:如果指令修改账户的数据,则设置为
true
要了解指令需要哪些账户,包括哪些必须是可写、只读或签署交易的账户,您必须参考程序定义的指令实现。
AccountMeta
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,}
数据
指令的 data
是一个字节数组,用于指定调用程序的哪条指令。它还包括指令所需的任何参数。
创建指令示例
下面的示例展示了一个 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));
Console
Click to execute the code.
以下代码显示了前面代码片段的输出。格式会因 SDK 而异,但请注意,每个指令都包含相同的三条必要信息:program_id
、accounts
、data
。
{"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}}
以下示例展示了如何手动构建转账指令。(Expanded Instruction
标签与
Instruction
标签在功能上是等效的。)
实际操作中,通常不需要手动构建 Instruction
。
大多数程序提供了带有辅助函数的客户端库,这些函数会为您创建指令。如果没有可用的库,您可以手动构建指令。
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount * LAMPORTS_PER_SOL});
Is this page helpful?