本文介绍了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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
 
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!