WEB3DEV Español

Cover image for Cómo acuñar NFTs en Solana usando Rust y Metaplex
Banana Labs
Banana Labs

Posted on

Cómo acuñar NFTs en Solana usando Rust y Metaplex

portada

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Finalmente, usa la CLI de Anchor para hacer un proyecto de Anchor con este comando:

anchor init <name-of-your-project>
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"]}
Enter fullscreen mode Exit fullscreen mode

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};
Enter fullscreen mode Exit fullscreen mode

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>,

}
Enter fullscreen mode Exit fullscreen mode

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(())

    }
Enter fullscreen mode Exit fullscreen mode

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,

            },

        ];
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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

),
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

¡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.
Enter fullscreen mode Exit fullscreen mode

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)