Non Fungible Token (NFT)
Cara membuat NFT
Untuk Membuat Sebuah NFT Kamu Harus:
- Upload gambar ke IPFS seperti Arweave
- Upload metadata json ke IPFS seperti Arweave
- Call metaplex untuk membuat sebuah account untuk NFT
Upload to Arweave
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));
})();
// 1. 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;
// 2. Upload metadata to Arweave
const metadataRequest = JSON.stringify(metadata);
const metadataTransaction = await arweave.createTransaction({
data: metadataRequest,
});
metadataTransaction.addTag("Content-Type", "application/json");
await arweave.transactions.sign(metadataTransaction, wallet);
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)
# 1. Load your arweave wallet
your_ar_wallet = Wallet('wallet.json')
# 2. Upload image to Arweave
with open('./code/nfts/arweave-upload/lowres-dog.png', 'rb') as f:
img_in_bytes = f.read()
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
# 3. 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
Mint NFT-nya
Jika kamu sudah meng-upload gambar dan metadata, kamu bisa mint NFT dengan kode berikut.
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);
})();
const mintNFTResponse = await metaplex.nfts().create({
uri: "https://ffaaqinzhkt4ukhbohixfliubnvpjgyedi3f2iccrq4efh3s.arweave.net/KUAIIbk6p8oo4XHRcq0U__C2r0mwQaNl0gQow4Qp9yk",
maxSupply: 1,
});
Catatan
Kamu tidak bisa mint sebuah NFT dari creator yang berbeda dari wallet-mu. Jika kamu mengalami creator issues, maka pastikan metadata-mu mendaftarkanmu sebagai creator.
Cara Mendapatkan Metadata NFT
Metaplex NFTs memiliki metadata yang disimpan di Arweave. Untuk mendapatkan metadata Arweave, kamu harus mendapatkan Metaplex PDA dan decode account data.
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 mint = new PublicKey("Ay1U9DWphDgc7hq58Yj1yHabt91zTzvV2YJbAWkPNbaK");
const nft = await metaplex.nfts().findByMint(mint);
console.log(nft.metadata);
/*
{
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] ]
}
}
*/
})();
const connection = new Connection(clusterApiUrl("mainnet-beta"));
const keypair = Keypair.generate();
const metaplex = new Metaplex(connection);
metaplex.use(keypairIdentity(keypair));
const mint = new PublicKey("Ay1U9DWphDgc7hq58Yj1yHabt91zTzvV2YJbAWkPNbaK");
const nft = await metaplex.nfts().findByMint(mint);
console.log(nft.metadata);
Cara Mengetahui Pemilik Sebuah NFT
Jika kamu memiliki mint key dari sebuah NFT, kamu bisa mencari pemilik terkini dengan mengintip di token account terbesar untuk mint key tersebut.
Mengingat bahwa NFTs memiliki 1 supply, dan mereka tidak dapat dibagi, yang artinya suatu token hanya dapat dimiliki oleh satu token account setiap kalinya, sementara itu semua token account lainnya untuk mint key tersebut memiliki saldo 0.
Ketika token account terbesar sudah diidentifikasi, kita bisa mengambil pemiliknya.
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>
}
*/
})();
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);
Cara untuk mengambil NFT Mint Addresses
Jika kamu tau public key dari Candy Machine, kamu bisa mendapatkan daftar semua NFT mint addresses yang dihasilkan dari Candy Machine tersebut dengan menggunakan code berikut. Ingat bahwa kita bisa menggunakan filter memcmp
karena, di v1, address dari Candy Machine selalu creator pertama.
Candy Machine V1
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);
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
Jika kamu menggunakan Candy Machine v2, maka pertama-tama kamu harus memiliki akses ke “Candy Machine Creator” address yang merupakan suatu PDA simpel yang menggunakan candy_machine
dan Candy Machine v2 address sebagai seeds. Ketika kamu sudah dapat creator address, kamu bisa memakainya dengan cara yang sama dengan v1.
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]);
})();
const getCandyMachineCreator = async (
candyMachine: PublicKey
): Promise<[PublicKey, number]> =>
PublicKey.findProgramAddress(
[Buffer.from("candy_machine"), candyMachine.toBuffer()],
CANDY_MACHINE_V2_PROGRAM
);
const candyMachineCreator = await getCandyMachineCreator(candyMachineId);
getMintAddresses(candyMachineCreator[0]);
Cara Mendapatkan Semua NFT dari Suatu Wallet
Ketika mendapatkan semua NFT dari sebuah wallet, kamu harus mendapatkan semua token account dan kemudian parse mana yang merupakan NFT. Ini bisa dilakukan denganfindDataByOwner
dari Metaplex js library.
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);
})();
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);