
Solana NFT: изменение сжатых NFT (2023)
Solana NFT: зачем нужны изменения
Разработчикам в Solana взаимодействие со сжатыми NFT может казаться сложной задачей. Теперь это не так: сжатые NFT можно изменять с помощью программы Bubblegum.
При выпуске сжатого NFT в инструкцию транзакции необходимо включать доказательства Меркла, чтобы вносить любые изменения в этот NFT, например передавать или сжигать его. Доказательство Меркла — это набор хешей, подтверждающий принадлежность листа дереву.
В этом руководстве мы рассмотрим:
- Важность изменения сжатых NFT.
- Настройку сжигания и передачи сжатых NFT.
- Значение API Digital Asset Standard (DAS).
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 и секретный ключ кошелька плательщика. Убедитесь, что на кошельке достаточно SOL для оплаты транзакций.
Solana NFT: изменения
Для изменений нужны актуальные доказательства для инструкции, а также текущий владелец актива, полномочия и хеши сжатия. Эти данные необходимо получить через API Digital Asset Standard (DAS), который упрощает взаимодействие со сжатыми активами.
Через Bubblegum сейчас доступны следующие изменения:
- Выпуск
- Передача
- Сжигание
- Делегирование, отмена делегирования
- Погашение, отмена погашения
- Распаковка
- Проверка создателя, назначение и проверка создателя
- Проверка коллекции, назначение и проверка коллекции
Для взаимодействия со сжатыми активами необходимо получить:
- getAssetProof — получает доказательство для вычисления.
- getAsset — получает хеш создателя, хеш данных, одноразовое значение листа и текущего владельца или делегата листа.
- getBubblegumAuthorityPDA — производный адрес программы, созданный на основе ID дерева.
В этих примерах мы рассмотрим сжигание и передачу выпущенных нами сжатых 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(
`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...
};Доказательство актива необходимо, поскольку оно отражает текущее состояние сжатого актива. Оно требуется при каждой передаче сжатого NFT. Вы вычисляете его в proofPath, чтобы затем передать в инструкцию транзакции, которую мы вскоре рассмотрим.
Получив путь доказательства, мы можем использовать getAsset в DAS API, чтобы получить одноразовое значение листа, делегата листа, хеш данных и хеш создателя.
Настроим это следующим образом:
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 возвращает владельца или делегата и одноразовое значение листа. Мы также определяем полномочия дерева, используя его 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: делегата или владельца листа, хеш данных, корень и одноразовое значение листа.
Также мы передаем несколько 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 и добавляем тип Transaction для transferIx из solana/web3.js .
Затем мы отправляем ее, передавая:
- Подключение к 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 из дерева Меркла. Это открывает различные сценарии использования: сжигание ради наград, удаление спам-активов или выполнение особых требований. Такой подход использовался при выпуске Tensor: 10 cNFT сжигались для выпуска одного нового 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 //
}Здесь используется тот же подход, что и в transferAsset: getAssetProof возвращает корень и ID дерева. Вы также вызываете getAsset, чтобы получить хеш создателя, хеш данных, одноразовое значение листа и текущего владельца.
Так легко определить, какие значения нужно получить для изменения сжатого актива.
Теперь под этим кодом можно настроить инструкцию транзакции:
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 предоставляют гибкий способ взаимодействия с экосистемой NFT в Solana. Благодаря доказательствам Меркла, полученным через DAS API, и точным инструкциям транзакций разработчики могут изменять эти уникальные цифровые активы и взаимодействовать с ними. Понимание особенностей передачи и сжигания помогает разработчикам создавать более надежные приложения.
Похожие статьи
Подпишитесь на Helius
Следите за новостями разработки на Solana и получайте уведомления о наших публикациях


