En este tutorial, aprenderás cómo acuñar un NFT en Solana escribiendo un contrato inteligente Rust y utilizando el Programa de Metadatos de Token de Metaplex.
Bienvenidos lectores. Este es el comienzo de una nueva serie de publicaciones de blog sobre el desarrollo en Solana y, en este, aprenderás a escribir un contrato personalizado para acuñar tu NFT en tan solo cuatro pasos.
Algunos consejos generales sobre el desarrollo en Solana
En el desarrollo en Solana, te enfrentarás a muchos errores y bugs específicamente extraños, y puede ser bastante difícil y frustrante corregirlos, ya que el ecosistema de desarrolladores de Solana no es tan grande como el ecosistema de desarrollo de Eth. Pero no te preocupes. Cuando te quedes atascado, simplemente busca en el lugar correcto la solución.
Durante mi desarrollo, estaba constantemente resolviendo mis dudas en los servidores de discord de Anchor, Metaplex y Superteam y examinando otros repositorios de código en GitHub y en la propia biblioteca del programa Metaplex.
Descripción general del proyecto
Las herramientas que usaremos para esto son:
- Herramientas CLI de Solana — El conjunto oficial de herramientas CLI de Solana
- Framework Anchor — Un framework de alto nivel para el desarrollo de programas Solana. Esto es obligatorio, a menos que seas un desarrollador a nivel de dios; en ese caso, no estás leyendo este blog. ja, ja, ja.
- Solana / web3.js — Una versión de Solana de web3.js
- Solana / spl-token — Un paquete para trabajar con tokens spl
- Mocha — Una herramienta de prueba JS
Introducción
Preparar el trabajo
Usa la CLI para configurar tu red como devnet con el siguiente comando:
solana config set --url devnet
Para confirmar si funcionó, verifica la salida después de ingresar el cmd:
Config File: /Users/anoushkkharangate/.config/solana/cli/config.yml
RPC URL: https://api.devnet.solana.com
WebSocket URL: wss://api.devnet.solana.com/ (computed)
Keypair Path: /Users/anoushkkharangate/.config/solana/id.json
Commitment: confirmed
A continuación, si aún no lo has hecho, configura una billetera de sistema de archivos utilizando esta guía, Documentos de la billetera Solana, y también agrega algunos sol devnet
usando el comando:
solana airdrop 1
Finalmente, usa la CLI de Anchor para hacer un proyecto de Anchor con este comando:
anchor init <name-of-your-project>
Asegúrate que Anchor.toml
también esté configurado como devnet.
[features]
seeds = false
[programs.devnet]
metaplex_anchor_nft = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[registry]
url = "https://anchor.projectserum.com"
[provider]
cluster = "devnet"
wallet = "/Users/<user-name>/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
Eso es todo. ¡Estás listo para lo más difícil!
Paso 1. Importar las dependencias
En tu proyecto, debería haber una carpeta llamada programs
. Ve a programs/<nombre-de-tu-proyecto>/Cargo.toml
, y añade estas dependencias. Asegúrate de usar la versión 0.24.2
y pueda usar avm para cambiarla.
[dependencies]
anchor-lang = "0.24.2"
anchor-spl = "0.24.2"
mpl-token-metadata = {version = "1.2.7", features = ["no-entrypoint"]}
Anchor retiró todas las versiones anteriores a 0.24.2 debido a una vulnerabilidad de seguridad, así que asegúrate de usar exactamente esa.
Luego ve al archivo en src lib.rs
e importa esto:
use anchor_lang::prelude::*;
use anchor_lang::solana_program::program::invoke;
use anchor_spl::token;
use anchor_spl::token::{MintTo, Token};
use mpl_token_metadata::instruction::{create_master_edition_v3, create_metadata_accounts_v2};
Genial. ¡Ahora podemos escribir la función de acuñación!
Paso 2. Escribiendo la estructura de la función de acuñación
Primero, crearemos la estructura de cuentas para la función de mint
#[derive(Accounts)]
pub struct MintNFT<'info> {
#[account(mut)]
pub mint_authority: Signer<'info>,
/// CHECK:Isso não é perigoso porque não lemos ou escrevemos nesta conta
#[account(mut)]
pub mint: UncheckedAccount<'info>,
// #[account(mut)]
pub token_program: Program<'info, Token>,
/// CHECK: Isso não é perigoso porque não lemos ou escrevemos nesta conta
#[account(mut)]
pub metadata: UncheckedAccount<'info>,
/// CHECK: Esto no es peligroso porque no leemos ni escribimos en esta cuenta.
#[account(mut)]
pub token_account: UncheckedAccount<'info>,
/// CHECK: Esto no es peligroso porque no leemos ni escribimos en esta cuenta.
pub token_metadata_program: UncheckedAccount<'info>,
/// CHECK: Esto no es peligroso porque no leemos ni escribimos en esta cuenta.
#[account(mut)]
pub payer: AccountInfo<'info>,
pub system_program: Program<'info, System>,
/// CHECK: Esto no es peligroso porque no leemos ni escribimos en esta cuenta.
pub rent: AccountInfo<'info>,
/// CHECK: Esto no es peligroso porque no leemos ni escribimos en esta cuenta.
#[account(mut)]
pub master_edition: UncheckedAccount<'info>,
}
No te preocupes por las cuentas no controladas, ya que las pasaremos al programa Metaplex, que las verificará.
Para usar cuentas no verificadas en Anchor, necesitas agregar este comentario encima de cada cuenta:
/// CHECK: Esto no es peligroso porque no leemos ni escribimos en esta cuenta
Paso 3. La función de acuñación
Vamos a hacer una función que use la estructura que acabamos de crear para acuñar el token:
pub fn mint_nft(
ctx: Context<MintNFT>,
creator_key: Pubkey,
uri: String,
title: String,
) -> Result<()> {
msg!("Initializing Mint NFT");
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.payer.to_account_info(),
};
msg!("CPI Accounts Assigned");
let cpi_program = ctx.accounts.token_program.to_account_info();
msg!("CPI Program Assigned");
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
msg!("CPI Context Assigned");
token::mint_to(cpi_ctx, 1)?;
msg!("Token Minted !!!");
let account_info = vec![
ctx.accounts.metadata.to_account_info(),
ctx.accounts.mint.to_account_info(),
ctx.accounts.mint_authority.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.token_metadata_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.rent.to_account_info(),
];
msg!("Account Info Assigned");
let creator = vec![
mpl_token_metadata::state::Creator {
address: creator_key,
verified: false,
share: 100,
},
mpl_token_metadata::state::Creator {
address: ctx.accounts.mint_authority.key(),
verified: false,
share: 0,
},
];
msg!("Creator Assigned");
let symbol = std::string::ToString::to_string("symb");
invoke(
&create_metadata_accounts_v2(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.metadata.key(),
ctx.accounts.mint.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.payer.key(),
ctx.accounts.payer.key(),
title,
symbol,
uri,
Some(creator),
1,
true,
false,
None,
None,
),
account_info.as_slice(),
)?;
msg!("Metadata Account Created !!!");
let master_edition_infos = vec![
ctx.accounts.master_edition.to_account_info(),
ctx.accounts.mint.to_account_info(),
ctx.accounts.mint_authority.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.metadata.to_account_info(),
ctx.accounts.token_metadata_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.rent.to_account_info(),
];
msg!("Master Edition Account Infos Assigned");
invoke(
&create_master_edition_v3(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.master_edition.key(),
ctx.accounts.mint.key(),
ctx.accounts.payer.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.metadata.key(),
ctx.accounts.payer.key(),
Some(0),
),
master_edition_infos.as_slice(),
)?;
msg!("Master Edition Nft Minted !!!");
Ok(())
}
Si deseas depurar tu programa, es mejor usar msg!()
para registrar el valor que deseas verificar. Acepta cadenas de texto, por lo que necesitarás usar std::string::ToString
para la conversión. Tus registros aparecerán en la terminal o en .anchor/program-logs/<program-id>
Entonces, algunas cosas hasta aquí…
El array creator
necesita tener a la persona que está acuñando los NFTs como parte de él, pero puedes definir la acción como 0, por lo que realmente no importa. Aquí está el código:
let creator = vec![
mpl_token_metadata::state::Creator {
address: creator_key,
verified: false,
share: 100,
},
mpl_token_metadata::state::Creator {
address: ctx.accounts.mint_authority.key(),
verified: false,
share: 0,
},
];
No implementé colecciones, ya que no están en el alcance de esta guía, pero puedes hacerlo usando:
mpl_token_metadata :: instrucción :: set_and_verify_collection
En cuanto a por qué establecí el suministro máximo en 0 aquí, es que en Metaplex, si el token es único, debes establecer su oferta máxima como cero, puesto que la oferta total — reclamada (1 – 1) 1 es igual a 0:
&create_master_edition_v3(
ctx.accounts.token_metadata_program.key(),
ctx.accounts.master_edition.key(),
ctx.accounts.mint.key(),
ctx.accounts.payer.key(),
ctx.accounts.mint_authority.key(),
ctx.accounts.metadata.key(),
ctx.accounts.payer.key(),
Some(0), // max supply 0)
Después de escribir la función, ejecuta anchor build && anchor deploy
y deberías ver el ID del programa desplegado
pega este ID del programa en tu Anchor.toml
y en lib.rs
donde veas este ID predeterminado Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS
Paso 4. Llamando a la función de acuñación
Antes de hacer cualquier cosa, asegúrate de haber importado @solana/web3.js
y @solana/spl-token
. Dentro de tests/<test-file>.ts
añade estas importaciones y constantes:
import {
TOKEN_PROGRAM_ID,
createAssociatedTokenAccountInstruction,
getAssociatedTokenAddress,
createInitializeMintInstruction,
MINT_SIZE,
} from "@solana/spl-token";
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
const { PublicKey, SystemProgram } = anchor.web3; q
const TOKEN_METADATA_PROGRAM_ID = new anchor.web3.PublicKey(
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
);
const lamports: number =
await program.provider.connection.getMinimumBalanceForRentExemption(
MINT_SIZE
);
const getMetadata = async (
mint: anchor.web3.PublicKey
): Promise<anchor.web3.PublicKey> => {
return (
await anchor.web3.PublicKey.findProgramAddress(
[
Buffer.from("metadata"),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
mint.toBuffer(),
],
TOKEN_METADATA_PROGRAM_ID
)
)[0];
};
const getMasterEdition = async (
mint: anchor.web3.PublicKey
): Promise<anchor.web3.PublicKey> => {
return (
await anchor.web3.PublicKey.findProgramAddress(
[
Buffer.from("metadata"),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
mint.toBuffer(),
Buffer.from("edition"),
],
TOKEN_METADATA_PROGRAM_ID
)
)[0];
};
const mintKey: anchor.web3.Keypair = anchor.web3.Keypair.generate();
Ahora vamos a hacer el token y la cuenta de token asociada, como se muestra a continuación:
const NftTokenAccount = await getAssociatedTokenAddress(
mintKey.publicKey,
program.provider.wallet.publicKey
);
console.log("NFT Account: ", NftTokenAccount.toBase58());
const mint_tx = new anchor.web3.Transaction().add(
anchor.web3.SystemProgram.createAccount({
fromPubkey: program.provider.wallet.publicKey,
newAccountPubkey: mintKey.publicKey,
space: MINT_SIZE,
programId: TOKEN_PROGRAM_ID,
lamports,
}),
createInitializeMintInstruction(
mintKey.publicKey,
0,
program.provider.wallet.publicKey,
program.provider.wallet.publicKey
),
createAssociatedTokenAccountInstruction(
program.provider.wallet.publicKey,
NftTokenAccount,
program.provider.wallet.publicKey,
mintKey.publicKey
)
);
const res = await program.provider.send(mint_tx, [mintKey]);
console.log(
await program.provider.connection.getParsedAccountInfo(mintKey.publicKey)
);
console.log("Account: ", res);
console.log("Mint key: ", mintKey.publicKey.toString());
console.log("User: ", program.provider.wallet.publicKey.toString());
const metadataAddress = await getMetadata(mintKey.publicKey);
const masterEdition = await getMasterEdition(mintKey.publicKey);
console.log("Metadata address: ", metadataAddress.toBase58());
console.log("MasterEdition: ", masterEdition.toBase58());
Nota: la autorización para acuñar y congelar debe ser la misma, de lo contrario no funcionará.
createInitializeMintInstruction( mintKey.publicKey, 0, program.provider.wallet.publicKey,// mint auth program.provider.wallet.publicKey // freeze auth
),
Ahora, llama a la función de acuñación y pasa todos los datos y cuentas
const tx = await program.rpc.mintNft(
mintKey.publicKey,
"https://arweave.net/y5e5DJsiwH0s_ayfMwYk-SnrZtVZzHLQDSTZ5dNRUHA",
"NFT Title",
{
accounts: {
mintAuthority: program.provider.wallet.publicKey,
mint: mintKey.publicKey,
tokenAccount: NftTokenAccount,
tokenProgram: TOKEN_PROGRAM_ID,
metadata: metadataAddress,
tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
payer: program.provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
masterEdition: masterEdition,
},
}
);
console.log("Your transaction signature", tx);
¡Eso es todo! Ahora solo ejecuta la prueba anchor y podrás acuñar tu NFT.
Account: 4swRFMNovHCkXY3gDgAGBXZwpfFuVyxWpWsgXqbYvoZG1M63nZHxyPRm7KTqAjSdTpHn2ivyPr6jQfxeLsB6a1nX
Mint key: DehGx61vZPYNaMWm9KYdP91UYXXLu1XKoc2CCu3NZFNb
User: 7CtWnYdTNBb3P9eViqSZKUekjcKnMcaasSMC7NbTVKuE
Metadata address: 7ut8YMzGqZAXvRDro8jLKkPnUccdeQxsfzNv1hjzc3Bo
MasterEdition: Au76v2ZDnWSLj23TCu9NRVEYWrbVUq6DAGNnCuALaN6o
Your transaction signature KwEst87H3dZ5GwQ5CDL1JtiRKwcXJKNzyvQShaTLiGxz4HQGsDA7EW6rrhqwbJ2TqQFRWzZFvhfBU1CpyYH7WhH
✔ Is initialized! (6950ms)
1 passing (7s)
✨ Done in 9.22s.
Si recibes algún error de programa específico con un valor hexadecimal como 0x1, convierte el valor hexadecimal en texto sin formato y ve al github de metaplex y busca usando el navegador por la aparición número + 1 de la palabra “error(“
Puedes ver el NFT aquí:
https://solscan.io/token/DehGx61vZPYNaMWm9KYdP91UYXXLu1XKoc2CCu3NZFNb?cluster=devnet
Resumen
Espero que esta guía haya sido útil para todos los nerds de Solana por ahí. Cuando intenté acuñar un NFT, estaba arrancándome el cabello, pero lentamente comenzó a tener sentido cuando algunas otras personas que disfrutan de las cosas difíciles me lo explicaron. Afortunadamente, lo hice mucho más fácil para ti.
Aquí está el GitHub para este proyecto:
https://github.com/anoushk1234/metaplex-anchor-nft
Puedes seguirme en mi Twitter y Github. Hasta la próxima, ¡sigue buscando lo difícil!
Muchas gracias a Pratik Saria y 0xDeep por ayudarme a entender cómo funcionan los NFTs de Solana y Anchor. Si no fuera por ellos, todavía estaría intentando averiguarlo.
Este artículo es una traducción realizada por @bananlabs. Puedes encontrar el artículo original aquí
Discussion (0)