Non Fungible Tokens (NFTs)

Làm thế nào để tạo một NFT

Để tạo một NFT bạn phải:

  1. Đăng tải ảnh lên IPFS ví như Arweave
  2. Đăng tải json metadata lên IPFS ví như Arweave
  3. Gọi metaplex để tạo một account cho NFT

Đăng tải lên Arweave

Press </> button to view full source
import fs from "fs";
import Arweave from "arweave";

(async () => {
  const arweave = Arweave.init({
    host: "arweave.net",
    port: 443,
    protocol: "https",
    timeout: 20000,
    logging: false,
  });

  // Upload image to Arweave
  const data = fs.readFileSync("./code/nfts/arweave-upload/lowres-dog.png");

  const transaction = await arweave.createTransaction({
    data: data,
  });

  transaction.addTag("Content-Type", "image/png");

  const wallet = JSON.parse(fs.readFileSync("wallet.json", "utf-8"))
  
  await arweave.transactions.sign(transaction, wallet);

  const response = await arweave.transactions.post(transaction);
  console.log(response);

  const id = transaction.id;
  const imageUrl = id ? `https://arweave.net/${id}` : undefined;
  console.log("imageUrl", imageUrl);

  // Upload metadata to Arweave

  const metadata = {
    name: "Custom NFT #1",
    symbol: "CNFT",
    description: "A description about my custom NFT #1",
    seller_fee_basis_points: 500,
    external_url: "https://www.customnft.com/",
    attributes: [
      {
        trait_type: "NFT type",
        value: "Custom",
      },
    ],
    collection: {
      name: "Test Collection",
      family: "Custom NFTs",
    },
    properties: {
      files: [
        {
          uri: imageUrl,
          type: "image/png",
        },
      ],
      category: "image",
      maxSupply: 0,
      creators: [
        {
          address: "CBBUMHRmbVUck99mTCip5sHP16kzGj3QTYB8K3XxwmQx",
          share: 100,
        },
      ],
    },
    image: imageUrl,
  };

  const metadataRequest = JSON.stringify(metadata);

  const metadataTransaction = await arweave.createTransaction({
    data: metadataRequest,
  });

  metadataTransaction.addTag("Content-Type", "application/json");

  await arweave.transactions.sign(metadataTransaction, wallet);

  console.log("metadata txid", metadataTransaction.id);

  console.log(await arweave.transactions.post(metadataTransaction));
})();
from arweave.arweave_lib import Wallet, Transaction, API_URL
import json

# Load your arweave wallet
your_ar_wallet = Wallet('wallet.json')

with open('./code/nfts/arweave-upload/lowres-dog.png', 'rb') as f:
    img_in_bytes = f.read()

# Upload image to Arweave
transaction = Transaction(your_ar_wallet, data=img_in_bytes)
transaction.add_tag('Content-Type', 'image/png')
transaction.sign()
transaction.send()

image_url = API_URL+"/"+transaction.id

# Define metadata
metadata = {
    "name": "Custom NFT #1",
    "symbol": "CNFT",
    "description": "A description about my custom NFT #1",
    "seller_fee_basis_points": 500,
    "external_url": "https://www.customnft.com/",
    "attributes": [
        {
            "trait_type": "NFT type",
            "value": "Custom"
        }
    ],
    "collection": {
        "name": "Test Collection",
        "family": "Custom NFTs",
    },
    "properties": {
        "files": [
            {
                "uri": image_url,
                "type": "image/png",
            },
        ],
        "category": "image",
        "maxSupply": 0,
        "creators": [
            {
                "address": "CBBUMHRmbVUck99mTCip5sHP16kzGj3QTYB8K3XxwmQx",
                "share": 100,
            },
        ],
    },
    "image": image_url,
}

# Upload metadata to Arweave
meta_transaction = Transaction(your_ar_wallet, data=json.dumps(metadata))
meta_transaction.add_tag('Content-Type', 'text/html')
meta_transaction.sign()
meta_transaction.send()

metadata_url = API_URL+"/"+meta_transaction.id

print(metadata_url)

Đúc NFT

Nếu bạn đã đăng tải ảnh và metadata, bạn có thể đúc NFT với đoạn mã bên dưới.

Press </> button to view full source
import { Metaplex, keypairIdentity } from "@metaplex-foundation/js";
import {
  Connection,
  clusterApiUrl,
  Keypair,
  LAMPORTS_PER_SOL,
} from "@solana/web3.js";
import dotenv from "dotenv";

dotenv.config();

(async () => {
  const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
  const keypair = Keypair.fromSecretKey(
    Buffer.from(JSON.parse(process.env.SOLANA_KEYPAIR!.toString()))
  );

  const metaplex = new Metaplex(connection);
  metaplex.use(keypairIdentity(keypair));

  const feePayerAirdropSignature = await connection.requestAirdrop(
    keypair.publicKey,
    LAMPORTS_PER_SOL
  );
  await connection.confirmTransaction(feePayerAirdropSignature);

  const mintNFTResponse = await metaplex.nfts().create({
    uri: "https://ffaaqinzhkt4ukhbohixfliubnvpjgyedi3f2iccrq4efh3s.arweave.net/KUAIIbk6p8oo4XHRcq0U__C2r0mwQaNl0gQow4Qp9yk",
    maxSupply: 1,
  });

  console.log(mintNFTResponse);
})();

Lưu ý

Bạn không thể đúc một NFT với một địa chỉ khác ngoài ví của bạn. Nếu bạn gặp phải vấn đề địa chỉ người tạo, bạn nên đảm bảo rằng metadata liệt kê bạn là người tạo hợp lệ.

Làm thế nào để truy vấn NFT Metadata

Các Metaplex NFT có metadata được lưu trên Arweave. Để có thể truy vấn được Arweave metadata, bạn phải thông qua Metaplex PDA và giải mã dữ liệu trong account đó.

Press </> button to view full source
import { Metaplex, keypairIdentity } from "@metaplex-foundation/js";
import { Connection, clusterApiUrl, Keypair, PublicKey } from "@solana/web3.js";

(async () => {
  const connection = new Connection(clusterApiUrl("mainnet-beta"));
  const keypair = Keypair.generate();

  const metaplex = new Metaplex(connection);
  metaplex.use(keypairIdentity(keypair));

  const mintAddress = new PublicKey(
    "Ay1U9DWphDgc7hq58Yj1yHabt91zTzvV2YJbAWkPNbaK"
  );

  const nft = await metaplex.nfts().findByMint({ mintAddress });

  console.log(nft.json);
  /*
  {
    name: 'SMB #139',
    symbol: 'SMB',
    description: 'SMB is a collection of 5000 randomly generated 24x24 pixels NFTs on the Solana Blockchain. Each SolanaMonkey is unique and comes with different type and attributes varying in rarity.',
    seller_fee_basis_points: 600,
    image: 'https://arweave.net/tZrNpbFUizSoFnyTqP4n2e1Tf7WvP3siUwFWKMMid_Q',
    external_url: 'https://solanamonkey.business/',
    collection: { name: 'SMB Gen2', family: 'SMB' },
    attributes: [
      { trait_type: 'Attributes Count', value: 2 },
      { trait_type: 'Type', value: 'Solana' },
      { trait_type: 'Clothes', value: 'Orange Shirt' },
      { trait_type: 'Ears', value: 'None' },
      { trait_type: 'Mouth', value: 'None' },
      { trait_type: 'Eyes', value: 'Cool Glasses' },
      { trait_type: 'Hat', value: 'None' }
    ],
    properties: {
      files: [ [Object], [Object] ],
      category: 'image',
      creators: [ [Object] ]
    }
  }
  */
})();

Làm thế nào để truy vấn chủ sở hữu của một NFTs

Nếu bạn có địa chỉ mint của một NFT, bạn có thể tìm được chủ sở hữu hiện tại của nó bằng truy vấn token account lớn nhất của địa chỉ mint đó.

Nhớ rằng vì tổng cung của NFT là 1, và chúng không thể chia nhỏ hơn, nên chỉ có duy nhất một token account sẽ chứa token đó ở mọi lúc. Tất cả các token account khác sẽ có số dư là 0.

Một khi token account lớn nhất được xác định, chúng ta có thể truy vấn chủ sỡ hữu của nó.

Press </> button to view full source
import { Connection, PublicKey } from "@solana/web3.js";

(async () => {
  const connection = new Connection("https://api.mainnet-beta.solana.com");
  const tokenMint = "9ARngHhVaCtH5JFieRdSS5Y8cdZk2TMF4tfGSWFB9iSK";

  const largestAccounts = await connection.getTokenLargestAccounts(
    new PublicKey(tokenMint)
  );
  const largestAccountInfo = await connection.getParsedAccountInfo(
    largestAccounts.value[0].address
  );
  console.log(largestAccountInfo.value.data.parsed.info.owner);
  /*
    PublicKey {
        _bn: <BN: 6ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9>
    }
     */
})();

Làm thế nào để truy vấn địa chỉ mint của NFT

Nếu bạn biết khoá công khai của Candy Machine, bạn có thể truy vấn được tất cả địa chỉ NFT mint được sinh ra từ Candy Machine đó bằng cách sử dụng đoạn mã bên dưới. Chú ý rằng chúng ta có thể sử dụng bộ lọc memcmp bên dưới bởi vì, trong v1, người tạo đầu tiên luôn là địa chỉ của Candy Machine.

Candy Machine V1

Press </> button to view full source
import { Connection, clusterApiUrl, PublicKey } from "@solana/web3.js";
import bs58 from "bs58";

const connection = new Connection(clusterApiUrl("mainnet-beta"));
const MAX_NAME_LENGTH = 32;
const MAX_URI_LENGTH = 200;
const MAX_SYMBOL_LENGTH = 10;
const MAX_CREATOR_LEN = 32 + 1 + 1;
const MAX_CREATOR_LIMIT = 5;
const MAX_DATA_SIZE =
  4 +
  MAX_NAME_LENGTH +
  4 +
  MAX_SYMBOL_LENGTH +
  4 +
  MAX_URI_LENGTH +
  2 +
  1 +
  4 +
  MAX_CREATOR_LIMIT * MAX_CREATOR_LEN;
const MAX_METADATA_LEN = 1 + 32 + 32 + MAX_DATA_SIZE + 1 + 1 + 9 + 172;
const CREATOR_ARRAY_START =
  1 +
  32 +
  32 +
  4 +
  MAX_NAME_LENGTH +
  4 +
  MAX_URI_LENGTH +
  4 +
  MAX_SYMBOL_LENGTH +
  2 +
  1 +
  4;

const TOKEN_METADATA_PROGRAM = new PublicKey(
  "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
);
const candyMachineId = new PublicKey("ENTER_YOUR_CANDY_MACHINE_ID_HERE");

const getMintAddresses = async (firstCreatorAddress: PublicKey) => {
  const metadataAccounts = await connection.getProgramAccounts(
    TOKEN_METADATA_PROGRAM,
    {
      // The mint address is located at byte 33 and lasts for 32 bytes.
      dataSlice: { offset: 33, length: 32 },

      filters: [
        // Only get Metadata accounts.
        { dataSize: MAX_METADATA_LEN },

        // Filter using the first creator.
        {
          memcmp: {
            offset: CREATOR_ARRAY_START,
            bytes: firstCreatorAddress.toBase58(),
          },
        },
      ],
    }
  );

  return metadataAccounts.map((metadataAccountInfo) =>
    bs58.encode(metadataAccountInfo.account.data)
  );
};

getMintAddresses(candyMachineId);

Candy Machine V2

Nếu bạn đang sử dụng Candy Machine v2, bạn sẽ cần truy cập vào địa chỉ "Candy Machine Creator" của nó. Địa chỉ này đơn giản là một PDA với seeds là candy_machine và địa chỉ Candy Machine v2. Một khi bạn có địa chỉ người tạo, bạn có thể sử dụng nó tương tự như cách mà chúng ta đã làm ở v1.

Press </> button to view full source
import { Connection, clusterApiUrl, PublicKey } from "@solana/web3.js";
import bs58 from "bs58";

const connection = new Connection(clusterApiUrl("mainnet-beta"));
const MAX_NAME_LENGTH = 32;
const MAX_URI_LENGTH = 200;
const MAX_SYMBOL_LENGTH = 10;
const MAX_CREATOR_LEN = 32 + 1 + 1;
const MAX_CREATOR_LIMIT = 5;
const MAX_DATA_SIZE =
  4 +
  MAX_NAME_LENGTH +
  4 +
  MAX_SYMBOL_LENGTH +
  4 +
  MAX_URI_LENGTH +
  2 +
  1 +
  4 +
  MAX_CREATOR_LIMIT * MAX_CREATOR_LEN;
const MAX_METADATA_LEN = 1 + 32 + 32 + MAX_DATA_SIZE + 1 + 1 + 9 + 172;
const CREATOR_ARRAY_START =
  1 +
  32 +
  32 +
  4 +
  MAX_NAME_LENGTH +
  4 +
  MAX_URI_LENGTH +
  4 +
  MAX_SYMBOL_LENGTH +
  2 +
  1 +
  4;

const TOKEN_METADATA_PROGRAM = new PublicKey(
  "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
);
const CANDY_MACHINE_V2_PROGRAM = new PublicKey(
  "cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ"
);
const candyMachineId = new PublicKey("ENTER_YOUR_CANDY_MACHINE_ID_HERE");

const getMintAddresses = async (firstCreatorAddress: PublicKey) => {
  const metadataAccounts = await connection.getProgramAccounts(
    TOKEN_METADATA_PROGRAM,
    {
      // The mint address is located at byte 33 and lasts for 32 bytes.
      dataSlice: { offset: 33, length: 32 },

      filters: [
        // Only get Metadata accounts.
        { dataSize: MAX_METADATA_LEN },

        // Filter using the first creator.
        {
          memcmp: {
            offset: CREATOR_ARRAY_START,
            bytes: firstCreatorAddress.toBase58(),
          },
        },
      ],
    }
  );

  return metadataAccounts.map((metadataAccountInfo) =>
    bs58.encode(metadataAccountInfo.account.data)
  );
};

const getCandyMachineCreator = async (
  candyMachine: PublicKey
): Promise<[PublicKey, number]> =>
  PublicKey.findProgramAddress(
    [Buffer.from("candy_machine"), candyMachine.toBuffer()],
    CANDY_MACHINE_V2_PROGRAM
  );

(async () => {
  const candyMachineCreator = await getCandyMachineCreator(candyMachineId);
  getMintAddresses(candyMachineCreator[0]);
})();

Làm thế nào để truy vấn tất cả NFT từ một ví?

Khi truy vấn tất cả NFT từ một ví, bạn sẽ cần đọc tất cả token account và sau đó suy ra từng NFT một. Tất cả có thể thực hiện chỉ bằng hàm findDataByOwneropen in new window từ thư viện Metaplex JS.

Press </> button to view full source
import { Metaplex, keypairIdentity } from "@metaplex-foundation/js";
import { Connection, clusterApiUrl, Keypair, PublicKey } from "@solana/web3.js";

(async () => {
  const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
  const keypair = Keypair.generate();

  const metaplex = new Metaplex(connection);
  metaplex.use(keypairIdentity(keypair));

  const owner = new PublicKey("2R4bHmSBHkHAskerTHE6GE1Fxbn31kaD5gHqpsPySVd7");
  const allNFTs = await metaplex.nfts().findAllByOwner({ owner });

  console.log(allNFTs);
})();
Last Updated:
Contributors: tuphan-dn