安曼是什么以及如何利用它来增强Solana本地开发?

  • QuickNode
  • 发布于 2025-01-30 12:11
  • 阅读 15

本文介绍了Amman,一个用于简化Solana开发者体验的本地测试验证器工具,详细介绍了其特点以及如何使用Amman进行本地开发和测试的过程。读者将了解到如何配置Amman,创建、铸造和转移代币,了解Amman Explorer的使用,最后总结了其对Solana开发的重要性。

概述

Amman 是一个 Solana 本地测试验证器的封装,旨在简化开发者的体验。Amman 由 Metaplex 构建和维护,Metaplex 是 Solana 上一个重要的 NFT 标准。Amman 可以减少测试 Solana 程序所需的时间。让我们开始吧!

你将要做的事情

  • 了解 Amman 的概念及其特点
  • 编写一个简单的脚本以测试 Amman 的一些功能
  • 创建一个 Amman 配置文件
  • 测试你的脚本并在 Amman Explorer 中浏览你的交易

你将需要的工具

什么是 Amman?

Amman 是 " A m odern man datory 工具包,用于帮助测试 Solana SDK 库和应用程序在本地运行的验证器" 的缩写。

显著特点:

  • Amman Explorer - 一个基于 Web 的探索工具,与你的本地 Solana 验证器连接,跟踪最近的交易,为账户提供自定义标签,以及反序列化账户数据。
  • Mock storage server - Amman 可用于模拟你 Solana 程序的存储服务器,这对于测试 NFT 相关程序非常有用。
  • 程序和账户加载 - Amman 可用于从本地文件系统或指定的 Solana 集群加载程序和账户。
  • 可共享配置 - Amman 可以配置为在多个项目之间共享相同的验证器和存储服务器,从而节省时间和精力。

如何使用 Amman?

让我们创建一个简单的脚本来测试 Amman 的功能。该脚本将:

  • 创建一个可替代的代币,利用 Amman 的 mock storage server 模拟上传代币元数据
  • 向钱包铸造代币
  • 将代币从发送者转移到接收者
  • 使用 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:用于交易的默认选项——我们将使用 skipPreflightprocessed 承诺以加快测试速度。
  • 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。

Mock Storage 函数

让我们创建一个函数,利用 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);
    }
}

在这里,我们需要做两件事情:

  1. 为接收者创建关联代币账户 (ATA)。我们使用 Umi 库中的 createAssociatedToken 函数。
  2. 使用 Umi 库中的 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('完成。'));

让我们细分一下:

  1. 我们首先创建一个新的 Amman 实例和一个连接实例到我们的本地验证器。
  2. 然后我们调用我们的 setupUmi 函数以设置我们的 Umi 实例。
  3. 然后我们调用我们的 setupAuthority 函数以设置权限账户并空投一些 SOL 到它。我们更新了权限签名者的 Umi 实例。
  4. 我们使用 Umi 库中的 generateSigner 函数创建一个 mintreceiver 签名者。通过这些,我们可以找到权限和接收者账户的 ATA。
  5. 我们使用 amman.addr.addLabel 函数给我们的账户添加标签。这将与 Amman 库中的 genLabeledKeypair 函数类似,但将标签添加到现有账户。通常情况下,我们可能会对指定的密钥对使用相同的方法,但我们使用此方法只是为了显示使用 Amman 库的不同方式。
  6. 然后我们调用我们的 uploadTokenMetadata 函数,将代币元数据上传到 Amman 的 mock storage,并使用其响应创建我们的代币元数据对象。
  7. 我们通过调用 mintTokens 函数创建一个新代币。
  8. 然后我们调用我们的 transferTokens 函数将代币转移到接收者账户。

配置 Amman

在运行脚本之前,我们需要配置 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 进去——我们将在下一步中完成。
  • jsonRpcUrlwebsocketUrl:这些是我们本地验证器的 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 中的交易。

你应该会注意到几个新事物:

  1. 输入 "账户输入" 部分现在是一个标记账户的列表(而不是我们习惯在 Solana Explorer 中看到的原始公钥)。这些应该与我们在脚本中定义的账户标签相匹配:

Account Input

  1. 在屏幕顶部,你应该看到一个 "加载历史" 按钮。单击它将加载我们在脚本中使用的账户的交易历史。你应该看到我们刚刚运行的交易:

Transaction History

  1. 快照可查看和存储账户的状态。这对于调试和测试非常有用。你可以通过单击屏幕右上角的 💾 图标查看快照。你可以存储这些快照,并在稍后加载它们,以返回到之前的状态。🤯

相当酷吧?

总结

Amman 是一个强大的工具集,可帮助 Solana 开发者,使本地开发和测试更加高效。在你继续在 Solana 上开发时,考虑将 Amman 集成到你的工作流程中,以简化开发过程。请查看以下额外资源以获取更多信息:

资源

让我们交流!

你已经在使用 Amman 吗?我们很想听听你的经历。通过 TwitterDiscord 联系我们,告诉我们你的体验、问题或反馈。

我们 ❤️ 反馈!

让我们知道 如果你有任何反馈或新的主题请求。我们很想听到你的声音。

  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。