非代替性トークン(NFT)

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)

NFTをミント

画像とメタデータを既にアップロードしている場合は、次のコードを使用して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);
})();

Note

ウォレットとは異なる作成者で 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であり、分割できないことを覚えておいてください。つまり、ある時点で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と Candy Machinev2アドレスをシードとして使用する単純なPDAである「Candy Machine Creator」アドレスにアクセスする必要があります。

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の作成と更新をサポートするようになりました。これにより、開発者はCandy Machine v2プログラムと対話し、Candy Machineを作成、更新、削除したり、そこから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();

authorityを介してCandy Machineを探す方法

authorityが特定の公開鍵であるすべてのCandy Machineを見つけるには、type パラメータをauthorityとしてfindAllByopen in new window関数を使用します。

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 オブジェクトを取得するには、 type パラメータをwalletとしてfindAllByopen in new window関数を使用します。 Candy Machine のウォレット アドレスは、エクスプローラーの [Anchor data] タブから取得できます。

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を見つけるには、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からミントされた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から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: PokoPoko2ry