Your First NFT
This tutorial will guide you through the process of using the Aptos TypeScript SDK (@aptos-labs/ts-sdk
) to create a new digital asset (often referred to as an NFT) on Aptos. By the end of this tutorial, you will know how to:
- Create a collection of digital assets (NFTs).
- Mint a new digital asset (NFT) within that collection.
- Transfer the digital asset (NFT) between accounts.
- Verify the digital asset’s (NFT’s) movement by checking the updated balances.
This tutorial assumes you are comfortable with using the Aptos CLI, have Node.js and npm installed, and understand basic JavaScript/TypeScript concepts. If you need more info, check out Node.js Introduction or the Aptos TypeScript SDK documentation.
Walking Through The Code
Below is the step-by-step explanation of how to create, transfer, and interact with a digital asset on-chain. We’ll go through how the example code (shown in full at the end) does it. To skip to just getting the code running, see Running An Example.
Key Steps
- Collection Creation: A “collection” is a grouping of digital assets. We’ll create a new collection under Alice’s account.
- Minting a Digital Asset: Once the collection is in place, we’ll mint a new digital asset (NFT) under that collection.
- Transferring a Digital Asset: Finally, we’ll transfer the newly minted asset from Alice to Bob.
Code Walkthrough
Setup the Client
We import and configure the Aptos
client from the SDK to connect to the specified network:
const APTOS_NETWORK = NetworkToNetworkName[process.env.APTOS_NETWORK] || Network.DEVNET;
const config = new AptosConfig({ network: APTOS_NETWORK });
const aptos = new Aptos(config);
This aptos
object allows us to interact with the Aptos blockchain (funding accounts, creating assets, submitting transactions, etc.).
Create and Fund Accounts
We generate two accounts, Alice and Bob. On devnet, we can easily fund them with test APT.
const alice = Account.generate();
const bob = Account.generate();
await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: INITIAL_BALANCE });
await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: INITIAL_BALANCE });
Create a Collection
We create a collection in Alice’s account. A collection acts like a “folder” or “category” for digital assets. In this case, we are creating "Example Collection"
.
const createCollectionTransaction = await aptos.createCollectionTransaction({
creator: alice,
description: "This is an example collection.",
name: "Example Collection",
uri: "aptos.dev",
});
const committedTxn = await aptos.signAndSubmitTransaction({
signer: alice,
transaction: createCollectionTransaction,
});
await aptos.waitForTransaction({ transactionHash: committedTxn.hash });
Always wait for the transaction to complete using waitForTransaction
before proceeding. This ensures the collection is ready before you try minting.
Mint a Digital Asset
With the collection created, we can now mint a digital asset (an NFT) for the collection. This involves providing details like the name, description, and a URI (often linking to metadata like images).
const mintTokenTransaction = await aptos.mintDigitalAssetTransaction({
creator: alice,
collection: "Example Collection",
description: "This is an example digital asset.",
name: "Example Asset",
uri: "aptos.dev/asset",
});
const mintTxn = await aptos.signAndSubmitTransaction({
signer: alice,
transaction: mintTokenTransaction,
});
await aptos.waitForTransaction({ transactionHash: mintTxn.hash });
You can change these values to customize your Digital Asset on-chain.
Transfer the Digital Asset
Once minted, the asset belongs to Alice. We can verify this by fetching Alice’s digital assets. Then we build and submit a transaction to transfer this asset to Bob.
const aliceDigitalAssets = await aptos.getOwnedDigitalAssets({ ownerAddress: alice.accountAddress });
const digitalAssetAddress = aliceDigitalAssets[0].token_data_id;
const transferTransaction = await aptos.transferDigitalAssetTransaction({
sender: alice,
digitalAssetAddress,
recipient: bob.accountAddress,
});
const transferTxn = await aptos.signAndSubmitTransaction({
signer: alice,
transaction: transferTransaction,
});
await aptos.waitForTransaction({ transactionHash: transferTxn.hash });
After completion, the asset should now appear in Bob’s account.
Verify the Balances
Finally, we check both Alice’s and Bob’s accounts to ensure that Alice no longer has the asset and Bob now has it.
const aliceDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({ ownerAddress: alice.accountAddress });
const bobDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({ ownerAddress: bob.accountAddress });
console.log(`Alice's digital asset balance: ${aliceDigitalAssetsAfter.length}`);
console.log(`Bob's digital asset balance: ${bobDigitalAssetsAfter.length}`);
Running An Example
Getting Started
Prerequisites
- Node.js: Make sure you have Node.js (version 16 or later) and npm installed.
- Aptos CLI (Optional): Having the Aptos CLI can be helpful if you want to inspect on-chain data directly, but it’s not required for this tutorial.
Set up Your Project
Create a new directory for your project and initialize a Node.js project:
mkdir aptos-digital-asset-tutorial
cd aptos-digital-asset-tutorial
npm init -y
This will create a package.json
file, allowing you to install dependencies and run scripts.
Install Dependencies
You will need the Aptos TypeScript SDK and dotenv
to manage environment variables:
npm install @aptos-labs/ts-sdk dotenv
npm install --save-dev @types/node
Create tsconfig.json
- Create a new TypeScript configuration file:
touch tsconfig.json
Windows users: Use type nul > tsconfig.json
in Command Prompt.
- Open the
tsconfig.json
file and add the following configuration:
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"types": ["node"],
"lib": ["es2020"]
}
}
Remember to save your file after adding the configuration!
This configuration ensures TypeScript properly recognizes Node.js types and provides appropriate type checking.
Configure Environment Variables
- Create a
.env
file by running:
touch .env
Windows users: Use type nul > .env
in Command Prompt.
- Open
.env
to edit it. - Update
.env
to have the following line:
APTOS_NETWORK=devnet
Remember to save your .env
file after adding the configuration!
By default, we’ll use devnet
, but you can also choose testnet
or mainnet
depending on your needs.
Adding index.ts
- Create a new file called
index.ts
in your project folder:
touch index.ts
Windows users: Use type nul > index.ts
in Command Prompt.
- Open
index.ts
for editing. - Copy the below code into
index.ts
:
index.ts
// Update the TODOs below to customize this digital asset to your needs.
// You will want to customize the Collection values and individual Digital Asset values.
// This example demonstrates creating a collection, populating it with digital assets, and transferring them.
import "dotenv/config";
import {
Account,
Aptos,
AptosConfig,
Network,
NetworkToNetworkName,
} from "@aptos-labs/ts-sdk";
// Verify environment variables are loaded
console.log("Environment variables loaded:", {
APTOS_NETWORK: process.env.APTOS_NETWORK || "not set"
});
const INITIAL_BALANCE = 100_000_000;
console.log("Step 1: Setting up a client to connect to Aptos");
const APTOS_NETWORK = NetworkToNetworkName[process.env.APTOS_NETWORK!] || Network.DEVNET;
const config = new AptosConfig({ network: APTOS_NETWORK });
const aptos = new Aptos(config);
async function example() {
console.log("\n=== Step 2: Creating and funding accounts ===\n");
const alice = Account.generate();
const bob = Account.generate();
console.log(`Alice's address: ${alice.accountAddress}`);
console.log(`Bob's address: ${bob.accountAddress}`);
console.log("Funding Alice's account...");
await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: INITIAL_BALANCE });
console.log("Alice's account funded!");
console.log("Funding Bob's account...");
await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: INITIAL_BALANCE });
console.log("Bob's account funded!");
console.log("\n=== Step 3: Creating a collection ===\n");
// TODO: Update these values to customize your Digital Asset!
const collectionName = "Example Collection";
const collectionDescription = "This is an example collection.";
const collectionURI = "aptos.dev";
console.log("Building the collection creation transaction...");
const createCollectionTransaction = await aptos.createCollectionTransaction({
creator: alice,
description: collectionDescription,
name: collectionName,
uri: collectionURI,
});
console.log("Submitting the collection creation transaction...");
const committedTxn = await aptos.signAndSubmitTransaction({
signer: alice,
transaction: createCollectionTransaction,
});
console.log("Waiting for the collection creation transaction to complete...");
await aptos.waitForTransaction({ transactionHash: committedTxn.hash });
console.log("Collection created successfully!");
console.log("\n=== Step 4: Minting a digital asset ===\n");
// TODO: Update the values of the Digital Assets you are minting!
const tokenName = "Example Asset";
const tokenDescription = "This is an example digital asset.";
const tokenURI = "aptos.dev/asset";
console.log("Building the mint transaction...");
const mintTokenTransaction = await aptos.mintDigitalAssetTransaction({
creator: alice,
collection: collectionName,
description: tokenDescription,
name: tokenName,
uri: tokenURI,
});
console.log(mintTokenTransaction)
console.log("Submitting the mint transaction...");
const mintTxn = await aptos.signAndSubmitTransaction({
signer: alice,
transaction: mintTokenTransaction,
});
console.log(mintTxn)
console.log("Waiting for the mint transaction to complete...");
await aptos.waitForTransaction({ transactionHash: mintTxn.hash });
console.log("Digital asset minted successfully!");
console.log("\n=== Step 5: Transferring the digital asset ===\n");
// Wait for the indexer to update with the latest data from on-chain
await new Promise((resolve) => setTimeout(resolve, 5000));
const aliceDigitalAssets = await aptos.getOwnedDigitalAssets({
ownerAddress: alice.accountAddress,
});
// Check if Alice has any digital assets before accessing them
if (aliceDigitalAssets.length === 0) {
console.error("No digital assets found for Alice. Make sure the minting was successful.");
return;
}
const digitalAssetAddress = aliceDigitalAssets[0].token_data_id;
console.log("Building the transfer transaction...");
const transferTransaction = await aptos.transferDigitalAssetTransaction({
sender: alice,
digitalAssetAddress,
recipient: bob.accountAddress,
});
console.log("Submitting the transfer transaction...");
const transferTxn = await aptos.signAndSubmitTransaction({
signer: alice,
transaction: transferTransaction,
});
console.log("Waiting for the transfer transaction to complete...");
await aptos.waitForTransaction({ transactionHash: transferTxn.hash });
console.log("Digital asset transferred successfully!");
console.log("\n=== Step 6: Verifying digital asset balances ===\n");
const aliceDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({
ownerAddress: alice.accountAddress,
});
const bobDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({
ownerAddress: bob.accountAddress,
});
console.log(`Alice's digital asset balance: ${aliceDigitalAssetsAfter.length}`);
console.log(`Bob's digital asset balance: ${bobDigitalAssetsAfter.length}`);
console.log("\n=== Step 7: Transaction hashes for explorer ===\n");
console.log(`Collection creation transaction: ${committedTxn.hash}`);
console.log(`Mint transaction: ${mintTxn.hash}`);
console.log(`Transfer transaction: ${transferTxn.hash}`);
console.log("\nYou can view these transactions on the Aptos Explorer:");
console.log("https://explorer.aptoslabs.com/?network=devnet");
}
example();
Run the code
npx ts-node index.ts
If everything is set up correctly, you will see output logs detailing each step, transaction hashes, and final balances.
View Your Transactions on the Explorer
After running the code, you’ll see transaction hashes in the console output, especially in Step 7 which displays all transaction hashes for easy reference:
=== Step 7: Transaction hashes for explorer ===
Collection creation transaction: 0x8c5d2a4ce32d76349bfb4f3830740c1c103399e8cbc31d6e2c7a871c88e6ad48
Mint transaction: 0x673d2cbb9fef468fe41f271c0fcf20872e9fa79afb6a2000368394000071b02e
Transfer transaction: 0x3a1e99d6fd3f8e7e962c311f3dfd92c11e468da5b6084123b8f7e0248a37ffa7
You can view these transactions on the Aptos Explorer:
https://explorer.aptoslabs.com/?network=devnet
You can view these transactions on the Aptos Explorer:
- Copy the transaction hash from your console
- Visit Aptos Explorer
- Make sure you’re on the correct network (Devnet)
- Paste the transaction hash in the search bar
- View the details of your transaction, including:
- The sender and recipient addresses
- The exact time the transaction was processed
- Gas fees paid
- The digital asset that was transferred
This is a great way to verify your transactions and understand how they’re recorded on the blockchain.
Congrats!