# 利用Typescript在Aptos发行NFT

## 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:

```bash
mkdir sample-collection
cd sample-collection
npm init
```

Install necessary packages:

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

Setup typescript params:

```bash
./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**. &#x20;

### 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.

```json
// 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:

<figure><img src="https://1167150119-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fj5drALjutaFKso9Wta2S%2Fuploads%2FxFM2UMqZ6M5gIKU4SWxY%2Ffolder-structure.png?alt=media&#x26;token=24964239-55ae-43c8-a775-e787d8e7c2fc" alt=""><figcaption><p>Code structure</p></figcaption></figure>

## Host assets on IPFS&#x20;

Here we use [nft.storage](https://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*)

```json
// 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&#x20;

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

```json
{
  "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](https://petra.app/).

Fund your account with some $APT tokens, 1 $APT tokens should be enough. You can buy on [Binance](https://www.binance.com), [FTX](https://ftx.com/).

## 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:

```typescript
// 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:

```typescript
// 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 purpos&#x65;**.  isMainnet** flag is used turn on/off mainnet interaction.

### issue\_NFT.ts

Create **issue\_NFT.ts** under the src folder. Content below:

```typescript
// 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.

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

Run the command:

```bash
npm run issueNFT
```

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

## Congratulations, you've issued your collection!:tada:

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](https://www.topaz.so/profile)

Below is the aptos birds on Aptos

![](https://1167150119-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fj5drALjutaFKso9Wta2S%2Fuploads%2FH8VETHY38bryta5anBh4%2Fimage.png?alt=media\&token=94ef2219-1ca5-44f1-9efd-b77152355c33)
