
NFT de Solana: Cómo modificar NFT comprimidos (2023)
NFT de Solana: Por qué se necesitan modificaciones
Como desarrollador en Solana, interactuar con NFT comprimidos puede parecer una tarea difícil. Esto ya no tiene por qué ser así gracias a la posibilidad de modificar o aplicar cambios a los NFT comprimidos mediante el programa Bubblegum.
Cuando se acuña un NFT comprimido, se deben proporcionar pruebas de Merkle junto con la instrucción de transacción para realizar cualquier cambio en ese NFT (por ejemplo, transferirlo o quemarlo). Si no conoces el concepto, una prueba de Merkle es un conjunto de hashes que permite demostrar que una hoja pertenece al árbol.
En esta guía, veremos:
- La importancia de las modificaciones de NFT comprimidos.
- Cómo configurar modificaciones para quemar y transferir NFT comprimidos.
- La importancia de la API Digital Asset Standard (DAS).
NFT de Solana: Requisitos previos
Antes de comenzar, asegúrate de tener:
- Conocimientos básicos de JavaScript/TypeScript.
- Cierta familiaridad con los NFT comprimidos.
- Git instalado.
- npm o yarn.
- Un clon del repositorio de ejemplo, disponible aquí.
NFT de Solana: Configuración del entorno
- Clona nuestro repositorio de ejemplo:
git clone- Ve a la carpeta del proyecto:
cd compression-examples- Instala npm:
npm install- Crea un archivo .env en la raíz:
API_KEY=YOUR_API_KEY
SECRET_KEY=YOUR_WALLET_SECRET_KEYEn él, ingresa tu clave de API de Helius y la clave secreta de la wallet del pagador. Asegúrate de que esta wallet tenga fondos de Solana para financiar las transacciones.
NFT de Solana: Modificaciones
Para realizar modificaciones, debes obtener las pruebas actuales de la instrucción, junto con el propietario, la autoridad y los hashes de compresión actuales del activo. Debes obtener estos datos de la API Digital Asset Standard (DAS), que simplifica las interacciones con activos comprimidos.
La lista actual de modificaciones disponibles mediante Bubblegum es:
- Acuñar
- Transferir
- Quemar
- Delegar, cancelar delegación
- Canjear, cancelar canje
- Descomprimir
- Verificar creador, establecer y verificar creador
- Verificar colección, establecer y verificar colección
Para interactuar con activos comprimidos, debes asegurarte de obtener:
- getAssetProof - Obtiene la prueba para el cálculo.
- getAsset - Obtiene el hash del creador, el hash de datos, el nonce de la hoja y el propietario o delegado actual de la hoja.
- getBubblegumAuthorityPDA - Una dirección derivada del programa a partir del ID del árbol.
En estos ejemplos, veremos cómo quemar y transferir los NFT comprimidos que acuñaremos.
NFT: Transferencia
La modificación de transferencia te permite mover un NFT comprimido de la wallet de un propietario a otra. Tanto las ventas como los listados de NFT comprimidos usan esta instrucción para mostrar las ventas en los marketplaces.
Para configurar esto en nuestro repositorio existente, iremos al archivo utils.ts y revisaremos nuestra función transferAsset.
Tendremos que proporcionar lo siguiente:
- Conexión - Una conexión RPC de Solana.
- Propietario actual - La clave pública del propietario actual del NFT.
- Nuevo propietario del NFT - La clave pública del nuevo propietario.
- ID del activo - Nos permite obtener los detalles actuales del activo específico que proporcionas.
En el siguiente ejemplo de la función transferAsset, puedes ver que primero solicitamos la prueba del activo y configuramos algo llamado proofPath. Esta solicitud a la API DAS devuelve la prueba del activo que quieres transferir y la procesa para incluirla en la instrucción de transacción.
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...
};Es necesario obtener la prueba del activo porque debes proporcionar el estado actual del activo comprimido. Esto es obligatorio para cada transferencia de un NFT comprimido. La procesas en proofPath para incluirla en la instrucción de transacción que veremos en breve.
Ahora que tenemos la ruta de la prueba, podemos usar getAsset en la API DAS para obtener el nonce de la hoja, el delegado de la hoja, el hash de datos y el hash del creador.
Podemos configurarlo así:
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);
);
};Puedes ver que la solicitud getAsset se realiza para obtener el propietario o delegado y el nonce de la hoja. También definimos la autoridad del árbol usando únicamente el ID del árbol, que obtuvimos de nuestra solicitud assetProof.
Ahora que los hemos definido, podemos empezar a crear nuestra instrucción de transacción.
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,
}
);
};Para hacer esto con un activo transferido, deberás proporcionar todas las variables que obtuvimos de getAsset, getAssetProof y el SDK de Metaplex: delegado o propietario de la hoja, hash de datos, raíz y nonce de la hoja.
También notarás que proporcionamos algunos ID de programas. Estas son las claves públicas de los programas de compresión y noop que se importan al principio del archivo. También puedes proporcionarlas como claves públicas.
Ahora que todo está listo, podemos configurar el envío de nuestra instrucción de transacción:
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;
}
};En el código anterior, definimos nuestra transacción mediante tx y agregamos un tipo Transaction para nuestra transferIx de solana/web3.js .
Después, la enviamos proporcionando:
- Conexión de Solana
- Instrucciones de transacción
- Pagador
- Compromiso
Ahora podemos enviar esta transacción ejecutando:
npm run e2eEsta función acuñará un NFT comprimido y su colección, y luego lo transferirá a una wallet especificada.
El ID del activo, ya que lo usaremos para la modificación de quemado.
Esta es la función transferAsset completa:
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: Quemado
Quemar cNFT elimina el NFT del árbol de Merkle. Esto permite diversos casos de uso, como quemarlos para obtener recompensas, eliminar activos no deseados o atender requisitos personalizados. Esto se usó en la acuñación de Tensor, donde se quemaron 10 cNFT para acuñar 1 cNFT nuevo.
Para quemar un NFT comprimido, tendremos que proporcionar lo siguiente:
- Conexión - Una conexión RPC de Solana.
- Propietario actual - La clave pública del propietario actual del NFT.
- ID del activo - Nos permite obtener los detalles actuales del activo específico que proporcionas.
Puedes verlo en nuestro archivo utils.ts, dentro de 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 //
}Esto es igual que transferAsset, ya que usa getAssetProof para obtener la raíz y el ID del árbol. También llamas a getAsset para obtener el hash del creador, el hash de datos, el nonce de la hoja y el propietario actual.
Esto facilita saber qué valores necesitas obtener aquí para modificar tu activo comprimido.
Ahora podemos configurar nuestra instrucción de transacción debajo de este código:
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... //
};Ahora que configuramos el quemado, podemos enviar nuestra transacción de una forma muy similar a la instrucción de transferencia:
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;
}
}Después, la enviamos proporcionando:
- Conexión de Solana
- Instrucciones de transacción
- Pagador
- Compromiso
Una vez que se ejecute esta función, quemará el activo correspondiente al ID que proporcionaste. Esto eliminará los datos del activo del árbol y aplicará una marca burnt=true al activo comprimido. De forma predeterminada, estos activos quemados aparecerán cuando uses la API DAS.
Ahora puedes ejecutar esto en tu terminal:
npm run burn -- --assetId=NFT de Solana: Conclusión
Los NFT comprimidos ofrecen una forma dinámica de interactuar con el ecosistema NFT de Solana. Mediante pruebas de Merkle obtenidas a través de la API DAS e instrucciones de transacción precisas, los desarrolladores pueden modificar estos activos digitales únicos e interactuar con ellos. Al comprender los detalles de las modificaciones de transferencia y quemado, los desarrolladores pueden crear aplicaciones más confiables.
Artículos relacionados
Suscríbete a Helius
Mantente al día con las novedades del desarrollo en Solana y recibe actualizaciones cuando publiquemos


