Để tiếp tục với series bài viết về Blockchain Solana thì hôm nay mình sẽ giới thiệu và hướng dẫn các bạn Xây dựng Backend API để mint NFT trên Solana Network bằng NodeJS phần 2.
Bài viết sẽ gồm các 2 phần:

  • Phần 1:
    • Thiết kế hệ thống
    • Dựng hệ thống Backend API bằng Nodejs
    • Build và deploy SmartContract để mint NFT
  • Phần 2:
    • Tạo tài khoản ipfs
    • Viết API kết nối SmartContract
    • Code logic thực hiện mint NFT

Trước khi bắt đầu phần 2, các bạn có thể xem lại phần 1 ở đây

Tạo tài khoản ipfs

IPFS là viết tắt của từ Interplanetary File System, một hệ thống tập tin phân tán ngang hàng kết nối tất cả các thiết bị máy tính với nhau. Cụ thể hơn, nó sẽ phân phối dữ liệu được lưu trữ theo hình thức P2P, hay còn gọi là mạng ngang hàng (mạng đồng đẳng).

1ToDDk6JefmSJH8ouJYSQEzpxc_UQpu1c

Cách hoạt động của IPFS: Đầu tiên mọi dữ liệu sẽ được mã hoá và được lưu dưới dạng mã hash (còn gọi là đối tượng IPFS). Cách thức hoạt động của IPFS sẽ tương tự như BitTorrent, đồng nghĩa với mỗi máy tính tham gia trong mạng lưới của nó sẽ đảm nhận cả việc download lẫn upload dữ liệu mà không cần có sự có mặt của một máy chủ trung tâm. Tổng quan, cách hoạt động của IPFS sẽ có 2 phần chính:

  • Xác định tệp có địa chỉ nội dung (giá trị hash của tệp đó).
  • Tìm dữ liệu được lưu trữ và tải xuống: khi bạn có đoạn hash của file hay trang cần tải, mạng sẽ tìm và connect tới máy tốt nhất để tải dữ liệu xuống cho bạn.

1uhTnQ7hHwFzHj9l4vl6GtnObh2nYLPaK

Các bạn đăng ký tài khoản và tạo project tại đây: https://infura.io/register. Sau khi tạo project thành công, có thể truy cập trang quản lý:

1jvISZHh9cIDHt7wZ640nw2PLvzl-Ol_C

Sau khi có tài khoản, các bạn tạo mới project để lấy các thông tin key phục vụ việc kết nối để upload ảnh khi mint NFT:

1qyml2hIApIcKcqBqPaK42jgaSSUbJ8iJ

Sau khi tạo project, sẽ có thông tin của PROJECT ID, API KEY SECRET và IPFS API ENDPOINT:

1zT9bBg2LHyfFM0B9a-AmpJHCk4jitoZu

Viết API kết nối SmartContract và thực hiện mint NFT

Sau khi đã có ProgramId của smart-contract, chúng ta sẽ viết 1 API ở backend NodeJS để mint NFT. Về vấn đề thêm route, validate, authenticate cho API, mình sẽ viết trong source code ở: https://github.com/gmo-vietnamlab/nft-certificate-backend. Trong bài viết này, mình chỉ tập trung vào phần logic API.

Bước 2: Copy file idl
Copy file idl được tạo ở phần 1 vào folder: vnlab-nft-certificate-backend/src/target/idl/sol_mint_nft.json

Bước 3: Viết code controller

Tạo file vnlab-nft-certificate-backend/src/controllers/mint-nft-sol.controller.js và thêm code sau:


const catchAsync = require('../utils/catchAsync');
const anchor = require("@project-serum/anchor");
const ipfsClient = require("ipfs-http-client");

const {
  TOKEN_PROGRAM_ID,
  MINT_SIZE,
  createAssociatedTokenAccountInstruction,
  getAssociatedTokenAddress,
  createInitializeMintInstruction,
} = require("@solana/spl-token");

const TOKEN_METADATA_PROGRAM_ID = new anchor.web3.PublicKey(
  "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
);
const nftName = "Vnlab(2022): Block chain Q3";
const nftDescription = "This is test nft.";
const nftSymbol = "Q3Nft";
const nftImageUrl = "https://blog.vietnamlab.vn/content/images/1jxWbRXhEbuaZmIFhuPEoBR17O5YbN3_N.png";

async function ipfs_metadata_upload() {
  const projectId = '2Eqxxxxx'; // change to PROJECT ID on infura.io
  const projectSecret = '5246xxxxxxxxx'; // change to API KEY SECRET on infura.io
  const auth =
    'Basic ' + Buffer.from(projectId + ':' + projectSecret).toString('base64');

  const ipfs = ipfsClient.create({
    host: "ipfs.infura.io",
    port: 5001,
    protocol: "https",
    headers: {
      authorization: auth,
    },
  });

  const nftMetadata = {
    name: nftName,
    symbol: nftSymbol,
    description: nftDescription,
    image: nftImageUrl,
    attributes: [
      {
        trait_type: "size",
        value: "very big"
      },
      {
        trait_type: "live in",
        value: "land"
      },
      {
        trait_type: "food",
        value: "glass"
      }
    ]
  };
  const nftMetadataIpfs = await ipfs.add(JSON.stringify(nftMetadata));
  if (nftMetadataIpfs == null) {
    return '';
  } else {
    return nftMetadataIpfs.path;
  }
}

const mintNftSol = catchAsync(async (req, res) => {

  /////// UPLOAD METADATA TO IPFS
  var metadataUri = await ipfs_metadata_upload();
  if (metadataUri == '') {
    console.log("IPFS metadata upload failed!");
    return;
  }
  metadataUri = `https://infura-ipfs.io/ipfs/${metadataUri}`;
  console.log("IPFS metadata uri: ", metadataUri);

  /////// CONNECT SMART-CONTRACT
  // Use a provider from value ANCHOR_PROVIDER_URL in .env
  const provider = anchor.AnchorProvider.env();
  // Configure the client to use the cluster.
  anchor.setProvider(provider);

  // Read the generated IDL.
  const idl = JSON.parse(
    require("fs").readFileSync("./src/target/idl/sol_mint_nft.json", "utf8")
  );

  //Address of the deployed program
  const programId = new anchor.web3.PublicKey("94hKFk7nLNyAYTxipCpiHuVt7ADmFBom2vjRFw3bcU7W"); // ProgramId of smart-contract (get from part 1)

  //Generate the program client from IDL
  const program = new anchor.Program(idl, programId);

  console.log("Program Id: ", program.programId.toBase58());
  console.log('Mint Size: ', MINT_SIZE);
  const lamports = await program.provider.connection.getMinimumBalanceForRentExemption(MINT_SIZE);
  console.log("Mint Account Lamports: ", lamports);

  const getMetadata = async (mint) => {
    return (await anchor.web3.PublicKey.findProgramAddress(
      [
        Buffer.from("metadata"),
        TOKEN_METADATA_PROGRAM_ID.toBuffer(),
        mint.toBuffer(),
      ],
      TOKEN_METADATA_PROGRAM_ID
    ))[0];
  };

  const mintKey = anchor.web3.Keypair.generate();

  const nftTokenAccount = await getAssociatedTokenAddress(
    mintKey.publicKey,
    provider.wallet.publicKey
  );
  console.log("NFT Account: ", nftTokenAccount.toBase58());

  // create storage account
  const mint_tx = new anchor.web3.Transaction().add(
    anchor.web3.SystemProgram.createAccount({
      fromPubkey: provider.wallet.publicKey,
      newAccountPubkey: mintKey.publicKey,
      space: MINT_SIZE,
      programId: TOKEN_PROGRAM_ID,
      lamports,
    }),
    createInitializeMintInstruction(
      mintKey.publicKey,
      0,
      provider.wallet.publicKey,
      provider.wallet.publicKey,
    ),
    createAssociatedTokenAccountInstruction(
      provider.wallet.publicKey,
      nftTokenAccount,
      provider.wallet.publicKey,
      mintKey.publicKey
    )
  );
  program.provider.connection._confirmTransactionInitialTimeout = 120000;
  const response = await program.provider.sendAndConfirm(mint_tx, [mintKey]);
  console.log("Mint key: ", mintKey.publicKey.toString());
  console.log("User: ", provider.wallet.publicKey.toString());

  const metadataAddress = await getMetadata(mintKey.publicKey);
  console.log("Metadata address: ", metadataAddress.toBase58());

  const tx = await program.rpc.mintNft(
    mintKey.publicKey,
    nftName,
    nftSymbol,
    metadataUri,
    {
      accounts: {
        mintAuthority: provider.wallet.publicKey,
        mint: mintKey.publicKey,
        tokenAccount: nftTokenAccount,
        tokenProgram: TOKEN_PROGRAM_ID,
        metadata: metadataAddress,
        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
        payer: provider.wallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
      }
    }
  );
  console.log("Your transaction signature", tx);

  res.send({ 'status' : 'ok', 'account' : program.programId.toBase58() });
});

module.exports = {
  mintNftSol,
};

Sau khi lưu lại, chúng ta có thể sử dụng postman để gọi API theo url: http://localhost:3000/v1/mint-nft-sol/create

1TDc5cHNYjh4pFMXzJNd8IObFFdijc3o4

IPFS metadata uri:  https://infura-ipfs.io/ipfs/QmQpXmbXLLc71YsB2n6f22cMmp5eSSs2QimzwVyCsgPTCD
Program Id:  94hKFk7nLNyAYTxipCpiHuVt7ADmFBom2vjRFw3bcU7W
Mint Size:  82
Mint Account Lamports:  1461600
NFT Account:  23PkbNRrAPBuMAhiEEmSSCdHtcz3dVLSjQgCHS1mRKgu
Mint key:  7pQFvu3KmY7Vmai3rjyY16pZt7B1dMtYCg1C7UkFq5FW
User:  FGKyx9SUbaxa6XQXLcqQPbq1FmCY9KMBGNUek1isSQEG
Metadata address:  EH2vBNJNcG6YSKw8Y2gtMMdWp8U3FWGc6J7GKwJmgkRM
Your transaction signature 3yPcmUf9KePi1yb9j6Leizb4BHYnSDoShBRxbUgdJdTdFG8oJnmR5rhRVcK6otS7EtBdUCiLVy2Lfkzbw9FnoD85

Kiểm tra thông tin NFT vừa được mint trên Solana

Sau khi mint NFT thành công, các bạn có thể kiểm tra thông tin NFT ở đây:

1ZR_oP4W_I2_o4CA9Kb7g7CK3FqcS582S

1ToZmCumEHwRK7EgBf9R-c1549HlK8hN9

Tổng kết

Như vậy mình đã hướng dẫn các bạn xây dựng hệ thống Backend API để mint NFT. Các bạn có thể tham khảo mã nguồn của bài viết này ở đây:

Trong bài viết có sử dụng open source và một số tài liệu tham khảo:

Cảm ơn các bạn đã đọc và theo dõi series bài viết của nhóm block-chain. Rất mong nhận được góp ý của các bạn để mình có thể làm tốt hơn ở những bài viết tiếp theo.