本文详细介绍了如何在Solana区块链上使用版本化交易和地址查找表,通过代码示例展示了如何创建、填充和使用地址查找表来优化交易大小。
2022 年 10 月 10 日(Epoch 358),Solana 通过一个称为“版本化交易”的概念增加了对新交易版本类型的支持。在此更改之后,Solana 运行时现在支持两种类型的交易:“legacy”(旧交易)和“0”(包含地址查找表的交易)。地址查找表为开发者提供了一种新的方式,可以有效地将许多地址加载到交易中,因此如果你因账户问题而遇到存储大小问题,这可能会帮助你解决!
地址查找表程序(Program ID: AddressLookupTab1e1111111111111111111111111)允许你将公钥存储在链上查找表中,并在你的版本化交易中调用查找表。由于传输到 Solana 验证器的序列化交易不得超过 1,232 字节(源代码,参考),利用查找表可以减少交易大小,并启用更复杂的交易指令(例如,更多的账户,更多的跨程序调用等)。Solana 查找表“有效地将 32 字节的地址‘压缩’为 1 字节的索引值”(来源)。这意味着通过使用查找表,我们的交易大小将更小(或者我们可以在交易中打包更多内容)!
在本指南中,你将:
创建并执行版本 0(V0)交易,
创建并填充地址查找表,
比较两个几乎相同的交易的交易大小(一个使用查找表,一个不使用)。
如果你需要帮助确保你现有的客户端应用程序可以支持版本化交易,请查看我们的指南:如何更新你的 Solana 客户端以处理版本化交易。
已安装 Nodejs(版本 16.15 或更高)
Typescript 经验和已安装的 ts-node
运行基本 Solana 交易的经验(指南:如何使用 Javascript 在 Solana 上发送交易)
如果你是从我们之前的指南:如何在 Solana 上使用版本化交易过来的,你可以重用你的 app.ts 并跳到组装版本 0 交易部分。否则,请设置你的项目:
在终端中创建一个新的项目目录,使用以下命令:
mkdir solana-versioned-tx
cd solana-versioned-tx
为你的应用程序创建一个文件,app.ts:
echo > app.ts
使用“yes”标志初始化你的项目,以使用新包的默认值:
yarn init --yes
# 或
npm init --yes
创建一个启用了 .json 导入的 tsconfig.json:
tsc -init --resolveJsonModule true
我们需要为本次练习添加 Solana Web3 库。在终端中输入:
yarn add @solana/web3.js@1
# 或
npm install @solana/web3.js@1
让我们开始吧。
打开 app.ts,并在第 1 行粘贴以下导入:
import { AddressLookupTableProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction, TransactionSignature, TransactionConfirmationStatus, SignatureStatus } from '@solana/web3.js';
我们从 Solana Web3 库中导入了一些基本的方法和类。可能有一些导入是你以前没有见过的(例如,AddressLookupTableProgram 和 VersionedTransaction)——我们将在本指南的后面部分介绍这些内容。
我们需要完成的第一项任务是创建一个带有钱包的账户并为其提供资金。我们将使用下面的方便工具自动生成一个新钱包并向其空投 1 SOL。(如果你更喜欢手动操作,也可以使用 Keypair.generate()
和 requestAirdrop()
函数来实现这一点)。
🔑生成一个带有 Devnet SOL 的新钱包
创建新钱包
一旦你成功生成了你的密钥对,你会注意到两个新的常量:secret
和 SIGNER_WALLET
,一个 Keypair。secret
是一个 32 字节的数组,用于生成公钥和私钥。SIGNER_WALLET
是一个 Keypair 实例,用于签署交易(我们已经空投了一些 devnet SOL 来支付 gas 费用)。如果还没有,请确保将其添加到你的代码中,放在其他常量下面。
在你的导入下面,粘贴你的新 secret,并添加:
const secret = [0,...,0]; // 👈 替换为你的 secret
const SIGNER_WALLET = Keypair.fromSecretKey(new Uint8Array(secret));
const DESTINATION_WALLET = Keypair.generate();
//const LOOKUP_TABLE_ADDRESS = new PublicKey(""); // 我们稍后会添加这个
我们定义了两个钱包:SIGNER_WALLET
将向我们的 DESTINATION_WALLET
发送 SOL。我们还添加了一个常量 LOOKUP_TABLE_ADDRESS
,稍后我们将更新它以引用我们的查找表的链上账户 ID。
要在 Solana 上构建,你需要一个 API 端点来连接网络。你可以使用公共节点或部署和管理自己的基础设施;但是,如果你希望获得 8 倍更快的响应时间,可以将繁重的工作交给我们。
了解为什么超过 50% 的 Solana 项目选择 QuickNode 并在此注册一个免费账户。我们将使用 Solana Devnet 节点。
复制 HTTP Provider 链接:
在 app.ts 中,在你的导入语句下,声明你的 RPC 并建立与 Solana 的连接:
const QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';
const SOLANA_CONNECTION = new Connection(QUICKNODE_RPC);
你的环境应该看起来像这样:
好了,让我们开始构建吧!
要使用查找表,你必须使用版本 0 交易。如果你不熟悉如何构建和执行版本化交易,请查看我们的指南,这里。
让我们从创建一个新函数 createAndSendV0Tx 开始,它接受一个 TransactionInstructions 数组,txInstructions:
async function createAndSendV0Tx(txInstructions: TransactionInstruction[]) {
// 步骤 1 - 获取最新的区块哈希
let latestBlockhash = await SOLANA_CONNECTION.getLatestBlockhash('finalized');
console.log(" ✅ - 获取了最新的区块哈希。最后有效高度:", latestBlockhash.lastValidBlockHeight);
// 步骤 2 - 生成交易消息
const messageV0 = new TransactionMessage({
payerKey: SIGNER_WALLET.publicKey,
recentBlockhash: latestBlockhash.blockhash,
instructions: txInstructions
}).compileToV0Message();
console.log(" ✅ - 编译了交易消息");
const transaction = new VersionedTransaction(messageV0);
// 步骤 3 - 使用所需的 `Signers` 签署你的交易
transaction.sign([SIGNER_WALLET]);
console.log(" ✅ - 交易已签署");
// 步骤 4 - 将我们的 v0 交易发送到集群
const txid = await SOLANA_CONNECTION.sendTransaction(transaction, { maxRetries: 5 });
console.log(" ✅ - 交易已发送到网络");
// 步骤 5 - 确认交易
const confirmation = await confirmTransaction(SOLANA_CONNECTION, txid);
if (confirmation.value.err) { throw new Error(" ❌ - 交易未确认。") }
console.log('🎉 交易成功确认!', '\n', `https://explorer.solana.com/tx/${txid}?cluster=devnet`);
}
让我们逐步了解我们的代码:
让我们定义我们在 createAndSendV0Tx 函数中调用的 confirmTransaction
函数。将以下代码添加到你的 app.ts 文件中:
async function confirmTransaction(
connection: Connection,
signature: TransactionSignature,
desiredConfirmationStatus: TransactionConfirmationStatus = 'confirmed',
timeout: number = 30000,
pollInterval: number = 1000,
searchTransactionHistory: boolean = false
): Promise<SignatureStatus> {
const start = Date.now();
while (Date.now() - start < timeout) {
const { value: statuses } = await connection.getSignatureStatuses([signature], { searchTransactionHistory });
if (!statuses || statuses.length === 0) {
throw new Error('Failed to get signature status');
}
const status = statuses[0];
if (status === null) {
// 如果状态为 null,交易尚未被知晓
await new Promise(resolve => setTimeout(resolve, pollInterval));
continue;
}
if (status.err) {
throw new Error(`交易失败: ${JSON.stringify(status.err)}`);
}
if (status.confirmationStatus && status.confirmationStatus === desiredConfirmationStatus) {
return status;
}
if (status.confirmationStatus === 'finalized') {
return status;
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
throw new Error(`交易确认超时,等待了 ${timeout}ms`);
}
这将简单地轮询 Solana 网络以获取交易状态,直到它被确认或达到超时。我们为超时和轮询间隔包含了一些默认值,但你可以根据需要调整它们。
好了!我们都准备好了。让我们添加一些指令。
创建一个新的异步函数 createLookupTable,它将构建我们的交易指令并调用 createAndSendV0Tx:
async function createLookupTable() {
// 步骤 1 - 获取一个查找表地址并创建查找表指令
const [lookupTableInst, lookupTableAddress] =
AddressLookupTableProgram.createLookupTable({
authority: SIGNER_WALLET.publicKey,
payer: SIGNER_WALLET.publicKey,
recentSlot: await SOLANA_CONNECTION.getSlot(),
});
// 步骤 2 - 记录查找表地址
console.log("查找表地址:", lookupTableAddress.toBase58());
// 步骤 3 - 生成交易并将其发送到网络
createAndSendV0Tx([lookupTableInst]);
}
分解我们的代码:
步骤 1:我们通过解构 createLookupTable 方法的结果创建两个变量,lookupTableInst 和 lookupTableAddress。该方法返回创建后表的公钥和一个可以传递到我们的 createAndSendV0Tx 函数中的 TransactionInstruction。
步骤 2:我们记录表的地址(在本练习中稍后我们将需要它)。
步骤 3:最后,我们通过将 lookupTableInst 传递到数组中调用 createAndSendV0Tx 以匹配我们的类型要求。
太棒了!此时,你应该能够运行你的代码并创建一个空的查找表。在你的函数之后,通过添加以下内容来调用它:
createLookupTable();
然后在你的终端中输入:
ts-node app.ts
你应该会在终端中看到你的交易进展,并最终收到一个指向 Solana Explorer 上交易页面的 URL:
我们的查找表账户地址是:3uBhgRWPTPLfvfqxi4M9eVZC8nS1kDG9XPkdHKgG69nw:
干得好!你已经创建了你的第一个查找表。
在我们继续之前,让我们做一些清理工作:
删除你对 createLookupTable() 的调用。我们不再需要它。
还记得我们之前创建的 LOOKUP_TABLE_ADDRESS 常量吗?删除注释反斜杠,并将你的控制台中的查找表地址添加到你的 PublicKey 声明中,如下所示(这是我们的第 6 行):
const LOOKUP_TABLE_ADDRESS = new PublicKey("YOUR_TABLE_ADDRESS_HERE");
// 例如,const LOOKUP_TABLE_ADDRESS = new PublicKey("3uBhgRWPTPLfvfqxi4M9eVZC8nS1kDG9XPkdHKgG69nw");
由于我们已经创建了 createAndSendV0Tx,向查找表添加地址很容易!我们需要做的就是创建一个 TransactionInstruction。创建一个新的异步函数 addAddressesToTable,它使用 AddressLookupTableProgram.extendLookupTable() 方法:
async function addAddressesToTable() {
// 步骤 1 - 创建交易指令
const addAddressesInstruction = AddressLookupTableProgram.extendLookupTable({
payer: SIGNER_WALLET.publicKey,
authority: SIGNER_WALLET.publicKey,
lookupTable: LOOKUP_TABLE_ADDRESS,
addresses: [\
Keypair.generate().publicKey,\
Keypair.generate().publicKey,\
Keypair.generate().publicKey,\
Keypair.generate().publicKey,\
Keypair.generate().publicKey\
],
});
// 步骤 2 - 生成交易并将其发送到网络
await createAndSendV0Tx([addAddressesInstruction]);
console.log(`查找表条目: `,`https://explorer.solana.com/address/${LOOKUP_TABLE_ADDRESS.toString()}/entries?cluster=devnet`)
}
让我们分解一下 extendLookupTable 方法的作用:
我们传递我们的 SIGNER_WALLET 来支付交易费用和任何额外的租金。
我们定义我们的更新权限——在我们的例子中,我们在上面的表创建步骤中将其设置为 SIGNER_WALLET。
我们传递查找表账户地址(我们将其定义为 LOOKUP_TABLE_ADDRESS)。
我们将一个地址数组传递到我们的查找表中。我们将传递一些随机的公钥,但你可以传递任何你喜欢的公钥!该程序的“压缩”支持在一个查找表中存储多达 256 个地址!
让我们还在交易完成后记录一个指向我们查找表条目的链接以便于访问。最后,我们将我们的 TransactionInstruction 传递到 createAndSendV0Tx 中以生成交易并将其发送到网络!
在你的函数之后,通过添加以下内容来调用你的新函数:
addAddressesToTable();
运行你的代码——在终端中,使用键盘上的上箭头键检索之前的命令,ts-node app.ts,然后按 Enter 运行你的代码。
你应该在终端中看到类似的交易流程,并成功获得指向你的交易和你的查找表条目在 Solana Explorer 上的链接。转到查找表条目。你应该会看到你的表存储的所有公钥列表:
干得好!你可以修改你的 addresses 数组并重新运行它以向你的查找表添加额外的公钥。
在继续之前,删除你对 addAddressesToTable();
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!