Overview
This step-by-step tutorial will guide you through the entire process, from building an Angular app to securely storing your artwork on IPFS using Pinata to deploying and minting your very own NFT collection with Thirdweb's powerful NFT Collection smart contract platform.
All the tools and platforms used in this tutorial are free to use. As a developer, you should not incur any costs while following along.
At the end of the tutorial, the NFTs you generate will be visible in a collection on the OpenSea platform.
Before minting, make sure your MetaMask wallet is connected to Sepolia and has test ETH. If you do not have any ETH in your testnet wallet, go to https://sepoliafaucet.com to get free Sepolia ETH for this tutorial.
๐ง Tech Stack & Tools
- Angular: Frontend framework
- Pinata: Upload files to IPFS
- Thirdweb: Web3 SDK + contract hosting
- MetaMask: Browser wallet / authentication
- OpenSea: NFT marketplace (Sepolia testnet)
โ Prerequisites
โ ๏ธ Installation of Node.js and Angular CLI is beyond the scope of this tutorial. It's assumed you already have the appropriate dev tools installed and set up prior to beginning.
- Node.js v18+
- Angular CLI: npm install -g @angular/cli
- MetaMask (connected to Sepolia)
- Test ETH: https://sepoliafaucet.com
- Pinata account with JWT token (Web3 SDK section)
- Thirdweb account
๐น Step 1: Upload Files to IPFS via Pinata
1.1 Setup Pinata account
Create a Pinata API Key (JWT)
Log into Pinata
Go to "Developers" section โ API Keys
Click New Key
- Name it nft-angular-app
- Choose "admin" option to give it full permissions
- After creating it, copy the JWT token โ you'll use it in your app.
1.2 Upload Images
Navigate to the Files tab
-
Manually upload your NFT image assets (JPG, PNG, etc.)
- Leave privacy setting to Public
โ ๏ธ All image files uploaded under your account will appear in the Angular app, regardless of the project.
๐น Step 2: Create Thirdweb Project + Contract
- Go to https://thirdweb.com/dashboard
- Click Create Project and save your:
- Client ID (frontend-safe)
- Specify localhost:4200 in the list of accepted domains to limit access
- Click Create
- Deploy a contract:
- View your new project
- Navigate to "Contracts"
- Choose Prebuilt Contract > NFT Collection
- Fill in name, symbol
- Ensure contract is choose Sepolia
- Deploy your contract -> you will be prompted to confirm the transaction using MetaMask
- Copy the deployed contract address โ youโll use this in Step 4 when we create our ThirdwebService class.
๐น Step 3: Angular App Setup
The following commands are cross-platform and should work the same on Windows, macOS, and Linux (provided Node.js and Angular CLI are properly installed). Run each command independantly as you will have some prompting between them.
โ When prompted, say NO to SSR/Prerendering.
โ ๏ธ When adding material choose Yes to proceed and pick any pre-built theme and say Yes to setting up global typography.
ng new nft-mint-app --standalone --routing --style=scss
cd nft-mint-app
ng add @angular/material
npm install @thirdweb-dev/sdk ethers pinata-web3
๐น Step 4: Thirdweb Service (src/app/thirdweb.service.ts)
Generate the Angular service:
ng generate service thirdweb --skip-tests
Code for thirdweb.service.ts
โ ๏ธ Be sure to paste the contract address of your Thirdweb NFT contract in the placeholder provided
import { Injectable } from '@angular/core';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { ethers } from 'ethers';
@Injectable({ providedIn: 'root' })
export class ThirdwebService {
private sdk: ThirdwebSDK | null = null;
private contractAddress = '0xYourContractAddressHere';
private async ensureSdk() {
if (typeof window === 'undefined' || !(window as any).ethereum) {
throw new Error('MetaMask is not available.');
}
await (window as any).ethereum.request({ method: 'eth_requestAccounts' });
if (!this.sdk) {
const provider = new ethers.providers.Web3Provider((window as any).ethereum);
const signer = provider.getSigner();
this.sdk = new ThirdwebSDK(signer, {
clientId: '[THIRD-WEB-CLIENT-ID]', // Frontend-safe
});
}
}
async mintTo(walletAddress: string, metadata: { name: string; description: string; image: string }) {
await this.ensureSdk();
const contract = await this.sdk!.getContract(this.contractAddress, 'nft-drop');
return contract.erc721.mintTo(walletAddress, metadata);
}
}
๐น Step 5: Add NFT Minting Logic to AppComponent
Replace the contents of src/app/app.component.html
<mat-toolbar color="primary">NFT Minting App</mat-toolbar>
<div class="container">
<h2 class="title">Mint Your NFT</h2>
<p>
This app allows you to mint your own NFT from an image that youโve uploaded to IPFS using Pinata.
It uses <strong>Thirdweb</strong>โs prebuilt ERC721 Drop contract and performs a lazy minting process.
Thirdweb makes it simple to interact with Web3 and smart contracts using their SDK.
Once minted, your NFT will be visible on OpenSea's Sepolia testnet.
</p>
<p>
To get started, connect your MetaMask wallet using the button below.
Your wallet must be connected to view available images and mint NFTs.
</p>
<div *ngIf="!walletConnected" style="margin-bottom: 2rem;">
<button mat-flat-button color="accent" (click)="connectWallet()">Connect Wallet</button>
</div>
<ul *ngIf="uploadedFiles.length && walletConnected">
<li *ngFor="let file of uploadedFiles" style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
<a [href]="pinataConfig.pinataGateway + file.cid" target="_blank">
<img [src]="pinataConfig.pinataGateway + '/ipfs/' + file.cid" alt="{{ file.name }}" style="height: 50px; border-radius: 4px;" />
</a>
<div style="flex-grow: 1">
<a [href]="pinataConfig.pinataGateway + '/ipfs/' + file.cid" target="_blank">
{{ file.name || file.cid }}
</a>
</div>
<button mat-stroked-button color="primary" (click)="mintNft(file.cid, file.name)" [disabled]="mintedCids.has(file.cid) || minting">
{{ mintedCids.has(file.cid) ? 'Minted' : 'Mint NFT' }}
</button>
</li>
</ul>
<p *ngIf="success">๐ NFT successfully minted!</p>
</div>
Replace the contents of src/app/app.component.ts:
โ ๏ธ Make sure to paste in your Pinata gateway and JWT token from Step 1 in the placeholders provided in the code below
import { Component, OnInit } from '@angular/core';
import { ThirdwebService } from './thirdweb.service';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { NgIf, NgFor } from '@angular/common';
import { PinataSDK } from 'pinata-web3';
interface Web3File {
cid: string;
name: string | null;
metadataCid: string | null
}
@Component({
selector: 'app-root',
standalone: true,
imports: [MatToolbarModule, MatButtonModule, NgIf, NgFor],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit {
pinataConfig = {
pinataGateway: 'https://[PINATA GATEWAY URL]',
pinataJwt: '[PINATA JWT]'
};
pinataGateway = this.pinataConfig.pinataGateway + "/ipfs/";
minting = false;
success = false;
uploadedFiles: Web3File[] = [];
mintedCids = new Set<string>();
walletConnected = false;
pinata = new PinataSDK({ pinataJwt: this.pinataConfig.pinataJwt });
constructor(private tw: ThirdwebService) {}
async ngOnInit() {
if (typeof window !== 'undefined' && (window as any).ethereum && (window as any).ethereum.selectedAddress) {
this.walletConnected = true;
}
const list = await this.pinata.listFiles();
this.uploadedFiles = list.map(file => ({
cid: file.ipfs_pin_hash,
name: file.metadata.name ?? '',
metadataCid: null
}));
}
async loadUploadedFiles() {
const files = await this.pinata.listFiles() as any[]; // You can define a better type later
this.uploadedFiles = files.map(meta => {
return {
name: meta.metadata.name,
cid: meta.metadata.ipfs_pin_hash,
metadataCid: meta.ipfs_pin_hash
} as Web3File;
});
}
connectWallet() {
if (typeof window !== 'undefined' && (window as any).ethereum) {
(window as any).ethereum.request({ method: 'eth_requestAccounts' })
.then(() => {
this.walletConnected = true;
console.log('Wallet connected');
})
.catch((error: any) => console.error('User denied wallet connection:', error));
} else {
alert('MetaMask is not installed.');
}
}
async mintNft(cid: string, name: string |null) {
this.success = false; //reset success flag
this.minting = true;
const metadata = {
name: `RedRock NFT: ${name}`,
description: 'Minted via Angular + Thirdweb',
image: `ipfs://${cid}`
};
try {
const accounts = await (window as any).ethereum.request({ method: 'eth_accounts' });
const wallet = accounts[0];
const tx = await this.tw.mintTo(wallet, metadata);
console.log('Minted to wallet:', tx);
this.mintedCids.add(cid);
this.success = true;
} catch (err) {
console.error('Error minting:', err);
}
this.minting = false;
}
}
Add a little style to your page:
Replace the contents of src/app/app.component.scss
.container {
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 80vh;
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
text-align: left;
}
.container .title {
text-align: center;
}
๐น Step 6: Start Your Angular App
Once everything is configured, itโs time to start your Angular app and try it out:
ng serve
Then open your browser to: http://localhost:4200
You should now see the NFT Minting App interface.
๐น Step 6.5: Minting UI Behavior
When the app loads for the first time, youโll see an empty page with basic instructions.
Click 'Connect Wallet' button to authenticate via MetaMask. Once connected, the app will list your uploaded metadata files from Pinata.
Each listed item will:
- Show the NFT name
- Display a thumbnail preview
- Include a 'Mint NFT' button beside it
Clicking Mint NFT button will:
- Use the selected metadata file's CID
- Pass the CID to the Thirdweb service, which interacts with your deployed contract
- Trigger MetaMask to prompt you to confirm the minting transaction
The minted NFT will be stored on the Sepolia testnet and assigned to your connected wallet address.
Because the NFTs are authored using your Metamask wallet address, you are able to verify the NFT being created by looking in the Thirdweb dashboard and finding the NFTs in the project you created in Step 2.
๐น Step 7: View NFT on OpenSea
- Visit https://testnets.opensea.io
- Connect your MetaMask wallet by clicking the wallet icon in the top right
- Make sure you're on the Sepolia Testnet โ you may need to go to MetaMask settings and enable Show test networks to see Sepolia
- Search your wallet address or visit your profile to view the minted NFTs
- Your NFT may take a minute or two to appear...just be patient.
โ You Did It!
You've built an NFT minting dApp with:
- ๐ฆ IPFS image + metadata hosting (Pinata)
- โ๏ธ Smart contract (Thirdweb)
- ๐ Wallet minting (MetaMask)
- ๐ผ NFT viewing (OpenSea testnet)
๐ Resources
- Thirdweb Dashboard
- Pinata Web3 SDK
- Thirdweb Docs
- OpenSea Testnet
- Sepolia Faucet
Top comments (0)