Skip to Content
ПосібникиFull-Stack React застосунок

Створення Full-Stack React застосунку на базі TUWA SDK

Цей посібник крок за кроком описує створення Next.js додатку на базі TUWA SDK . За підсумком ви отримаєте:

  • Підключення гаманців за допомогою модальних вікон Nova Connect на базі безкастодіального клієнта Satellite Connect.
  • Вхід через SIWX: підключений гаманець підписує повідомлення CAIP-122, а ваш сервер зберігає сесію в куці HttpOnly.
  • Сховище транзакцій Pulsar разом з модальними вікнами та тостами Nova Transactions.
  • Next.js Server Action, який достовірно ідентифікує авторизований гаманець.

Усе імпортується з єдиного пакету @tuwaio/sdk та плагіна потрібної мережі. Продовженням цього посібника є синхронізація транзакцій із Quasar. Готовими прикладами архітектури є стартові шаблони nextjs-evm та nextjs-tuwa-quasar (Starter Templates).

Кроки, що відрізняються залежно від мережі, мають вкладки EVM та Solana. Вкладки синхронізовані: оберіть мережу один раз, і всі кроки адаптуються автоматично. Для одночасної підтримки обох мереж перегляньте розділ про EVM та Solana разом.

Перед початком

  • Next.js 16 з App Router та React 19.2 або новіше. Приклади використовують клієнтські модулі 'use client' та аліас імпорту @/ для каталогу src/.
  • URL вашого додатку в змінній NEXT_PUBLIC_APP_URL (наприклад http://localhost:3000): сервер приймає повідомлення авторизації виключно для цього домену.

Крок 1: Встановлення пакетів

pnpm add @tuwaio/sdk @tuwaio/evm-sdk @wagmi/core viem

Пакет @tuwaio/sdk надає базові пакети TUWA та спільні бібліотеки; додаток мережі містить специфічні для неї інструменти. Підключіть таблицю стилів Nova один раз у глобальному CSS-файлі вашого додатку:

src/app/globals.css
@import '@tuwaio/sdk/styles/all.css';

Крок 2: Налаштування мережі

Створіть конфігурацію @wagmi/core один раз поза компонентами. Функція createDefaultTransports використовує публічні RPC-ендпоінти чейнів viem з обмеженням частоти; для продакшну вказуйте власні RPC. З опцією ssr: true wagmi очікує відновлення стану браузером та реєстрації встановлених розширень (MetaMask, Rabby тощо); компонент EVMConnectorsWatcher із Кроку 5 робить це автоматично, тому провайдер WagmiProvider з бібліотеки wagmi не потрібен.

src/configs/appConfig.ts
import { createDefaultTransports } from '@tuwaio/evm-sdk/satellite';import { createConfig, injected } from '@wagmi/core';import { mainnet, sepolia } from 'viem/chains'; export const appChains = [sepolia, mainnet] as const; export const wagmiConfig = createConfig({ chains: appChains, connectors: [injected()], transports: createDefaultTransports(appChains), ssr: true,});

Не використовуйте розробницький конектор impersonated у додатку з SIWX: імітований гаманець не може підписати повідомлення авторизації, тому Nova Connect автоматично відключить його.


Крок 3: Додавання маршрутів SIWX

Сервер генерує nonce, перевіряє криптографічні підписи та зберігає сесії. Для розробки використовуйте сховище в пам'яті:

src/lib/authStores.ts
import { MemorySiwxNonceStore, MemorySiwxSessionStore } from '@tuwaio/sdk/siwx/server'; // In memory, for one server process. They refuse to run with NODE_ENV=production: use a shared store such as Redis// there (see the @tuwaio/siwx-server page).export const sessionStore = new MemorySiwxSessionStore();export const nonceStore = new MemorySiwxNonceStore();

Один Next.js Route Handler обслуговує ендпоінти /api/siwx/nonce, /api/siwx/verify, /api/siwx/session та /api/siwx/logout:

src/app/api/siwx/[...siwx]/route.ts
import { createSiwxApiHandler } from '@tuwaio/sdk/siwx/server-next'; import { nonceStore, sessionStore } from '@/lib/authStores'; const appUrl = new URL(process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000'); export const { GET, POST, DELETE } = createSiwxApiHandler({ sessionStore, nonceStore, policy: { expectedDomain: appUrl.host, expectedUri: appUrl.origin, requireExpirationTime: true, maxIssuedAtAgeSeconds: 300, },});

Для продакшн-сховищ на базі Redis, параметрів allowedChainIds та часу життя сесії див. @tuwaio/siwx-server . Детальніше про механіку входу читайте в посібнику з мультичейн-автентифікації. В EVM смарт-контрактні гаманці (Safe, Coinbase Smart Wallet) потребують клієнта для кожного чейну в verifyOptions.publicClient; посібник із зовнішньої автентифікації демонструє це разом із маршрутами JWT для Coinbase CDP.


Крок 4: Створення сховища транзакцій

src/hooks/pulsarStore.ts
import { pulsarEvmAdapter } from '@tuwaio/evm-sdk/pulsar';import { createBoundedUseStore, createPulsarStore, type Transaction } from '@tuwaio/sdk/pulsar'; import { appChains, wagmiConfig } from '@/configs/appConfig'; export const pulsarStore = createPulsarStore<Transaction>({ name: 'my-app-transactions', // the localStorage key adapter: [pulsarEvmAdapter(wagmiConfig, appChains)],}); export const usePulsarStore = createBoundedUseStore(pulsarStore);

Сховище зберігає транзакції в localStorage та відстежує стан кожної у фоновому режимі до досягнення фінального статусу в блокчейні. Використовуйте власний тип замість базового Transaction, щоб строго типізувати корисне навантаження payload, як показано в посібнику з відстеження транзакцій у React.


Крок 5: Рендеринг провайдерів

Ініціалізуйте адаптер Satellite та параметри SIWX поза компонентом, щоб вони створювалися лише один раз. NovaConnectProvider отримує конфігурацію siwx: він запитує підпис у кожного підключеного гаманця, відключає гаманець у разі відмови та скидає сесію при розриві з'єднання. Не передавайте siwx у компонент-watcher.

src/providers/Providers.tsx
'use client'; import { EVMConnectorsWatcher } from '@tuwaio/evm-sdk/nova-connect';import { satelliteEVMAdapter } from '@tuwaio/evm-sdk/satellite';import { NovaConnectProvider, type NovaConnectProviderProps } from '@tuwaio/sdk/nova-connect';import { NovaTransactionsProvider } from '@tuwaio/sdk/nova-transactions/providers';import { getAdapterFromConnectorType } from '@tuwaio/sdk/orbit';import { useInitializeTransactionsPool } from '@tuwaio/sdk/pulsar';import { SatelliteConnectProvider, useSatelliteConnectStore } from '@tuwaio/sdk/satellite';import type { ReactNode } from 'react'; import { appChains, wagmiConfig } from '@/configs/appConfig';import { usePulsarStore } from '@/hooks/pulsarStore'; const satelliteAdapter = satelliteEVMAdapter(wagmiConfig, appChains); const siwx: NovaConnectProviderProps['siwx'] = { getNonce: async () => { const res = await fetch('/api/siwx/nonce'); return ((await res.json()) as { nonce: string }).nonce; }, verifier: async (payload) => { const res = await fetch('/api/siwx/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); return res.ok ? res.json() : null; }, destroyer: async () => { await fetch('/api/siwx/logout', { method: 'POST' }); },}; // The modals and toasts of Nova Transactions, fed by the Pulsar store.function TransactionsUI() { const transactionsPool = usePulsarStore((state) => state.transactionsPool); const initialTx = usePulsarStore((state) => state.initialTx); const closeTxTrackedModal = usePulsarStore((state) => state.closeTxTrackedModal); const executeTxAction = usePulsarStore((state) => state.executeTxAction); const initializeTransactionsPool = usePulsarStore((state) => state.initializeTransactionsPool); const getAdapter = usePulsarStore((state) => state.getAdapter); const activeConnection = useSatelliteConnectStore((state) => state.activeConnection); // Restarts the trackers of pending transactions after a page reload. useInitializeTransactionsPool({ initializeTransactionsPool }); return ( <NovaTransactionsProvider transactionsPool={transactionsPool} initialTx={initialTx} closeTxTrackedModal={closeTxTrackedModal} executeTxAction={executeTxAction} connectedWalletAddress={activeConnection?.isConnected ? activeConnection.address : undefined} connectedAdapterType={getAdapterFromConnectorType(activeConnection?.connectorType ?? 'evm:')} adapter={getAdapter()} /> );} export function Providers({ children }: { children: ReactNode }) { const transactionsPool = usePulsarStore((state) => state.transactionsPool); const getAdapter = usePulsarStore((state) => state.getAdapter); return ( <SatelliteConnectProvider adapter={satelliteAdapter} autoConnect> <EVMConnectorsWatcher wagmiConfig={wagmiConfig} /> <TransactionsUI /> <NovaConnectProvider appChains={appChains} transactionPool={transactionsPool} pulsarAdapter={getAdapter()} siwx={siwx} withBalance withChain > {children} </NovaConnectProvider> </SatelliteConnectProvider> );}

Підключіть провайдери в кореневому макеті, а кнопку підключення розмістіть у будь-якому клієнтському місці:

src/app/layout.tsx
import './globals.css'; import type { ReactNode } from 'react'; import { Providers } from '@/providers/Providers'; export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body> <Providers>{children}</Providers> </body> </html> );}
src/app/page.tsx
'use client'; import { ConnectButton } from '@tuwaio/sdk/nova-connect/components'; export default function HomePage() { return ( <main> <ConnectButton /> </main> );}

Відкрийте додаток, підключіть гаманець і підпишіть повідомлення: кнопка відобразить адресу підключеного гаманця, а на сервері з'явиться дійсна криптографічна сесія.

Satellite Connect автоматично відновлює підключення останнього гаманця після перезавантаження. За замовчуванням прапорець rememberWallet зберігає цей гаманець у localStorage, доки користувач не натисне Відключити. Шаблони та сайти TUWA передають rememberWallet={false} у SatelliteConnectProvider: гаманець зберігається лише в sessionStorage (до закриття вкладки), якщо користувач не відмітить чекбокс "Запам'ятати цей гаманець" у модальному вікні Nova Connect.


Крок 6: Відправка відстежуваної транзакції

Відправка транзакції працює стандартно: сформуйте виклик смарт-контракту (EVM) або інструкцію програми (Solana) і передайте результат у метод executeTxAction вашого сховища. Використовуйте Кроки 2, 3, 6 та 7 із посібника з відстеження транзакцій, змінивши шляхи імпорту:

У посібникуЗ єдиним TUWA SDK
@tuwaio/pulsar-core@tuwaio/sdk/pulsar
@tuwaio/pulsar-react@tuwaio/sdk/pulsar
@tuwaio/orbit-core@tuwaio/sdk/orbit
@tuwaio/nova-connect/components@tuwaio/sdk/nova-connect/components
@tuwaio/nova-connect/satellite@tuwaio/sdk/nova-connect/satellite
@tuwaio/pulsar-evm@tuwaio/evm-sdk/pulsar
@tuwaio/pulsar-solana@tuwaio/solana-sdk/pulsar

Транзакція миттєво з'являється у спливаючих тостах Nova та в модальному вікні історії, зберігаючись після перезавантаження вкладки. Оновлюйте дані за допомогою хука useTxFinished, який гарантовано спрацює навіть для транзакцій, що фіналізувалися під час перезавантаження сторінки.


Крок 7: Читання сесії на сервері

Next.js Server Action або API-маршрут може легко перевірити, хто саме авторизований, перш ніж виконувати мутацію. Функція isSessionMatchingTarget надійно порівнює адресу з підтвердженої серверної сесії із цільовою адресою:

src/app/actions.ts
'use server'; import { getSiwxServerSession, isSessionMatchingTarget } from '@tuwaio/sdk/siwx/server';import { cookies } from 'next/headers'; import { sessionStore } from '@/lib/authStores'; export async function saveProfile(walletAddress: string, nickname: string) { const session = await getSiwxServerSession({ cookieSource: await cookies(), sessionStore }); if (!session || !isSessionMatchingTarget(session, walletAddress)) { throw new Error('Unauthorized'); } // The request comes from the owner of `walletAddress`: save `nickname`. return { walletAddress, nickname };}
Ніколи не довіряйте об'єкту сесії, надісланому браузером у тілі запиту: стан SIWX із @tuwaio/sdk/siwx на фронтенді є виключно станом UI. Лише криптографічна кука, прочитана на сервері, є юридичним і технічним доказом автентифікації.

🌐 EVM та Solana разом

Додаток з одночасною підтримкою обох мереж встановлює обидва аддони та поєднує конфігурації: обидва налаштування в appConfig.ts, обидва адаптери Pulsar у масиві adapter сховища, а в Providers.tsx — обидва адаптери Satellite, обидва компоненти-watcher та списки мереж:

<SatelliteConnectProvider adapter={[satelliteEVMAdapter(wagmiConfig, appChains), satelliteSolanaAdapter({ rpcUrls: solanaRPCUrls })]} autoConnect> <EVMConnectorsWatcher wagmiConfig={wagmiConfig} /> <SolanaConnectorsWatcher /> <TransactionsUI /> <NovaConnectProvider appChains={appChains} solanaRPCUrls={solanaRPCUrls} transactionPool={transactionsPool} pulsarAdapter={getAdapter()} siwx={siwx} withBalance withChain > {children} </NovaConnectProvider></SatelliteConnectProvider>

Створюйте масив адаптерів поза компонентом, аналогічно до одиночного адаптера. Стартовий шаблон nextjs-tuwa-quasar містить цю конфігурацію у готовому до запуску вигляді разом із Quasar Sync.


📚 Наступні кроки

Востаннє оновлено