非同质化代币 (NFTs)

如何创建一个NFT

要创建一个 NFT,你需要:

  1. 将图像上传到像 Arweave 这样的 IPFS 网络上。
  2. 将 JSON 元数据上传到像 Arweave 这样的 IPFS 网络上。
  3. 调用 Metaplex 创建一个用于该 NFT 的账户。

上传到 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)

铸造(Mint)该 NFT

如果你已经上传了图像和元数据,您可以使用以下代码铸造(Mint)该 NFT。

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

注意

你不能使用与你钱包不同的创作者信息来铸造(Mint) NFT。如果遇到创作者的问题,请确保你的元数据中将你列为创作者。

如何获取 NFT 元数据

Metaplex 的 NFT 元数据存储在 Arweave 上。为了获取 Arweave 的元数据,您需要获取 Metaplex PDA(程序派生账户)并对账户数据进行解码。

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] ]
    }
  }
  */
})();

如何获取NFT的所有者

如果你拥有 NFT 的铸币密钥,你可以通过查看该铸币密钥对应的最大代币账户来找到其当前所有者。

请记住,NFT 的供应量为 1,它们是不可分割的,这意味着在任何时刻只有一个代币账户持有该代币,而其他所有与该铸币密钥相关的代币账户的余额都为 0。

一旦确定了最大代币账户,我们可以获取它的所有者。

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>
    }
     */
})();

如何获取 NFT 的铸币地址

如果你知道Candy Machine的公钥,你可以使用以下代码获取从该Candy Machine生成的所有 NFT 铸币地址的列表。请注意,我们可以使用以下的 memcmp 过滤器,因为在 v1 版本中,第一个创作者的地址总是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

如果你正在使用Candy Machine v2,你首先需要访问其 "Candy Machine Creator" 地址,该地址是一个简单的 PDA,使用candy_machine和Candy Machine v2 地址作为种子生成。一旦你获得了创建者地址,你可以像对待 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]);
})();

如何从钱包获取所有 NFT?

当从钱包获取所有 NFT 时,你需要获取所有的代币账户,然后解析出其中的 NFT。你可以使用 Metaplex JS 库中的 findDataByOwneropen in new window 方法来完成这个过程。

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

Candy Machine v2

Metaplex JS SDK 现在支持通过代码创建和更新Candy Machine v2。它使开发者能够与糖果机v2 程序进行交互,创建、更新和删除Candy Machine,并从中铸造(Mint) NFT。

如何创建Candy Machine

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

const createCandyMachine = async () => {
  const connection = new Connection(
    "https://api.devnet.solana.com/",
    "confirmed"
  );
  const payer = Keypair.generate();

  // request airdrop
  const airdropSignature = await connection.requestAirdrop(
    payer.publicKey,
    2 * LAMPORTS_PER_SOL
  );

  console.log(`Airdrop signature - ${airdropSignature}`);

  // creating metaplex instance with payer as the authority
  const metaplex = Metaplex.make(connection).use(keypairIdentity(payer));

  // creating a candy machine
  const { candyMachine } = await metaplex.candyMachinesV2().create({
    sellerFeeBasisPoints: 5, // 0.05% royalties
    price: sol(0.0001), // 0.0001 SOL
    itemsAvailable: toBigNumber(5), // 5 items available
  });

  console.log(`Candy Machine ID - ${candyMachine.address.toString()}`);
};

createCandyMachine();

如何删除Candy Machine

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

const deleteCandyMachine = async () => {
  const connection = new Connection(
    "https://api.devnet.solana.com/",
    "confirmed"
  );
  const payer = Keypair.generate();

  // request airdrop
  const airdropSignature = await connection.requestAirdrop(
    payer.publicKey,
    2 * LAMPORTS_PER_SOL
  );

  console.log(`Airdrop signature - ${airdropSignature}`);

  // creating metaplex instance with payer as the authority
  const metaplex = Metaplex.make(connection).use(keypairIdentity(payer));

  // creating a candy machine
  const { candyMachine } = await metaplex.candyMachinesV2().create({
    sellerFeeBasisPoints: 5, // 0.05% royalties
    price: sol(0.0001), // 0.0001 SOL
    itemsAvailable: toBigNumber(5), // 5 items available
  });

  console.log(`Candy Machine ID - ${candyMachine.address.toString()}`);

  // deleting the candy machine
  const { response } = await metaplex.candyMachinesV2().delete({
    candyMachine,
  });

  console.log(`Delete Candy Machine signature - ${response.signature}`);
};

deleteCandyMachine();

如何通过权限查找Candy Machine

要查找所有权限为特定公钥的 Candy Machine,我们需要使用 findAllByopen in new window 函数,并将 type 参数设置为 authority

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

const findCandyMachineViaAuthority = async () => {
  const connection = new Connection(
    "https://api.devnet.solana.com/",
    "confirmed"
  );
  const metaplex = new Metaplex(connection);
  const authority = new PublicKey(
    "9pr8wNxphx2PhBRbHKuH7YhPs5zbDuxx62UcDiayXxrw"
  );

  const candyMachines = await metaplex.candyMachinesV2().findAllBy({
    type: "authority",
    publicKey: authority,
  });

  candyMachines.map((candyMachine, index) => {
    console.log(`#${index + 1} Candy Machine ID - ${candyMachine.address}`);
  });

  /**
   * #1 Candy Machine ID - HSZxtWx6vgGWGsWu9SouXkHA2bAKCMtMZyMKzF2dvhrR
   */
};

findCandyMachineViaAuthority();

如何通过钱包地址查找Candy Machine

要通过钱包地址获取 Candy Machine 对象,我们需要使用 findAllByopen in new window 函数,并将 type 参数设置为 wallet。你可以从浏览器的 "Anchor data" 选项卡中获取 Candy Machine 的钱包地址。

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

const findCandyMachineViaWallet = async () => {
  const connection = new Connection(
    "https://api.devnet.solana.com/",
    "confirmed"
  );
  const metaplex = new Metaplex(connection);
  const wallet = new PublicKey("9pr8wNxphx2PhBRbHKuH7YhPs5zbDuxx62UcDiayXxrw");

  const candyMachines = await metaplex.candyMachinesV2().findAllBy({
    type: "wallet",
    publicKey: wallet,
  });

  candyMachines.map((candyMachine, index) => {
    console.log(`#${index + 1} Candy Machine ID - ${candyMachine.address}`);
  });

  /**
   * #1 Candy Machine ID - HSZxtWx6vgGWGsWu9SouXkHA2bAKCMtMZyMKzF2dvhrR
   */
};

findCandyMachineViaWallet();

如何通过Candy Machine的地址查找它

要通过Candy Machine的地址查找Candy Machine,我们需要使用findByAddressopen in new window 函数。

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

const findCandyMachineViaAddress = async () => {
  const connection = new Connection(
    "https://api.devnet.solana.com/",
    "confirmed"
  );
  const metaplex = new Metaplex(connection);
  const candyMachineId = new PublicKey(
    "HSZxtWx6vgGWGsWu9SouXkHA2bAKCMtMZyMKzF2dvhrR"
  );

  const candyMachine = await metaplex.candyMachinesV2().findByAddress({
    address: candyMachineId,
  });
};

findCandyMachineViaAddress();

如何从Candy Machine找到铸造(mint)的 NFT

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

const findCandyMachineMintedNfts = async () => {
  const connection = new Connection(
    "https://api.devnet.solana.com/",
    "confirmed"
  );
  const metaplex = new Metaplex(connection);
  const candyMachineId = new PublicKey(
    "HSZxtWx6vgGWGsWu9SouXkHA2bAKCMtMZyMKzF2dvhrR"
  );

  const candyMachine = await metaplex.candyMachinesV2().findMintedNfts({
    candyMachine: candyMachineId,
  });
};

findCandyMachineMintedNfts();

如何将物品插入到Candy Machine

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

const insertItems = async () => {
  const connection = new Connection(
    "https://api.devnet.solana.com/",
    "confirmed"
  );
  const metaplex = new Metaplex(connection);
  const candyMachineId = new PublicKey(
    "HSZxtWx6vgGWGsWu9SouXkHA2bAKCMtMZyMKzF2dvhrR"
  );

  await metaplex.candyMachines().insertItems({
    candyMachineId,
    items: [
      { name: "My NFT #1", uri: "https://example.com/nft1" },
      { name: "My NFT #2", uri: "https://example.com/nft2" },
      { name: "My NFT #3", uri: "https://example.com/nft3" },
    ],
  });
};

insertItems();

如何从Candy Machine铸造(Mint)一个 NFT

默认情况下,铸造的 NFT 的所有者是metaplex.identity().publicKey。如果你希望将 NFT 铸造到其他钱包中,可以将新的钱包公钥与newOwner参数一起传递。

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

const mintNft = async () => {
  const connection = new Connection(
    "https://api.devnet.solana.com/",
    "confirmed"
  );
  const metaplex = new Metaplex(connection);
  const candyMachineId = new PublicKey(
    "HSZxtWx6vgGWGsWu9SouXkHA2bAKCMtMZyMKzF2dvhrR"
  );

  // by default, the owner of the minted nft would be `metaplex.identity().publicKey`. if you want to mint the nft to some other wallet, pass that public key along with the `newOwner` parameter
  const candyMachine = await metaplex.candyMachinesV2().mint({
    candyMachine: candyMachineId,
    // newOwner: new PublicKey("some-other-public-key");
  });
};

mintNft();
Last Updated:
Contributors: lillianrf