本文详细介绍了如何在Solana上修改压缩NFT,包括转移和销毁操作。通过使用Bubblegum程序和数字资产标准(DAS)API,开发者可以有效地获取Merkle证明,执行转移和销毁操作。文中提供了相关代码示例,帮助开发者更好地理解和实现这些功能。
6分钟阅读
2023年9月12日
作为 Solana 的开发者,与压缩 NFT 交互似乎是一场艰苦的斗争。通过 Bubblegum 程序 的修改或更改压缩 NFT 的能力,这种情况不再需要存在。
当一个压缩 NFT 被铸造时,必须在交易指令中提供 Merkle 证明,以对该 NFT 进行任何更改(例如,转移、销毁)。对不熟悉的人来说,Merkle 证明是一组可以证明叶子属于树的哈希。
在本指南中,我们将涵盖:
在深入之前,请确保你具备:
代码
git clone
代码
cd compression-examples
代码
npm install
代码
API_KEY=YOUR_API_KEY
SECRET_KEY=YOUR_WALLET_SECRET_KEY
在此输入你的 Helius API 密钥 和用户 钱包密钥。确保该钱包中有 Solana 以资助交易。
修改需要获取当前证明与该指令有关的当前资产所有权、权限和压缩哈希。你需要从 数字资产标准 (DAS) API 获取这些数据,该 API 简化了与压缩资产的交互。
通过Bubblegum可用的当前修改列表包括:
要与压缩资产进行交互,你需确保返回以下内容:
在这些示例中,我们将讨论铸造后的压缩 NFT 的销毁和转移。
转移修改允许你将压缩 NFT 从一个所有者的钱包移动到另一个。压缩 NFT 销售和列表均使用此指令在市场上展示销售。
要在现有仓库中设置此功能,我们将转到 utils.ts
文件,查看我们的 transferAsset 函数。
我们需要传入以下内容:
在下面的示例 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,
}
);
};
要为转移的资产执行此操作,你需要传入所有我们从 getAsset
、getAssetProof
和 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 类型。
然后我们通过传递以下内容来提交它:
我们现在可以通过运行以下命令提交此交易:
代码
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;
}
};
销毁 cNFT 会将 NFT 从 Merkle 树中清除。这允许多种用例,例如为了奖励而销毁、删除垃圾资产或满足自定义要求。这在 Tensor 铸币中使用,其中 10 个 cNFT 被销毁以铸造 1 个新 cNFT。
要销毁压缩 NFT,我们需要传入以下内容:
我们可以在 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;
}
}
然后我们通过传递以下内容来提交它:
一旦运行此函数,它将根据你提供的 ID 销毁资产。这将从树中移除资产数据,并在压缩资产上应用 burnt=true 标志。默认情况下,当你使用 DAS API 时,这些被销毁的资产将返回。
在你的终端中,现在可以运行:
代码
npm run burn -- --assetId=
压缩 NFT 提供了一种动态的方式与 Solana 上的 NFT 生态系统互动。通过通过 DAS 获取的 Merkle 证明和精确的交易指令,开发人员可以修改和与这些独特的数字资产进行交互。通过理解转移和销毁修改的细微差别,开发人员能够构建更可靠的应用程序。
- 原文链接: helius.dev/blog/solana-n...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!