
Solana NFT:圧縮NFTを変更する方法(2023年)
Solana NFT:変更が必要な理由
Solana開発者にとって、圧縮NFTの操作は難しく感じられるかもしれません。しかし、Bubblegumプログラムを通じて圧縮NFTを変更できるようになったため、もはやそのような心配はありません。
圧縮NFTをミントした後、そのNFTに変更(転送やバーンなど)を加えるには、トランザクション命令とともにMerkle proofを提供する必要があります。Merkle proofとは、そのリーフがツリーに属していることを証明できるハッシュの集合です。
このガイドでは、以下について説明します。
- 圧縮NFTにおける変更の重要性。
- 圧縮NFTのバーンと転送を設定する方法。
- Digital Asset Standard(DAS)APIの重要性。
Solana NFT:前提条件
始める前に、以下を用意してください。
- JavaScript/TypeScriptの基本的な知識。
- 圧縮NFTに関する基礎知識。
- Gitがインストールされていること。
- npmまたはyarn。
- こちらから入手できるサンプルリポジトリのクローン。
Solana NFT:環境設定
- サンプルリポジトリをクローンします。
git clone- プロジェクトフォルダに移動します。
cd compression-examples- npmをインストールします。
npm install- ルートに**.env**ファイルを作成します。
API_KEY=YOUR_API_KEY
SECRET_KEY=YOUR_WALLET_SECRET_KEYこのファイルにHelius APIキーと、支払者のウォレット秘密鍵を入力します。トランザクションの資金を支払えるよう、このウォレットにSolanaを入れておいてください。
Solana NFT:変更
変更には、命令に使用する最新のproofに加え、アセットの現在の所有者、権限、圧縮ハッシュを取得する必要があります。このデータは、圧縮アセットの操作を効率化するDigital Asset Standard(DAS)APIから取得します。
Bubblegumで現在利用できる変更操作は以下のとおりです。
- ミント
- 転送
- バーン
- 委任、委任の取り消し
- 償還、償還の取り消し
- 解凍
- クリエイターの検証、クリエイターの設定と検証
- コレクションの検証、コレクションの設定と検証
圧縮アセットを操作するには、以下の結果を取得する必要があります。
- getAssetProof - 計算に使用するproofを取得します。
- getAsset - クリエイターハッシュ、データハッシュ、リーフnonce、現在の所有者/リーフデリゲートを取得します。
- getBubblegumAuthorityPDA - ツリーIDから導出されるプログラムアドレスです。
以下の例では、ミントする圧縮NFTのバーンと転送について説明します。
NFT:転送
転送操作を使うと、圧縮NFTを現在の所有者のウォレットから別のウォレットへ移動できます。圧縮NFTの販売と出品では、どちらもこの命令を使用してマーケットプレイスに販売情報を表示します。
既存のリポジトリで設定するために、utils.tsファイルを開き、transferAsset関数を確認します。
以下を渡す必要があります。
- 接続 - Solana RPC接続。
- 現在の所有者 - 現在のNFT所有者の公開鍵。
- NFTの新しい所有者 - 新しい所有者の公開鍵。
- アセットID - 渡した特定のアセットの最新情報を取得するために使用します。
以下のtransferAsset関数の例では、最初にアセットのproofをリクエストし、proofPathと呼ばれるものを設定しています。これはDAS APIへのリクエストで、転送するアセットのproofを返します。そのproofを計算し、トランザクション命令に渡します。
export const transferAsset = async (
connectionWrapper: WrappedConnection,
owner: Keypair,
newOwner: Keypair,
assetId: string
) => {
console.log(
`Transfering asset ${assetId} from ${owner.publicKey.toBase58()} to ${newOwner.publicKey.toBase58()}.
This will depend on indexer api calls to fetch the necessary data.`
);
let assetProof = await connectionWrapper.getAssetProof(assetId);
if (!assetProof?.proof || assetProof.proof.length === 0) {
throw new Error("Proof is empty");
}
let proofPath = assetProof.proof.map((node: string) => ({
pubkey: new PublicKey(node),
isSigner: false,
isWritable: false,
}));
console.log("Successfully got proof path from RPC.");
// Additional code...
};圧縮アセットの現在の状態を取得する必要があるため、アセットのproofを返す処理が必要です。これは、圧縮NFTを転送するたびに必須です。後ほど説明するトランザクション命令に渡せるよう、proofPathで計算します。
proof pathを取得したので、DAS APIのgetAssetを使用して、リーフnonce、リーフデリゲート、データハッシュ、クリエイターハッシュを取得できます。
以下のように設定できます。
export const transferAsset = async (
connectionWrapper: WrappedConnection,
owner: Keypair,
newOwner: Keypair,
assetId: string
) => {
// Previous code //
// Calling getAsset from DAS
const rpcAsset = await connectionWrapper.getAsset(assetId);
console.log(
"Successfully got asset from RPC. Current owner: " +
rpcAsset.ownership.owner
);
if (rpcAsset.ownership.owner !== owner.publicKey.toBase58()) {
throw new Error(
`NFT is not owned by the expected owner. Expected ${owner.publicKey.toBase58()} but got ${
rpcAsset.ownership.owner
}.`
);
}
// Leaf nonce from getAsset call.
const leafNonce = rpcAsset.compression.leaf_id;
// Locating Tree Authority.
const treeAuthority = await getBubblegumAuthorityPDA(
new PublicKey(assetProof.tree_id)
);
// Leaf Owner/Delegate from getAsset call.
const leafDelegate = rpcAsset.ownership.delegate
? new PublicKey(rpcAsset.ownership.delegate)
: new PublicKey(rpcAsset.ownership.owner);
);
};getAssetリクエストで、所有者/デリゲートとリーフnonceを取得していることが分かります。また、ツリーID(assetProofリクエストから取得したもの)を使用するだけで、ツリー権限も定義しています。
これらを定義できたので、トランザクション命令の構築を始めます。
export const transferAsset = async (
connectionWrapper: WrappedConnection,
owner: Keypair,
newOwner: Keypair,
assetId: string
) => {
// Previous code //
// Transfer Instruction.
let transferIx = createTransferInstruction(
{
treeAuthority, // Tree authority
leafOwner: new PublicKey(rpcAsset.ownership.owner), // Current NFT owner
leafDelegate: leafDelegate, // Leaf delegate/owner returned
newLeafOwner: newOwner.publicKey, // New wallet to transfer our NFT.
merkleTree: new PublicKey(assetProof.tree_id), // Merkle tree public key.
logWrapper: SPL_NOOP_PROGRAM_ID, // NOOP program ID.
compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, // Compression Program ID.
anchorRemainingAccounts: proofPath, // Proofs computed to pass in
},
{
root: bufferToArray(bs58.decode(assetProof.root)), // Root from getAssetProof returned
dataHash: bufferToArray(
bs58.decode(rpcAsset.compression.data_hash.trim()) // Data Hash from getAsset
),
creatorHash: bufferToArray(
bs58.decode(rpcAsset.compression.creator_hash.trim()) // Creator Hash from getAsset
),
nonce: leafNonce, // Leaf nonce from getAsset
index: leafNonce,
}
);
};転送するアセットに対してこれを実行するには、getAsset、getAssetProof、Metaplex SDKから取得したすべての変数(リーフデリゲート/所有者、データハッシュ、ルート、リーフnonce)を渡す必要があります。
複数のプログラムIDも渡していることが分かります。これらは、ファイルの先頭でインポートしている圧縮プログラムとnoopプログラムの公開鍵です。代わりに、これらを公開鍵として渡すこともできます。
準備が整ったので、トランザクション命令の送信を設定します。
export const transferAsset = async (
connectionWrapper: WrappedConnection,
owner: Keypair,
newOwner: Keypair,
assetId: string
) => {
// Previous code //
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("Failed to transfer compressed asset", 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(
`Transfering asset ${assetId} from ${owner.publicKey.toBase58()} to ${newOwner.publicKey.toBase58()}.
This will depend on indexer api calls to fetch the necessary data.`
);
let assetProof = await connectionWrapper.getAssetProof(assetId);
if (!assetProof?.proof || assetProof.proof.length === 0) {
throw new Error("Proof is empty");
}
let proofPath = assetProof.proof.map((node: string) => ({
pubkey: new PublicKey(node),
isSigner: false,
isWritable: false,
}));
console.log("Successfully got proof path from RPC.");
const rpcAsset = await connectionWrapper.getAsset(assetId);
console.log(
"Successfully got asset from RPC. Current owner: " +
rpcAsset.ownership.owner
);
if (rpcAsset.ownership.owner !== owner.publicKey.toBase58()) {
throw new Error(
`NFT is not owned by the expected owner. Expected ${owner.publicKey.toBase58()} but got ${
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("Failed to transfer compressed asset", e);
throw e;
}
};NFT:バーン
cNFTをバーンすると、そのNFTがMerkle treeから削除されます。これにより、報酬を得るためのバーン、スパムアセットの削除、独自要件への対応など、さまざまなユースケースが可能になります。Tensorのミントでは、新しい1つのcNFTをミントするために10個の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);
// Remaining code //
}getAssetProofを使用してルートとツリーIDを取得するため、これはtransferAssetと同じです。また、getAssetを呼び出して、クリエイターハッシュ、データハッシュ、リーフnonce、現在の所有者を取得しています。
これにより、圧縮アセットを変更するために、ここでどの値を取得する必要があるかを簡単に把握できます。
次に、このコードの下でトランザクション命令を設定します。
export const burnAsset = async (
connectionWrapper: WrappedConnection,
owner: Keypair,
assetId?: string
) => {
// Previous code //
// Burn transaction instruction //
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,
}
);
// Later code... //
};バーンの設定が完了したので、転送命令とよく似た方法でトランザクションを送信できます。
export const burnAsset = async (
connectionWrapper: WrappedConnection,
owner: Keypair,
assetId?: string
) => {
// Previous code //
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("Failed to burn compressed asset", e);
throw e;
}
}次に、以下を渡して送信します。
- Solana接続
- トランザクション命令
- 支払者
- コミットメント
この関数を実行すると、指定したIDのアセットがバーンされます。アセットデータがツリーから削除され、圧縮アセットにburnt=trueフラグが設定されます。デフォルトでは、DAS APIを使用すると、これらのバーン済みアセットも結果に含まれます。
ターミナルで以下を実行できます。
npm run burn -- --assetId=Solana NFT:まとめ
圧縮NFTは、SolanaのNFTエコシステムと柔軟に連携する手段を提供します。DAS APIから取得したMerkle proofと正確なトランザクション命令を使用することで、開発者はこれらのユニークなデジタルアセットを変更し、操作できます。転送とバーン操作の詳細を理解すれば、より信頼性の高いアプリケーションを構築できます。
関連記事
Heliusを購読
Solana開発の最新情報や新しい記事の公開通知を受け取れます


