利用Typescript在Aptos发行NFT

This tutorial walk you through the process of issuing NFT on Aptos step by step.

Source code

It's recommended to download the source code which gives you hands on experience.

https://github.com/zengxinhai/issue-NFT-on-Aptos

Prerequisites

You only need to know some Typescript to get started, make sure you have node installed.

Set up the project

Create an empty project and do some init:

mkdir sample-collection
cd sample-collection
npm init

Install necessary packages:

npm install aptos -S
npm install dotenv typescript ts-node -D

Setup typescript params:

./node_modules/typescrit/bin/tsc --init

Make sure to enable resolveJsonModule, and disable strictNullChecks

Prepare static files

Images

Make an asset folder to put the logo and pictures for the collection. Here, we have 1 logo image, and 2 token images in token-images subfolder.

Token meta data

Make an metadata folder to put the meta data for each token in the collection. Below is a sample for the token meta data.

// 1.json
{
  "name": "AptosBirds #1",
  "description": "AptosBirds #1",
  "image": "",
  "external_url": "",
  "attributes": [
    {
      "trait_type": "Specie",
      "value": "Owl"
    },
    {
      "trait_type": "Eyes",
      "value": "Angry - Yellow"
    },
    {
      "trait_type": "Eyewear",
      "value": "None"
    },
    {
      "trait_type": "Outerwear",
      "value": "Hoodie Down"
    },
    {
      "trait_type": "Headwear",
      "value": "None"
    },
    {
      "trait_type": "Body",
      "value": "Tabby"
    },
    {
      "trait_type": "Feathers",
      "value": "Gray"
    },
    {
      "trait_type": "Background",
      "value": "Green"
    },
    {
      "trait_type": "Beak",
      "value": "Small - Orange"
    }
  ]
}
  • name, description will be used by marketplace to show info on website.

  • attributes will be used by users to filter based on different traits.

  • image will be shown to user. We'll fill it after we upload the images to IPFS.

Now we have folder structure like this:

Host assets on IPFS

Here we use nft.storage to upload files to IPFS

NFTUp tool

Download at: https://nft.storage/docs/how-to/nftup/

Follow its steps to set up the account and on how to upload assets.

Upload the assets

Upload token-images folder, and then we can fill the image field for token meta data. Do it for all your token meta data. (change to your own ipfs urls)

// 1.json
{
  "name": "AptosBirds #1",
  "description": "AptosBirds #1",
  "image": "https://nftstorage.link/ipfs/bafybeibgtdkejt77t4w2fl2kh36cokmj5vipwfsxsn2z2fx35trlvg2kc4/1.png",
  "external_url": "https://nftstorage.link/ipfs/bafybeibgtdkejt77t4w2fl2kh36cokmj5vipwfsxsn2z2fx35trlvg2kc4/1.png",
  "attributes": [
    ...
  ]
}

After filling all the info for token meta data, upload metadata folder.

Finally, upload the logo picture.

Set the metadata, royalty, token config

Make a nft-config.json in the project root folder, we'll use these info for later use. Content below:

{
  "collectionMeta": {
    "name": "Aptos Birds",
    "description": "They're a collection of 10,000 utility-enabled PFPs that feature a richly diverse and unique pool of rarity-powered traits. What's more, each Aptosbird unlocks private club membership and additional benefits the longer you hold them.",
    "external_url": "https://aptosbirds.xyz"
  },
  "royalty": {
    "takeRate": 5,
    "feeRecipient": "0xc8c5f00f234b26a7e206d616a9a2063e6b7894651abc94d4b0ace979f53295fa"
  },
  "tokenInfo": {
    "maxSupply": 10000
  },
  "assets": {
    "logo": "./assets/logo.png",
    "collectionImagesPath": "./assets/collection-images/"
  }
}

You can adjust the config according to your need. A few things to be clear:

  • takeRate: the percent of the royalty, 5 stands for 5%.

  • feeRecipient: the address that will collect the royalty fees

  • maxSupply: the maximum supply for this collection

Set up Aptos account

We need an Aptos account to interact with the blockchain. If you don't have an account, you can generate an account using the Petra wallet.

Fund your account with some $APT tokens, 1 $APT tokens should be enough. You can buy on Binance, FTX.

Mint NFT using typescript

Make a src folder in the project root to contain all the source code, also you need a .env file to store PRIV_KEY to prevent you from submitting it to a git repo.

account.ts

Create account.ts under the src folder. Content below:

// account.ts
import dotenv from 'dotenv';
dotenv.config();

import { AptosAccount } from "aptos";

// Need to set PRIV_KEY in the .env file
const PRIV_KEY = process.env.PRIV_KEY;

// AptosAccount accepts unit8array as private key, so we need this helper function
const fromHexStringToUnit8Array = (hexString: string) =>
  Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));

const PRIV_KEY_ARR = fromHexStringToUnit8Array(PRIV_KEY);

export const aptosAccount = new AptosAccount(PRIV_KEY_ARR);
  • Get the private key from .env file

  • Convert to Unit8Array

  • Initialize the account, and export for later use.

chain.ts

Create chain.ts under the src folder. Content below:

// chain.ts
export const APTOS_NODE_URL_MAIN = 'https://fullnode.mainnet.aptoslabs.com'
import { AptosClient, FaucetClient, MaybeHexString, TokenClient } from "aptos";

const APTOS_NODE_URL_MAIN = 'https://fullnode.mainnet.aptoslabs.com'
const APTOS_NODE_URL_DEV = 'https://fullnode.devnet.aptoslabs.com'
const APTOS_FAUCET_URL_DEV = 'https://faucet.devnet.aptoslabs.com'

// For mainnet usage, set CHAIN_NET in .env file
const CHAIN_NET = process.env.CHAIN_NET;
export const isMainnet = CHAIN_NET === "Main";

// Initialize aptos client
// This client is used to interact with aptos blockchain
const aptosNodeUrl = isMainnet ? APTOS_NODE_URL_MAIN : APTOS_NODE_URL_DEV;
const aptosClient = new AptosClient(aptosNodeUrl);
export const tokenClient = new TokenClient(aptosClient);

// For dev purpose, we need to fund our account with faucet.
export const fundAccountForDev = (address: MaybeHexString) => {
  const faucetClient = new FaucetClient(APTOS_NODE_URL_DEV, APTOS_FAUCET_URL_DEV);
  faucetClient.fundAccount(address, 100_000_000);
}

Here we get the tokenClient to interact with Aptos blockchain, and a helper function fundAccountForDev to get $APT for dev purpose. isMainnet flag is used turn on/off mainnet interaction.

issue_NFT.ts

Create issue_NFT.ts under the src folder. Content below:

// issue_NFT.ts
import collectionConfig from "../collection-config.json";
import { aptosAccount } from "./account";
import { aptosClient, tokenClient, fundAccountForDev, isMainnet } from "./chain";

(async () => {
  console.log("=== Addresses ===");
  console.log(`Account address: ${aptosAccount.address()}`);
  console.log("");

  // Fund the account if in dev
  if (isMainnet) {
    await fundAccountForDev(aptosAccount.address());
  }

  console.log("=== Creating Collection ===");

  // Get params from config
  const collectionName = collectionConfig.collectionMeta.name;
  const collectionDesc = collectionConfig.collectionMeta.description;
  const collectionUri = collectionConfig.assets.logoUri;
  const collectionMaxAmount = collectionConfig.tokenInfo.maxSupply;

  const tokenUri = collectionConfig.assets.tokenMetadataUri;

  const feeRecipient = collectionConfig.royalty.feeRecipient;
  const royaltyPointsDenominator = 100;
  const royaltyPointsNumerator = collectionConfig.royalty.takeRate;

  // Create the collection.
  const createCollectionTxn = await tokenClient.createCollection(
    aptosAccount,
    collectionName,
    collectionDesc,
    collectionUri,
    collectionMaxAmount,
  );
  await aptosClient.waitForTransaction(createCollectionTxn, { checkSuccess: true });

  // Log the collection info after creation
  const logCollection = async () => {
    console.log("Collection info:");
    const collectionData = await tokenClient.getCollectionData(aptosAccount.address(), collectionName);
    console.log(`${collectionName}: ${JSON.stringify(collectionData, null, 4)}`);
  }
  await logCollection();

  console.log("=== Mint Token ===");

  const mintToken = async (tokenId: number) => {
    const txnHash = await tokenClient.createToken(
      aptosAccount,
      collectionName,
      `${collectionName} #${tokenId}`, // name of token, exp. Monkey #1
      `${collectionName} #${tokenId}`, // description of token
      1, // amount to mint, in most cases 1 for ERC721
      `${tokenUri}/${tokenId}.json`,
      1, // max supply for this token, set 1 for ERC721
      feeRecipient,
      royaltyPointsDenominator,
      royaltyPointsNumerator,
    );
    await aptosClient.waitForTransaction(txnHash, { checkSuccess: true });
  }
  // mint token 1 
  await mintToken(1);
  // await mintToken(2); // you can do the same with token 2

  // Log the collection info after token mint
  logCollection();
})();

The above code created a collection, and mint 1 token under the collection. A few things to konw:

  • You can only create 1 collection with the same name.

  • If you set CHAIN_NET=main, you'll need to fund your account with $APT to mint tokens

Issue the NFT

Add a command in package.json.

// package.json
{
  ...
  "scripts": {
    "issueNFT": "ts-node src/issue_NFT.ts"
  },
  ...
}

Run the command:

npm run issueNFT

If nothing is wrong, then you've issued your NFT.

Congratulations, you've issued your collection!🎉

Now, you've issued your NFT collection on Aptos. If you do it on mainnet, you can go check how your NFT looks on: topaz.so

Below is the aptos birds on Aptos

Last updated