Gasless Transactions on Somnia with Thirdweb Account Abstraction
This tutorial demonstrates how to build a gasless NFT minting application on Somnia using Thirdweb's Account Abstraction infrastructure. Users can mint NFTs without holding STT tokens by using Smart Accounts with sponsored transactions.
Prerequisites
Basic knowledge of React and Next.js
A Thirdweb account and API key
A deployed ERC721 NFT Smart Contract on Somnia
What You'll Build
A web application where users can:
Connect using email or social accounts (via in-app wallets)
This is the main component where users will interact with your NFT minting application. We'll break this into three parts: imports and setup, the component logic, and the UI rendering.
Imports and Component Setup
First, let's set up our imports and initialize the component. Create app/page.tsx.
page.tsx
'use client';
import { useState } from 'react';
import { balanceOf, totalSupply } from 'thirdweb/extensions/erc721';
import {
ConnectButton,
TransactionButton,
useActiveAccount,
useReadContract,
} from 'thirdweb/react';
import { prepareContractCall } from 'thirdweb';
import {
accountAbstraction,
client,
nftContract,
wallets,
} from '../constants';
import Link from 'next/link';
These imports provide:
ERC721 Extensions: Functions to read NFT data (balance and total supply)
React Components: Pre-built UI components for wallet connection and transactions
Hooks: To access the connected account and read blockchain data
All our constants from the previous step
Component Logic and Data Fetching
Now let's add the component logic that handles wallet connections and reads blockchain data:
page.tsx
export default function Home() {
// Get the currently connected account (either smart account or regular wallet)
const account = useActiveAccount();
// State for showing transaction progress
const [txStatus, setTxStatus] = useState<string>('');
// Read the total number of NFTs minted from the contract
const { data: totalMinted } = useReadContract(totalSupply, {
contract: nftContract,
});
// Read how many NFTs the connected user owns
const { data: userBalance } = useReadContract(balanceOf, {
contract: nftContract,
owner: account?.address!,
queryOptions: { enabled: !!account }, // Only fetch when account is connected
});
useActiveAccount(): Gets the connected wallet/smart account
useState: Manages transaction status messages
useReadContract: Automatically fetches and updates blockchain data
The queries will re-fetch automatically when accounts change or transactions complete
User Interface
Now let's build the complete UI that users will interact with
page.tsx
'use client';
import { useState } from 'react';
import { balanceOf, totalSupply } from 'thirdweb/extensions/erc721';
import {
ConnectButton,
TransactionButton,
useActiveAccount,
useReadContract,
} from 'thirdweb/react';
import { prepareContractCall } from 'thirdweb';
import {
accountAbstraction,
client,
nftContract,
wallets,
} from '../../constants';
import Link from 'next/link';
const GaslessHome: React.FC = () => {
const account = useActiveAccount();
const [txStatus, setTxStatus] = useState<string>('');
// Get total supply
const { data: totalMinted } = useReadContract(totalSupply, {
contract: nftContract,
});
// Get user's balance
const { data: userBalance } = useReadContract(balanceOf, {
contract: nftContract,
owner: account?.address!,
queryOptions: { enabled: !!account },
});
return (
<div className='flex flex-col items-center min-h-screen p-8'>
{/* Main Title */}
<h1 className='text-2xl md:text-6xl font-semibold md:font-bold tracking-tighter mb-12 text-zinc-100'>
Gasless NFT Minting on Somnia
</h1>
{/* Wallet Connection Button */}
<ConnectButton
client={client}
wallets={wallets} // Enables in-app wallet options
accountAbstraction={accountAbstraction} // Enables smart accounts for regular wallets
connectModal={{
size: 'wide',
title: 'Choose Your Login Method',
welcomeScreen: {
title: 'Gasless NFT Minting',
subtitle: 'Sign in to mint NFTs without gas fees',
},
}}
appMetadata={{
name: 'Somnia NFT Minter',
url: 'https://somnia.network',
}}
/>
{/* NFT Display and Minting Section */}
<div className='flex flex-col mt-8 items-center'>
{/* Stats Card */}
<div className='mb-8 p-8 bg-zinc-900 rounded-2xl shadow-xl'>
<div className='text-center mb-6'>
<p className='text-3xl font-bold text-white mb-2'>
{totalMinted?.toString() || '0'}
</p>
<p className='text-sm text-zinc-400'>Total NFTs Minted</p>
</div>
{/* NFT Visual Representation */}
<div className='flex items-center justify-center'>
<div className='w-48 h-48 bg-gradient-to-br from-purple-600 to-blue-600 rounded-xl flex items-center justify-center shadow-lg'>
<div className='text-white text-center'>
<p className='text-6xl font-bold mb-2'>NFT</p>
<p className='text-sm opacity-80'>SimpleNFT on Somnia</p>
</div>
</div>
</div>
</div>
{/* Conditional Rendering: Connected vs Not Connected */}
{account ? (
<div className='flex flex-col items-center gap-4'>
{/* User Stats */}
<div className='text-center'>
<p className='font-semibold text-lg'>
You own{' '}
<span className='text-green-400'>
{userBalance?.toString() || '0'}
</span>{' '}
NFTs
</p>
<p className='text-sm text-zinc-400 mt-1'>
Wallet: {account.address.slice(0, 6)}...{account.address.slice(-4)}
</p>
</div>
{/* Transaction Status */}
{txStatus && (
<p className='text-sm text-yellow-400 mb-2'>{txStatus}</p>
)}
{/* Mint Button */}
<TransactionButton
transaction={() =>
prepareContractCall({
contract: nftContract,
method: 'function mint(address to)',
params: [account.address],
})
}
onError={(error) => {
console.error('Transaction error:', error);
setTxStatus('');
// User-friendly error messages
let errorMessage = 'Transaction failed';
if (error.message?.includes('insufficient funds')) {
errorMessage = 'Insufficient funds for gas';
} else if (error.message?.includes('rejected')) {
errorMessage = 'Transaction rejected by user';
} else if (error.message?.includes('500')) {
errorMessage = 'Service temporarily unavailable';
}
alert(`Error: ${errorMessage}`);
}}
onTransactionSent={(result) => {
console.log('Transaction sent:', result.transactionHash);
setTxStatus('Transaction submitted! Waiting for confirmation...');
}}
onTransactionConfirmed={async (receipt) => {
console.log('Transaction confirmed:', receipt);
setTxStatus('');
alert('NFT minted successfully!');
}}
className='px-8 py-4 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 rounded-lg font-semibold transition-all transform hover:scale-105 shadow-lg'
>
Mint NFT (Gasless)
</TransactionButton>
</div>
) : (
{/* Not Connected State */}
<p className='text-center w-full mt-10 text-zinc-400'>
Connect your wallet to mint NFTs without gas fees!
</p>
)}
</div>
{/* Navigation - Removed since this is now the home page */}
</div>
);
};
Understanding the UI Components
ConnectButton: This component handles both connection methods:
In-App Wallets: Users can sign in with email, Google, Apple, etc.
Traditional Wallets: Users can connect MetaMask, WalletConnect, etc.
Both methods automatically create smart accounts for gasless transactions
TransactionButton: This component prepares and sends the transaction automatically. Handling all wallet interactions and confirmations, and provides callbacks for different transaction states. It also shows loading states automatically
Error Handling: The component provides user-friendly error messages instead of technical blockchain errors, improving the user experience.
Update the Layout
The layout file wraps your entire application and is where we set up the Thirdweb provider. This provider is essential as it:
Manages wallet connections across your app
Handles blockchain interactions
Provides React hooks for reading blockchain data
Manages transaction states
Update app/layout.tsx:
layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThirdwebProvider } from "thirdweb/react";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Gasless NFT Minting on Somnia",
description: "Mint NFTs without gas fees using Account Abstraction",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<ThirdwebProvider>
<main className="min-h-screen bg-zinc-950 text-white">
{children}
</main>
</ThirdwebProvider>
</body>
</html>
);
}
Conclusion
You've successfully built a gasless NFT minting application on Somnia! This implementation leverages Thirdweb's Account Abstraction to provide a seamless Web3 experience where users can interact with blockchain without holding native tokens.
For more advanced features and updates on Somnia support, check: