本文介绍了Amman,一个用于简化Solana开发者体验的本地测试验证器工具,详细介绍了其特点以及如何使用Amman进行本地开发和测试的过程。读者将了解到如何配置Amman,创建、铸造和转移代币,了解Amman Explorer的使用,最后总结了其对Solana开发的重要性。
Amman 是一个 Solana 本地测试验证器的封装,旨在简化开发者的体验。Amman 由 Metaplex 构建和维护,Metaplex 是 Solana 上一个重要的 NFT 标准。Amman 可以减少测试 Solana 程序所需的时间。让我们开始吧!
Amman 是 " A m odern man datory 工具包,用于帮助测试 Solana SDK 库和应用程序在本地运行的验证器" 的缩写。
显著特点:
让我们创建一个简单的脚本来测试 Amman 的功能。该脚本将:
注意:这将以我们的 可替代代币指南 为基础,使用 Umi。
首先,让我们设置我们的项目:
mkdir amman-test
cd amman-test
npm init -y
npm install @metaplex-foundation/amman @solana/web3.js@1 @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults @metaplex-foundation/mpl-token-metadata @metaplex-foundation/mpl-toolbox @metaplex-foundation/umi-web3js-adapters
我们将使用 npx
进行测试而不是全局安装,但如果你愿意,也可以全局安装 Amman。将以下脚本添加到你的 package.json
:
"scripts": {
"amman:start": "npx amman start"
}
很好!我们准备开始编写我们的脚本了。
在项目根目录中创建一个新的 app.ts
文件。
echo > app.ts
在代码编辑器中打开文件,开始吧!
首先,让我们导入必要的依赖:
import { Amman, LOCALHOST, AMMAN_EXPLORER } from "@metaplex-foundation/amman-client";
import { Connection } from "@solana/web3.js";
import { percentAmount, generateSigner, signerIdentity, TransactionBuilderSendAndConfirmOptions, transactionBuilder, createSignerFromKeypair, Umi, Keypair, Signer, KeypairSigner } from '@metaplex-foundation/umi';
import { TokenStandard, createAndMint, mplTokenMetadata, CreateV1InstructionDataArgs } from '@metaplex-foundation/mpl-token-metadata';
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import { base58 } from "@metaplex-foundation/umi/serializers";
import { fromWeb3JsKeypair, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
import { findAssociatedTokenPda, transferTokens as transferTokensUmi, createAssociatedToken } from '@metaplex-foundation/mpl-toolbox';
让我们定义一些类型和接口以帮助我们管理元数据。在导入的下面添加以下内容:
type TokenMetadata = Pick<CreateV1InstructionDataArgs, 'name' | 'symbol' | 'uri'>;
type MetaplexFile = Readonly<{
buffer: Buffer;
fileName: string;
displayName: string;
uniqueName: string;
contentType: string | null;
extension: string | null;
tags: [];
}>;
type OffchainMetadata = {
name: string;
symbol: string;
image: string;
description: string;
creator: {
name: string;
site: string;
}
}
我们定义了一个 TokenMetadata
类型,用于创建我们的代币。它是 Umi 库中 CreateV1InstructionDataArgs
类型的一个子集。我们还将定义一个 MetaplexFile
类型,用于表示上传到 Amman 的 mock storage 的文件。最后,我们定义了一个 OffchainMetadata
类型,用于存储我们的代币元数据。
接下来,我们定义一些常量以在整个脚本中使用。在导入的下面添加以下内容:
const AIRDROP_AMOUNT = 100; // 100 SOL
const TOKEN_DECIMALS = 5;
const INITIAL_MINT_AMOUNT = 1_000_000 * Math.pow(10, TOKEN_DECIMALS); // 100 万个代币
const TRANSFER_AMOUNT = 100 * Math.pow(10, TOKEN_DECIMALS); // 100 个代币
const STORAGE_ID = "mock-storage";
const DEFAULT_OPTIONS: TransactionBuilderSendAndConfirmOptions = {
send: { skipPreflight: true },
confirm: { commitment: 'processed' },
};
const OFF_CHAIN_METADATA: OffchainMetadata = {
"name": "Fake Bonk",
"symbol": "xBONK",
"image": "https://arweave.net/hQiPZOsRZXGXBJd_82PhVdlM_hACsT_q6wqwf5cSY7I",
"description": "FAKE Bonk Inu 代币",
"creator": {
"name": "QuickNode Guides",
"site": "https://www.quicknode.com/guides/"
}
}
以下是常量的简要概述:
AIRDROP_AMOUNT
:空投到权限/发送者账户的 SOL 数量。TOKEN_DECIMALS
:代币的小数位数(我们将创建一个具有 5 位小数的假 BONK 代币)。INITIAL_MINT_AMOUNT
:铸造的初始代币数量。TRANSFER_AMOUNT
:转移的代币数量。STORAGE_ID
:用于 Amman 的 mock storage 的存储 ID(这需要与我们的 Amman 配置匹配——稍后我们将在本指南中介绍)。DEFAULT_OPTIONS
:用于交易的默认选项——我们将使用 skipPreflight 和 processed 承诺以加快测试速度。OFF_CHAIN_METADATA
:我们将上传到 Amman 的 mock storage 的代币示例离线元数据。现在,让我们定义一些函数来帮助我们设置项目。在导入的下面添加以下内容:
async function setupUmi(): Promise<Umi> {
const connection = new Connection(LOCALHOST);
const umi = createUmi(connection);
umi.use(mplTokenMetadata());
return umi;
}
async function setupAuthority(umi: Umi, amman: Amman, connection: Connection): Promise<[Umi, Keypair]> {
const [_authorityPublicKey, authorityKeypair] = await amman.genLabeledKeypair("Authority");
const authority = fromWeb3JsKeypair(authorityKeypair);
const authoritySigner = createSignerFromKeypair(umi, authority);
umi.use(signerIdentity(authoritySigner));
await airdropSol(umi, authority, amman, connection);
return [umi, authority];
}
async function airdropSol(umi: Umi, authority: Keypair, amman: Amman, connection: Connection): Promise<void> {
try {
await amman.airdrop(connection, toWeb3JsPublicKey(authority.publicKey), AIRDROP_AMOUNT);
console.log(`✅ - 空投 ${AIRDROP_AMOUNT} SOL 到 ${authority.publicKey}`);
} catch (err) {
console.error("❌ - 空投 SOL 时出错:", err);
}
}
在这里,我们定义了三个帮助函数来设置我们的项目:
setupUmi
:此函数初始化 Umi 库并返回一个新的实例。注意,Amman 库有一个方便的 LOCALHOST
常量,可以用于连接到本地验证器。由于我们使用代币元数据,因此我们还必须使用 mplTokenMetadata
函数来启用代币元数据程序。setupAuthority
:此函数设置权限账户,返回具有权限签名者的 Umi 新实例,并返回权限的密钥对。注意我们使用了 Amman 客户端的 genLabeledKeypair
函数。此函数将创建一个新的 Solana 密钥对并为其添加一个标签,该标签将在 Amman Explorer 中可用。airdropSol
:该函数向权限账户空投 SOL。让我们创建一个函数,利用 Amman 的 mock storage 上传我们的代币元数据。在 airdropSol
函数下面添加以下内容:
async function uploadTokenMetadata(amman: Amman, tokenMetadata: OffchainMetadata): Promise<string> {
const storage = amman.createMockStorageDriver(STORAGE_ID, 1);
const file: MetaplexFile = {
buffer: Buffer.from(JSON.stringify(tokenMetadata)),
fileName: "xBONK.json",
displayName: "xBONK.json",
uniqueName: "xBONK.json",
contentType: "application/json",
extension: "json",
tags: [],
}
try {
const uploadResponse = await storage.upload(file);
console.log(`✅ - 成功上传元数据`);
return uploadResponse;
} catch (err) {
console.error("❌ - 上传元数据时出错:", err);
throw err;
}
}
该文件将使用 Amman
实例创建一个新的 mock storage 驱动程序,使用 STORAGE_ID
和一个任意的费用为 1。然后我们创建一个 MetaplexFile
对象(这是 upload
函数所期望的),并将代币元数据上传到存储。最后,我们记录成功消息并返回上传的响应。
接下来,定义一个铸造我们的代币的函数。在 uploadTokenMetadata
函数的下面添加以下内容:
async function mintTokens(umi: Umi, mint: Signer, authority: Keypair, metadata: TokenMetadata): Promise<void> {
try {
const response = await createAndMint(umi, {
mint,
authority: umi.identity,
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
sellerFeeBasisPoints: percentAmount(0),
decimals: TOKEN_DECIMALS,
amount: INITIAL_MINT_AMOUNT,
tokenOwner: authority.publicKey,
tokenStandard: TokenStandard.Fungible,
}).useLegacyVersion().sendAndConfirm(umi, DEFAULT_OPTIONS);
console.log(`✅ - 成功铸造 ${INITIAL_MINT_AMOUNT / Math.pow(10, TOKEN_DECIMALS)} 个代币 (${mint.publicKey})`);
const [signature] = base58.deserialize(response.signature);
console.log(` ${AMMAN_EXPLORER}/#/tx/${signature}`);
} catch (err) {
console.error("❌ - 铸造代币时出错:", err);
}
}
这将简单地使用 Umi 库中的 createAndMint
函数创建一个具有指定元数据的新代币。然后我们记录成功消息和 Amman Explorer 中交易的链接(_注意 AMMAN_EXPLORER
URL 常量来自 amman-client 库_)。
接下来,让我们在 mintTokens
函数的下面定义一个转移我们的代币的函数:
async function transferTokens(umi: Umi, mint: Signer, authority: Keypair, receiver: KeypairSigner): Promise<void> {
const [senderAta] = findAssociatedTokenPda(umi, { mint: mint.publicKey, owner: authority.publicKey });
const [receiverAta] = findAssociatedTokenPda(umi, { mint: mint.publicKey, owner: receiver.publicKey });
const createAtaInstruction = createAssociatedToken(umi, {
payer: umi.identity,
owner: receiver.publicKey,
mint: mint.publicKey,
ata: receiverAta,
});
const transferInstruction = transferTokensUmi(umi, {
source: senderAta,
destination: receiverAta,
amount: TRANSFER_AMOUNT,
});
const transferTransaction = transactionBuilder()
.add(createAtaInstruction)
.add(transferInstruction);
try {
const response = await transferTransaction.useLegacyVersion().sendAndConfirm(umi, DEFAULT_OPTIONS);
if (response.result.value.err) {
throw new Error(JSON.stringify(response.result.value.err));
}
console.log(`✅ - 成功转移 ${TRANSFER_AMOUNT / Math.pow(10, TOKEN_DECIMALS)} 个代币`);
const [signature] = base58.deserialize(response.signature);
console.log(` ${AMMAN_EXPLORER}/#/tx/${signature}`);
} catch (err) {
console.error("❌ - 发送代币时出错:", err);
}
}
在这里,我们需要做两件事情:
createAssociatedToken
函数。transferTokens
函数转移代币(我们将其视为 transferTokensUmi
以避免类型错误)。
然后我们记录成功消息和 Amman Explorer 中交易的链接。很棒!现在我们已经定义了我们的函数,让我们定义我们的 main
函数,将我们的各部分结合在一起。在 transferTokens
函数的下面添加以下内容:
async function main() {
const amman = Amman.instance();
const connection = new Connection(LOCALHOST, 'processed');
let umi = await setupUmi();
const [updatedUmi, authority] = await setupAuthority(umi, amman, connection);
umi = updatedUmi;
const mint = generateSigner(umi);
const receiver = generateSigner(umi);
const [senderAta] = findAssociatedTokenPda(umi, { mint: mint.publicKey, owner: authority.publicKey });
const [receiverAta] = findAssociatedTokenPda(umi, { mint: mint.publicKey, owner: receiver.publicKey });
amman.addr.addLabel("xBONK", toWeb3JsPublicKey(mint.publicKey));
amman.addr.addLabel("Receiver Wallet", toWeb3JsPublicKey(receiver.publicKey));
amman.addr.addLabel("Sender xBONK Account", toWeb3JsPublicKey(senderAta));
amman.addr.addLabel("Receiver xBONK Account", toWeb3JsPublicKey(receiverAta));
const uri = await uploadTokenMetadata(amman, OFF_CHAIN_METADATA);
const metadata: TokenMetadata = {
name: "FakeBONK",
symbol: "xBONK",
uri
};
await mintTokens(umi, mint, authority, metadata);
await transferTokens(umi, mint, authority, receiver);
}
main().then(() => console.log('完成。'));
让我们细分一下:
Amman
实例和一个连接实例到我们的本地验证器。setupUmi
函数以设置我们的 Umi
实例。setupAuthority
函数以设置权限账户并空投一些 SOL 到它。我们更新了权限签名者的 Umi
实例。generateSigner
函数创建一个 mint
和 receiver
签名者。通过这些,我们可以找到权限和接收者账户的 ATA。amman.addr.addLabel
函数给我们的账户添加标签。这将与 Amman 库中的 genLabeledKeypair
函数类似,但将标签添加到现有账户。通常情况下,我们可能会对指定的密钥对使用相同的方法,但我们使用此方法只是为了显示使用 Amman 库的不同方式。uploadTokenMetadata
函数,将代币元数据上传到 Amman 的 mock storage,并使用其响应创建我们的代币元数据对象。mintTokens
函数创建一个新代币。transferTokens
函数将代币转移到接收者账户。在运行脚本之前,我们需要配置 Amman。Amman 有许多配置选项——我们将介绍一些常见的,但请查看 Amman 文档或 Amman 源代码获取更多信息。
在项目根目录中创建一个新的 .ammanrc.js
文件,内容如下:
const { LOCALHOST } = require("@metaplex-foundation/amman");
const path = require('path');
function localDeployPath(programName) {
return path.join(__dirname, `programs`, `${programName}.so`);
}
module.exports = {
validator: {
killRunningValidators: true,
accountsCluster: "https://example.solana-mainnet.quiknode.pro/123456/", // 👈 用你自己的 QuickNode 端点替换
programs: [\
{\
label: 'Metaplex Metadata',\
programId: "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s",\
deployPath: localDeployPath('metadata')\
}\
],
jsonRpcUrl: LOCALHOST,
websocketUrl: "",
commitment: "confirmed",
resetLedger: true,
verifyFees: false,
detached: process.env.CI != null,
},
relay: {
enabled: process.env.CI == null,
killRunningRelay: true,
},
storage: {
enabled: process.env.CI == null,
storageId: "mock-storage",
clearOnStart: true,
},
};
validator
配置是我们指定如何启动和运行 solana-test-validator
的地方:
killRunningValidators
:这将在启动新的验证器之前杀死任何正在运行的验证器。accountsCluster
:这是我们用于获取账户数据并启动验证器的 QuickNode 端点的 URL。例如,如果我们希望程序或代币模拟主网部署,我们可以使用主网端点。如果你没有 QuickNode 端点,可以在 这里 获得一个免费端点。或者,你可以使用 Solana.com 的公共端点。programs
:这是我们希望加载到验证器中的程序数组。在这种情况下,我们正在部署 Metaplex Metadata 程序。你会注意到,我们正在使用 localDeployPath
函数指定程序所在的路径。我们需要创建此目录并将程序 dump
进去——我们将在下一步中完成。jsonRpcUrl
和 websocketUrl
:这些是我们本地验证器的 URL。我们将使用这些连接到我们的本地验证器并与之交互。commitment
:这是我们希望为验证器使用的承诺级别。我们将使用此选项确保验证器与网络保持同步。resetLedger
:这将在启动验证器之前重置分类帐。我们将使用此选项使每次启动验证器时都从创世纪区块开始。verifyFees
:如果为真,则验证器在收取交易费用之前不会被视为完全启动。detached
:这将在后台运行验证器,使 amman
能够在其继续运行时退出。relay
配置指定了我们如何分享数据在验证器和 Amman Explorer 之间。对于本示例,我们将使用默认配置。有关 relay
配置的更多信息,请查看 Amman 源代码。
最后,storage
配置指定了我们希望如何存储 mock storage。我们将创建一个名为 mock-storage
的存储标识符,以匹配我们脚本中的 STORAGE_ID
常量。我们还将将 clearOnStart
标志设置为 true,这将在启动验证器之前清除存储。
正如上面提到的,我们必须从主网克隆 Metaplex Metadata 程序。为此,我们可以在 Solana CLI 中使用 dump
命令。dump
命令将程序账户的可执行字节码导出到文件中,我们可以将其部署到我们的本地网络。我们需要程序 ID。对于 Metaplex Token Metadata,程序 ID 是 metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s
。
在终端中,创建一个名为 programs
的新目录,然后输入以下命令以将程序转储到文件中:
mkdir programs
然后,输入以下命令以将程序转储到文件中:
solana program dump -u m metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s programs/metadata.so
这应该会在 programs
目录中创建一个 metadata.so
文件,这与我们在配置文件中指定的内容相匹配。我们应该可以开始运行脚本了!
通过运行以下命令启动 Amman 验证器:
npm run amman:start
打开一个新的终端并导航到你的项目目录。如果你愿意,可以通过运行以下命令浏览 Amman CLI 工具:
npx amman help
现在,运行你的脚本:
ts-node app.ts
你应该会看到类似于以下内容的输出:
✅ - 空投 100 SOL 到 5L8siRBhjiAE4GJKZmZSSkfCYBSRZXMv44SVuoD888Yt
✅ - 成功上传元数据
✅ - 成功铸造 1000000 个代币 (7nE1GmnMmDKiycFZxTUdf2Eis9KoYZWN6xNapYzcVCu2)
https://amman-explorer.metaplex.com/#/tx/2AQhGq2BxmGFfvVEJ6uKkMYKGmAswWWZ9vg4YHw2iqr1Nd9wGChL9rLJWZTqKRBxZepTMPQutJ6Lqj2ZbRuNKkX
✅ - 成功转移 100 个代币
https://amman-explorer.metaplex.com/#/tx/3X6CAX6YUWZQ4X8AyHK2EuFHZHZ3iS8GrJnCwNvQKsyF8UjWZ6ysRgXCH7Y2q7q3zJNkiNKn1CwS6xEQ5R1YuZfv
完成。
同时,你会注意到,运行本地验证器的终端窗口将显示你的交易的实时日志。你还可以通过点击终端中的链接在 Amman Explorer 上查看你的交易。请点击转移交易的链接以查看 Explorer 中的交易。
你应该会注意到几个新事物:
相当酷吧?
Amman 是一个强大的工具集,可帮助 Solana 开发者,使本地开发和测试更加高效。在你继续在 Solana 上开发时,考虑将 Amman 集成到你的工作流程中,以简化开发过程。请查看以下额外资源以获取更多信息:
你已经在使用 Amman 吗?我们很想听听你的经历。通过 Twitter 或 Discord 联系我们,告诉我们你的体验、问题或反馈。
让我们知道 如果你有任何反馈或新的主题请求。我们很想听到你的声音。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!