如何修改Solana上的压缩NFT(2023)

  • Helius
  • 发布于 2023-09-14 23:29
  • 阅读 10

本文详细介绍了如何在Solana上修改压缩NFT,包括转移和销毁操作。通过使用Bubblegum程序和数字资产标准(DAS)API,开发者可以有效地获取Merkle证明,执行转移和销毁操作。文中提供了相关代码示例,帮助开发者更好地理解和实现这些功能。

Solana NFT: 修改压缩 NFT(2023)

6分钟阅读

2023年9月12日

Solana NFT: 为什么需要修改

作为 Solana 的开发者,与压缩 NFT 交互似乎是一场艰苦的斗争。通过 Bubblegum 程序 的修改或更改压缩 NFT 的能力,这种情况不再需要存在。

当一个压缩 NFT 被铸造时,必须在交易指令中提供 Merkle 证明,以对该 NFT 进行任何更改(例如,转移、销毁)。对不熟悉的人来说,Merkle 证明是一组可以证明叶子属于树的哈希。

在本指南中,我们将涵盖:

  • 使用压缩 NFT 进行修改的重要性。
  • 设置针对压缩 NFT 的销毁和转移修改。
  • 详细介绍数字资产标准 (DAS) API 的重要性。

Solana NFT: 前提条件

在深入之前,请确保你具备:

  • 对 JavaScript/TypeScript 的基本理解。
  • 对压缩 NFT 有一定的了解。
  • 安装了 Git。
  • 有 npm 或 yarn。
  • 克隆了示例仓库,链接可在 这里 找到。

Solana NFT: 环境设置

  1. 克隆我们的示例仓库:

代码

git clone
  1. 进入项目文件夹:

代码

cd compression-examples
  1. 安装 npm:

代码

npm install
  1. 在根目录创建 .env 文件:

代码

API_KEY=YOUR_API_KEY
SECRET_KEY=YOUR_WALLET_SECRET_KEY

在此输入你的 Helius API 密钥 和用户 钱包密钥。确保该钱包中有 Solana 以资助交易。

Solana NFT: 修改

修改需要获取当前证明与该指令有关的当前资产所有权、权限和压缩哈希。你需要从 数字资产标准 (DAS) API 获取这些数据,该 API 简化了与压缩资产的交互。

通过Bubblegum可用的当前修改列表包括:

  • 铸造
  • 转移
  • 销毁
  • 委托,取消委托
  • 兑换,取消兑换
  • 解压
  • 验证创作者,设置并验证创作者
  • 验证集合,设置并验证集合

要与压缩资产进行交互,你需确保返回以下内容:

在这些示例中,我们将讨论铸造后的压缩 NFT 的销毁和转移。

NFT: 转移

转移修改允许你将压缩 NFT 从一个所有者的钱包移动到另一个。压缩 NFT 销售和列表均使用此指令在市场上展示销售。

要在现有仓库中设置此功能,我们将转到 utils.ts 文件,查看我们的 transferAsset 函数。

我们需要传入以下内容:

  • 连接 - 一个 Solana RPC 连接。
  • 当前拥有者 - 当前 NFT 拥有者的公钥。
  • 新拥有者的 NFT - 新拥有者的公钥。
  • 资产ID - 允许我们获取你传入的特定资产的当前详细信息。

在下面的示例 transferAsset 函数中,我们最初请求资产证明并设置名为 proofPath的东西。这是 DAS API 上的请求,返回你想要转移的资产的证明,你正在计算以传递到交易指令中。

代码

export const transferAsset = async (
  connectionWrapper: WrappedConnection,
  owner: Keypair,
  newOwner: Keypair,
  assetId: string
) => {
  console.log(
    `正在将资产 ${assetId} 从 ${owner.publicKey.toBase58()} 转移到 ${newOwner.publicKey.toBase58()}.
    这将依赖于索引器 API 调用以获取必要数据。`
  );
  let assetProof = await connectionWrapper.getAssetProof(assetId);
  if (!assetProof?.proof || assetProof.proof.length === 0) {
    throw new Error("证明为空");
  }
  let proofPath = assetProof.proof.map((node: string) => ({
    pubkey: new PublicKey(node),
    isSigner: false,
    isWritable: false,
  }));
  console.log("成功从 RPC 获取证明路径。");

// 其他代码...

};

返回资产证明是必要的,因为你需要返回压缩资产的当前状态。这是每次转移压缩 NFT 需要的。你在 proofPath 中计算这些,以便将其传递到稍后看到的交易指令中。

现在我们有了证明路径,可以使用 DAS API 的 getAsset 来返回我们的叶子随机数、叶子委托、数据哈希和创作者哈希。

我们可以在下面设置这个:

代码

export const transferAsset = async (
  connectionWrapper: WrappedConnection,
  owner: Keypair,
  newOwner: Keypair,
  assetId: string
) => {

    // 之前的代码 //

    // 调用 DAS 的 getAsset
  const rpcAsset = await connectionWrapper.getAsset(assetId);
  console.log(
    "成功从 RPC 获取资产。当前拥有者: " +
      rpcAsset.ownership.owner
  );
  if (rpcAsset.ownership.owner !== owner.publicKey.toBase58()) {
    throw new Error(
      `NFT 并不属于预期的拥有者。预期 ${owner.publicKey.toBase58()} 但得到 ${rpcAsset.ownership.owner}。`
    );
  }
// 从 getAsset 调用中获取的叶子随机数。
  const leafNonce = rpcAsset.compression.leaf_id;
// 定位树权限。
  const treeAuthority = await getBubblegumAuthorityPDA(
    new PublicKey(assetProof.tree_id)
  );
// 从 getAsset 调用中获取的叶子拥有者/委托。
  const leafDelegate = rpcAsset.ownership.delegate
    ? new PublicKey(rpcAsset.ownership.delegate)
    : new PublicKey(rpcAsset.ownership.owner);
};

你可以看到,getAsset 请求是为了返回所有者/委托和叶子随机数。我们还通过使用树 ID(我们从 assetProof 请求中返回的)来定义我们的树权限。

现在我们定义好这些后,可以开始构建我们的交易指令。

代码

export const transferAsset = async (
  connectionWrapper: WrappedConnection,
  owner: Keypair,
  newOwner: Keypair,
  assetId: string
) => {

    // 之前的代码 //

    // 转移指令。
  let transferIx = createTransferInstruction(
    {
      treeAuthority, // 树权限
      leafOwner: new PublicKey(rpcAsset.ownership.owner), // 当前 NFT 拥有者
      leafDelegate: leafDelegate, // 返回的叶子委托/拥有者
      newLeafOwner: newOwner.publicKey, // 转移 NFT 的新钱包。
      merkleTree: new PublicKey(assetproof.tree_id), // Merkle 树公钥。
      logWrapper: SPL_NOOP_PROGRAM_ID, // NOOP程序ID。
      compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, // 压缩程序ID。
      anchorRemainingAccounts: proofPath, // 计算的证明传入
    },
    {
      root: bufferToArray(bs58.decode(assetProof.root)), // 从 getAssetProof 返回的根
      dataHash: bufferToArray(
        bs58.decode(rpcAsset.compression.data_hash.trim()) // 从 getAsset 返回的数据哈希
      ),
      creatorHash: bufferToArray(
        bs58.decode(rpcAsset.compression.creator_hash.trim()) // 从 getAsset 返回的创作者哈希
      ),
      nonce: leafNonce, // 从 getAsset 返回的叶子随机数
      index: leafNonce,
    }
  );
};

要为转移的资产执行此操作,你需要传入所有我们从 getAssetgetAssetProof 和 Metaplex SDK 返还的变量:叶子委托/拥有者、数据哈希、根和叶子随机数。

你还会注意到,我们传入了一些程序 ID。这些是引入在文件顶部的压缩和 noop 程序的公钥。你也可以选择将其作为公钥传递。

现在一切准备就绪,我们可以设置发送我们的交易指令:

代码

export const transferAsset = async (
  connectionWrapper: WrappedConnection,
  owner: Keypair,
  newOwner: Keypair,
  assetId: string
) => {

 // 之前的代码 //

  const tx = new Transaction().add(transferIx);
  tx.feePayer = owner.publicKey;
  try {
    const sig = await sendAndConfirmTransaction(
      connectionWrapper,
      tx,
      [owner],
      {
        commitment: "confirmed",
        skipPreflight: true,
      }
    );
    return sig;
  } catch (e) {
    console.error("转移压缩资产失败", e);
    throw e;
  }
};

在上面的代码中,我们使用 tx 来定义我们的交易,并为 solana/web3.js 的 transferIx 添加一个 Transaction 类型。

然后我们通过传递以下内容来提交它:

  • Solana 连接
  • 交易指令
  • 付款人
  • 承诺

我们现在可以通过运行以下命令提交此交易:

代码

npm run e2e

此函数将铸造一个压缩 NFT 及其集合,然后将其转移到指定的钱包。

注意:

资产 ID 因为我们将用于销毁修改。

以下是完整的 transferAsset 函数:

代码

export const transferAsset = async (
  connectionWrapper: WrappedConnection,
  owner: Keypair,
  newOwner: Keypair,
  assetId: string
) => {
  console.log(
    `正在将资产 ${assetId} 从 ${owner.publicKey.toBase58()} 转移到 ${newOwner.publicKey.toBase58()}.
    这将依赖于索引器 API 调用以获取必要数据。`
  );
  let assetProof = await connectionWrapper.getAssetProof(assetId);
  if (!assetProof?.proof || assetProof.proof.length === 0) {
    throw new Error("证明为空");
  }
  let proofPath = assetProof.proof.map((node: string) => ({
    pubkey: new PublicKey(node),
    isSigner: false,
    isWritable: false,
  }));
  console.log("成功从 RPC 获取证明路径。");

  const rpcAsset = await connectionWrapper.getAsset(assetId);
  console.log(
    "成功从 RPC 获取资产。当前拥有者: " +
      rpcAsset.ownership.owner
  );
  if (rpcAsset.ownership.owner !== owner.publicKey.toBase58()) {
    throw new Error(
      `NFT 并不属于预期的拥有者。预期 ${owner.publicKey.toBase58()} 但得到 ${rpcAsset.ownership.owner}。`
    );
  }
  const leafNonce = rpcAsset.compression.leaf_id;
  const treeAuthority = await getBubblegumAuthorityPDA(
    new PublicKey(assetProof.tree_id)
  );
  const leafDelegate = rpcAsset.ownership.delegate
    ? new PublicKey(rpcAsset.ownership.delegate)
    : new PublicKey(rpcAsset.ownership.owner);
  let transferIx = createTransferInstruction(
    {
      treeAuthority,
      leafOwner: new PublicKey(rpcAsset.ownership.owner),
      leafDelegate: leafDelegate,
      newLeafOwner: newOwner.publicKey,
      merkleTree: new PublicKey(assetProof.tree_id),
      logWrapper: SPL_NOOP_PROGRAM_ID,
      compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
      anchorRemainingAccounts: proofPath,
    },
    {
      root: bufferToArray(bs58.decode(assetProof.root)),
      dataHash: bufferToArray(
        bs58.decode(rpcAsset.compression.data_hash.trim())
      ),
      creatorHash: bufferToArray(
        bs58.decode(rpcAsset.compression.creator_hash.trim())
      ),
      nonce: leafNonce,
      index: leafNonce,
    }
  );
  const tx = new Transaction().add(transferIx);
  tx.feePayer = owner.publicKey;
  try {
    const sig = await sendAndConfirmTransaction(
      connectionWrapper,
      tx,
      [owner],
      {
        commitment: "confirmed",
        skipPreflight: true,
      }
    );
    return sig;
  } catch (e) {
    console.error("转移压缩资产失败", e);
    throw e;
  }
};

NFT: 销毁

销毁 cNFT 会将 NFT 从 Merkle 树中清除。这允许多种用例,例如为了奖励而销毁、删除垃圾资产或满足自定义要求。这在 Tensor 铸币中使用,其中 10 个 cNFT 被销毁以铸造 1 个新 cNFT。

要销毁压缩 NFT,我们需要传入以下内容:

  • 连接 - 一个 Solana RPC 连接。
  • 当前拥有者 - 当前 NFT 拥有者的公钥。
  • 资产 ID - 允许我们获取你传入的特定资产的当前详细信息。

我们可以在 utils.ts 中看到这一点,位于 burnAsset

代码

export const burnAsset = async (
  connectionWrapper: WrappedConnection,
  owner: Keypair,
  assetId?: string
) => {
  let assetProof = await connectionWrapper.getAssetProof(assetId);
  const rpcAsset = await connectionWrapper.getAsset(assetId);
  const leafNonce = rpcAsset.compression.leaf_id;
  let proofPath = assetProof.proof.map((node: string) => ({
    pubkey: new PublicKey(node),
    isSigner: false,
    isWritable: false,
}));
const treeAuthority = await getBubblegumAuthorityPDA(
    new PublicKey(assetProof.tree_id)
  );
  const leafDelegate = rpcAsset.ownership.delegate
    ? new PublicKey(rpcAsset.ownership.delegate)
    : new PublicKey(rpcAsset.ownership.owner);

// 其余代码 //

}

这与 transferAsset 相同,因为它使用 getAssetProof 来返回根和树 ID。你还调用 getAsset 来返回创作者哈希、数据哈希、叶子随机数和当前所有者。

这使你可以轻松了解要返回的值,以便修改你的压缩资产。

现在,我们可以在上述代码下设置我们的交易指令:

代码

export const burnAsset = async (
  connectionWrapper: WrappedConnection,
  owner: Keypair,
  assetId?: string
) => {

    // 之前的代码 //

// 销毁交易指令 //
  const burnIx = createBurnInstruction(
    {
      treeAuthority,
      leafOwner: new PublicKey(rpcAsset.ownership.owner),
      leafDelegate,
      merkleTree: new PublicKey(assetProof.tree_id),
      logWrapper: SPL_NOOP_PROGRAM_ID,
      compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
      anchorRemainingAccounts: proofPath,

    },
    {
      root: bufferToArray(bs58.decode(assetProof.root)),
      dataHash: bufferToArray(
        bs58.decode(rpcAsset.compression.data_hash.trim())
      ),
      creatorHash: bufferToArray(
        bs58.decode(rpcAsset.compression.creator_hash.trim())
      ),
      nonce: leafNonce,
      index: leafNonce,
    }
  );
 // 后续代码... //
};

现在我们已针对销毁进行了设置,我们可以以非常类似的方式提交我们的交易,与转移指令相同:

代码

export const burnAsset = async (
  connectionWrapper: WrappedConnection,
  owner: Keypair,
  assetId?: string
) => {

// 之前的代码 //

const tx = new Transaction().add(burnIx);
  tx.feePayer = owner.publicKey;
  try {
    const sig = await sendAndConfirmTransaction(
      connectionWrapper,
      tx,
      [owner],
      {
        commitment: "confirmed",
        skipPreflight: true,
      }
    );
    return sig;
  } catch (e) {
    console.error("销毁压缩资产失败", e);
    throw e;
  }
}

然后我们通过传递以下内容来提交它:

  • Solana 连接
  • 交易指令
  • 付款人
  • 承诺

一旦运行此函数,它将根据你提供的 ID 销毁资产。这将从树中移除资产数据,并在压缩资产上应用 burnt=true 标志。默认情况下,当你使用 DAS API 时,这些被销毁的资产将返回。

在你的终端中,现在可以运行:

代码

npm run burn -- --assetId=

Solana NFTs: 结论

压缩 NFT 提供了一种动态的方式与 Solana 上的 NFT 生态系统互动。通过通过 DAS 获取的 Merkle 证明和精确的交易指令,开发人员可以修改和与这些独特的数字资产进行交互。通过理解转移和销毁修改的细微差别,开发人员能够构建更可靠的应用程序。

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

0 条评论

请先 登录 后评论
Helius
Helius
https://www.helius.dev/