Somnia 백서

Somnia

원문

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
프로젝트로 돌아가기