bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 54쪽 중 26쪽
omnia
These are the tokens LI.FI can currently route to Somnia (Chain ID `5031`):
| Symbol | Address | Decimals | Notes |
| ------ | -------------------------------------------- | -------- | ----------------------- |
| SOMI | `0x0000000000000000000000000000000000000000` | 18 | Native gas token |
| WSOMI | `0x046EDe9564A72571df6F5e44d0405360c0f4dCab` | 18 | Wrapped SOMI |
| USDC.e | `0x28BEc7E30E6faee657a03e19Bf1128AaD7632A00` | 6 | Bridged USDC (Stargate) |
| USDT | `0x67B302E35Aef5EEE8c32D934F5856869EF428330` | 6 | Bridged USDT (Stargate) |
| WETH | `0x936Ab8C674bcb567CD5dEB85D8A216494704E9D8` | 18 | Wrapped ETH |
{% hint style="warning" %}
This list reflects LI.FI's coverage at the time of writing and grows over time. Always fetch the current set with `getTokens` at runtime rather than hard-coding it (see [Step 6](#step-6-keep-your-ui-in-sync)).
{% endhint %}
## Step 4: Execute the route
A quote must first be converted to a **route** with `convertQuoteToRoute`, then passed to `executeRoute`. The SDK handles token approvals, network switching, and signing through the wallet provider you configured in Step 2. Use `updateRouteHook` to react to progress in your UI.
```typescript
import { convertQuoteToRoute, executeRoute } from '@lifi/sdk';
// A quote cannot be executed directly — convert it to a route first
const route = convertQuoteToRoute(quote);
const executedRoute = await executeRoute(route, {
updateRouteHook: (updatedRoute) => {
// Called whenever the route changes during execution — drive your progress UI from here
updatedRoute.steps.forEach((step, i) => {
step.execution?.process.forEach((process) => {
console.log(`Step ${i + 1}:`, process.type, process.status);
if (process.txHash) console.log(' tx hash:', process.txHash);
});
});
},
});
```
{% hint style="info" %}
`executeRoute` also accepts other hooks such as `acceptExchangeRateUpdateHook` (confirm a changed rate before continuing) and `switchChainHook`. See the official [Execute Routes](https://docs.li.fi/sdk/execute-routes) guide for the full list.
{% endhint %}
## Step 5: Handle the outcome
Status updates arrive through the `updateRouteHook` from Step 4, read each step's `execution.status` and `execution.process` to drive your UI. Cross-chain transfers don't always end in an exact 1:1 result, so handle the final states explicitly:
| Final state | Meaning | What to show the user |
| ----------- | -------------------------------------------------------------- | ------------------------------------------- |
| Completed | Success, exact destination token received | "Funds arrived on Somnia" |
| Partial | Success, but a different token was received (still full value) | "Completed, received an alternate token" |
| Refunded | The transfer failed and funds were returned | "Refunded on the source chain" |
| Failed | Execution failed | Prompt the user to retry or contact support |
{% hint style="info" %}
A **same-chain** swap is atomic: it either fully succeeds or reverts. A **cross-chain** transfer can have the bridge succeed while the destination step fails, in that case the bridged asset is refunded on the destination chain.
{% endhint %}
To resume tracking an interrupted transfer (for example, after a page reload), the SDK can pick up an in-progress route again. See the official [Execute Routes](https://docs.li.fi/sdk/execute-routes) guide. For server-side or out-of-band status checks, query the REST [status endpoint](https://docs.li.fi/api-reference/check-the-status-of-a-cross-chain-transfer) directly.
## Step 6: Keep your UI in sync
Use the SDK's query helpers to populate dropdowns and **verify that Somnia routes and tokens are live** before showing them to users.
```typescript
import { ChainId, ChainType, getChains, getTokens, getTools } from '@lifi/sdk';
// Confirm Somnia (5031) is currently supported
const chains = await getChains({ chainTypes: [ChainType.EVM] });
const somniaSupported = chains.some((c) => c.id === 5031);
// Tokens available on Somnia — getTokens returns a map keyed by chain ID
// (cast 5031 to ChainId if your SDK version predates Somnia in the enum)
const { tokens } = await getTokens({ chains: [5031 as ChainId] });
const somniaTokens = tokens[5031] ?? [];
// Available bridges and exchanges
const { bridges, exchanges } = await getTools();
```
{% hint style="warning" %}
Somnia support on LI.FI is live and expanding. Treat `getChains` and `getTokens` as the source of truth at runtime rather than hard-coding lists, new tokens and bridges are added regularly.
{% endhint %}
## Monetization (optional)
Integrators can charge a fee on transactions routed through their app. Configure a fee when requesting a route and withdraw collected fees via LI.FI's Partner Portal.
See the official guide for the current setup: <https://docs.li.fi/sdk/monetize-sdk>.
## Troubleshooting
* **No route found:** Try a different token pair or a larger amount. Very small amounts may not cover bridge/gas costs.
* **Slippage / minimum-received error:** Increase the `slippage` parameter (default `0.005` = 0.5%) when requesting the quote.
* **Rate limited (`429`):** Without an API key you're limited to 200 requests / 2 hours. [Request a key](https://li.fi/) for production and back off with retries.
* **Insufficient balance:** Ensure the user holds enough of the source token **plus** native gas on the source chain.
* **Network switch fails:** Confirm Somnia (`5031`) is added to the user's wallet. See [Connect Your Wallet to Mainnet](/get-started/connect-your-wallet-to-mainnet).
## Need Help?
* LI.FI SDK docs (source of truth): <https://docs.li.fi/sdk/overview>
* Somnia Discord: <https://discord.gg/somnia>
* Somnia Developer Telegram: <https://t.me/+s_oRMnGpOyQ3ODQ0>
## Disclaimers
* LI.FI is a third-party provider. Always integrate from the official packages (`@lifi/sdk`) and verify chain/token data against the live API.
* Bridging and swapping involve smart-contract and market risk. Test thoroughly on a small amount before enabling production flows.
# Account Abstraction
Account Abstraction (AA) revolutionizes how users interact with blockchain applications by making wallets **smarter, simpler, and more programmable**.
In this section, you’ll explore how to implement **Smart Contract Accounts (SCAs)** on Somnia using modern tooling like **Thirdweb** and **Privy**, and learn how to enable **gasless transactions** and **session keys** for better UX.
You’ll learn how to:
* Create and manage smart contract wallets
* Implement user operations via ERC-4337-style flows
* Enable sponsored and gasless transactions
* Simplify onboarding through smart wallets and relayers
> Account Abstraction bridges the gap between Web2 simplicity and Web3 ownership — empowering developers to build dApps users actually love to use.
# Gasless Transactions with Thirdweb
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)
* Mint NFTs without paying gas fees
* View their NFT balance
* Experience seamless Web3 interactions
## Create a Next.js Project
```bash
npx create-next-app@latest somnia-gasless-nft --typescript --tailwind --app
cd somnia-gasless-nft
```
## Install Thirdweb SDK
```bash
npm install thirdweb
```
## Set Up Environment Variables
Create a .env.local file in your project root:
```bash
NEXT_PUBLIC_THIRDWEB_CLIENT_ID=your_client_id_here
```
Get your Client ID from the[ Thirdweb Dashboard](https://thirdweb.com/dashboard/settings).
## Deploy a Simple NFT Contract
If you haven't already, deploy this simple ERC721 contract on Somnia.
<details>
<summary>SimpleNFT.sol</summary>
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract SimpleNFT is ERC721 {
uint256 private _tokenIdCounter;
constructor() ERC721("SimpleNFT", "SNFT") {}
function mint(address to) public {
uint256 tokenId = _tokenIdCounter;
_tokenIdCounter++;
_safeMint(to, tokenId);
}
function totalSupply() public view returns (uint256) {
return _tokenIdCounter;
}
}
```
</details>
## Create Constants Configuration
Now we need to set up our configuration file that will handle all the blockchain connections and smart account setup. This file will:
* Initialize the Thirdweb client with your API key
* Set up both traditional wallet and in-app wallet options
* Define the smart account infrastructure
Create a new folder called constants in your project root, then create `constants/index.ts.`
<details>
<summary>constants</summary>
```typescript
import { createThirdwebClient, getContract } from 'thirdweb';
import { SmartWalletOptions, inAppWallet } from 'thirdweb/wallets';
import { somniaTestnet } from 'thirdweb/chains';
// Validate environment variables
const clientId = process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID;
if (!clientId) {
throw new Error('No client ID provided');
}
// Initialize Thirdweb client
export const client = createThirdwebClient({
clientId: clientId,
});
// Use Somnia testnet
export const chain = somniaTestnet;
// Your deployed NFT contract address
export const nftContractAddress = '0x...'; // UPDATE with your contract address
// Get contract instance
export const nftContract = getContract({
address: nftContractAddress,
chain,
client,
});
// Account Abstraction configuration for standard wallet connection
export const accountAbstraction: SmartWalletOptions = {
chain,
sponsorGas: true, // Enable gasless transactions
};
// Smart Account infrastructure addresses
const FACTORY_ADDRESS = '0x4be0ddfebca9a5a4a617dee4dece99e7c862dceb'; // Thirdweb Account Factory
// In-app wallet configuration with smart accounts
export const wallets = [
inAppWallet({
smartAccount: {
chain: somniaTestnet,
sponsorGas: true,
factoryAddress: FACTORY_ADDRESS,
},
}),
];
```
</details>
## Create the Main Minting Page
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`.
<details>
<summary>page.tsx</summary>
```typescript
'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';
```
</details>
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:
<details>
<summary>page.tsx</summary>
```typescript
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
});
```
</details>
* `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
<details>
<summary>page.tsx</summary>
```typescript
'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>
);
};
```
</details>
### 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`:
<details>
<summary>layout.tsx</summary>
```typescript
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>
);
}
```
</details>
## 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:
* [Thirdweb Account Abstraction Guide](https://portal.thirdweb.com/connect/account-abstraction)
### Resources
* [Live Demo](https://nextjs-thirdweb-somnia.vercel.app/)
# Smart Wallet App with Thirdweb
The Somnia mission is to enable the development of mass-consumer real-time applications. The Somnia Network allows developers to build a unique experience by implementing Smart Contract Wallets with gasless transactions via Account Abstraction (ERC-4337). In this tutorial, we'll use the [Thirdweb React SDK](https://portal.thirdweb.com/react/v5) to:
* Connect Smart Wallets (Account Abstraction)
* Read Wallet Balance
* Send STT Tokens
## Pre-requisites
Before we start, ensure you have:
* Basic knowledge of React
* A [Thirdweb](https://thirdweb.com/) account & Client ID
* Node.js & npm installed
## Install Dependencies
Run the following command to set up your project:
```bash
npx create-next-app@latest somnia-thirdweb
cd somnia-thirdweb
npm 프로젝트로 돌아가기