Skip to Content
Core ConceptsFull TUWA Stack (React)

Complete TUWA Stack Integration

This guide walks you through replicating the exact, production-ready environment used by the Quasar Dashboard. The integration combines EVM/Solana wallet management, session caching, secure SIWE authentication, and Quasar Cloud Sync into a unified React context tree.


Installation & Module Overview

To integrate the full TUWA ecosystem, you must install the following modules, grouped by their specific purpose:

1. Quasar Cloud Sync

Provides the core API client and React authentication bridge to communicate with Quasar’s backend.

  • @tuwaio/quasar-sdk: Handles server-side API proxy verification (utils.verifyMiniSession), Quasar client initialization, and the frontend <QuasarAuthBridge /> component.

2. Pulsar Transaction Tracking

The underlying state-machine that tracks local wallet transaction states and handles cross-chain transaction events.

  • @tuwaio/pulsar-core: Core state store and manager for the local transaction pool.
  • @tuwaio/pulsar-evm: EVM tracking adapter.
  • @tuwaio/pulsar-solana: Solana tracking adapter.
  • @tuwaio/pulsar-react: React bindings and execution hooks (useInitializeTransactionsPool).
  • @tuwaio/orbit-core / @tuwaio/orbit-evm / @tuwaio/orbit-solana: Low-level blockchain connection and transaction mapping primitives.

3. Satellite Wallet Connector & Auth

Standardizes multi-chain wallet adapters (EVM + Solana) and encapsulates Next-Auth/SIWE logic.

  • @tuwaio/satellite-core: Core connection managers.
  • @tuwaio/satellite-evm: EVM adaptor connecting Wagmi to Satellite.
  • @tuwaio/satellite-solana: Solana adaptor bridging Solana standard wallet adapters.
  • @tuwaio/satellite-siwe-next-auth: Integrates Sign-In With Ethereum for cookie-based authentication.
  • wagmi / @wagmi/core / @wagmi/connectors / viem: Peer dependencies managing EVM client state.
  • @solana/react / gill: Peer dependencies managing Solana client state and connection interfaces.

4. Nova UI Kit

Provides pre-built modular UI overlays, transaction trackers, and action status notifications.

  • @tuwaio/nova-connect: Exposes connector watchers (EVMConnectorsWatcher, SolanaConnectorsWatcher) and the main provider <NovaConnectProvider />.
  • @tuwaio/nova-transactions: Houses UI components (modals and toast notifications) for tracing pending, succeeded, or failed transactions.

[!NOTE] The list above outlines the core ecosystem packages. It does not list all indirect peer dependencies (e.g., UI primitives like Framer Motion, Radix UI dialogs, or specific wallet standards). For a complete, production-ready dependency list, refer to the nextjs-tuwa-quasar example template  or check the documentation of each package individually.


1. App Configuration (appConfig.ts)

Define your network endpoints, supported EVM chains, and Wagmi client instance.

// src/configs/appConfig.ts import { createDefaultTransports, impersonated, safeSdkOptions } from '@tuwaio/satellite-evm'; import { baseAccount, safe, walletConnect } from '@wagmi/connectors'; import { createConfig, injected } from '@wagmi/core'; import { mainnet, sepolia, polygon, base, Chain } from 'viem/chains'; export const appConfig = { appName: 'Pulsar & Quasar Cloud Sync Demo', appDescription: 'Next.js Demo App with Pulsar and Quasar SDK Integration', projectId: process.env.NEXT_PUBLIC_WALLET_PROJECT_ID ?? '', appLogoUrl: 'logo_url', appUrl: process.env.NODE_ENV === 'development' ? 'http://localhost:3000/' : 'https://example.com/', }; export const solanaRPCUrls = { mainnet: `https://solana-mainnet.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_KEY}`, devnet: 'https://api.devnet.solana.com', }; export const appEVMChains = [mainnet, sepolia, polygon, base] as readonly [Chain, ...Chain[]]; export const wagmiConfig = createConfig({ connectors: [ injected(), baseAccount({ appName: appConfig.appName, appLogoUrl: appConfig.appLogoUrl }), safe({ ...safeSdkOptions }), walletConnect({ projectId: appConfig.projectId, metadata: { name: appConfig.appName, description: appConfig.appDescription, url: appConfig.appUrl, icons: [appConfig.appLogoUrl], }, }), impersonated({}), ], transports: createDefaultTransports(appEVMChains), chains: appEVMChains, ssr: true, syncConnectedChain: true, });

2. Server-Side Synchronization Actions (actions.ts)

To protect your API quotas, the frontend must not call the Quasar API directly using your secret keys. Instead, use Next.js Server Actions to securely proxy calls from your server backend, verifying the wallet signature (Mini-Session).

// src/app/actions.ts 'use server'; import { MiniSessionAuth, Quasar, Transaction, utils } from '@tuwaio/quasar-sdk'; import { appConfig } from '@/configs/appConfig'; const quasar = new Quasar({ secretKey: process.env.QUASAR_SDK_SK ?? '', baseUrl: process.env.QUASAR_BASE_URL ?? 'https://api.tuwa.io', }); // Sync local transaction tracking to the Quasar ledger export async function syncTransaction(tx: Transaction, authData: MiniSessionAuth) { const isValid = await utils.verifyMiniSession({ walletAddress: authData.walletAddress, signature: authData.signature, timestamp: authData.timestamp, chainType: authData.chainType, }); if (!isValid) throw new Error('Invalid or expired security signature.'); try { await quasar.pulsar.syncCreate(tx, appConfig.appName); return { success: true }; } catch (error) { console.error('Remote transaction sync failed:', error); throw error; } } // Fetch historical indexed transactions for a wallet export async function getHistory( params: { walletAddress: string; page?: number; limit?: number; appName?: string }, authData: MiniSessionAuth, ) { const isValid = await utils.verifyMiniSession({ walletAddress: authData.walletAddress, signature: authData.signature, timestamp: authData.timestamp, chainType: authData.chainType, }); if (!isValid) throw new Error('Invalid or expired security signature.'); try { return await quasar.pulsar.getHistory(params); } catch (error) { console.error('Failed to fetch transaction history:', error); throw error; } }

3. Client-Side State Management & Stores

Replicate the tracking state using local storage, Pulsar SDK adapters, and Zustand-bound hooks.

A. Client Session Store (useAuthStore.ts)

Uses the SDK’s built-in helper to cache the Quasar Mini-Session token on the client, avoiding repeated wallet prompts.

// src/hooks/useAuthStore.ts import { utils } from '@tuwaio/quasar-sdk'; export const useAuthStore = utils.createMiniSessionStore('quasar-mini-session-storage');

B. Pulsar Sync Store (pulsarStoreHook.ts)

Manages the local transaction tracking pool and hooks into the Server Actions defined above.

// src/hooks/pulsarStoreHook.ts 'use client'; import { createBoundedUseStore, createPulsarStore, createTxInMemoryStore } from '@tuwaio/pulsar-core'; import { pulsarEvmAdapter } from '@tuwaio/pulsar-evm'; import { pulsarSolanaAdapter } from '@tuwaio/pulsar-solana'; import { getMiniSessionAuth } from '@tuwaio/quasar-sdk/react'; import { getHistory, syncTransaction } from '@/app/actions'; import { appConfig, appEVMChains, solanaRPCUrls, wagmiConfig } from '@/configs/appConfig'; const storageName = 'quasar-tx-tracking-storage'; // State-machine for real-time local transaction tracking const initialStore = createPulsarStore({ name: storageName, adapter: [ pulsarEvmAdapter(wagmiConfig, appEVMChains), pulsarSolanaAdapter({ rpcUrls: solanaRPCUrls }), ], beforeTxProcess: async () => { await getMiniSessionAuth(); }, onRemoteCreate: async (tx) => { try { const auth = await getMiniSessionAuth(); await syncTransaction(tx as any, auth); } catch (err) { console.error('[Pulsar] Remote sync failed:', err); } }, }); export const usePulsarStore = createBoundedUseStore(initialStore); // In-memory pagination and historical syncing const pulsarInMemoryStore = createTxInMemoryStore({ localTransactionsPool: initialStore.getState().transactionsPool, getHistory: async ({ page, walletAddress }) => { try { const auth = await getMiniSessionAuth(); const history = await getHistory({ walletAddress, page, limit: 10, appName: appConfig.appName, }, auth); return history; } catch (error) { console.error('[Pulsar] Failed to fetch history:', error); throw error; } }, onHistoryFetched: async (remoteTxs) => { await initialStore.getState().injectExternalPendingTxs(remoteTxs); }, }); initialStore.subscribe((state) => pulsarInMemoryStore.getState().syncWithLocalPool(state.transactionsPool) ); export const usePulsarInMemoryStore = createBoundedUseStore(pulsarInMemoryStore);

4. Assembling the Provider Tree

Compose all multi-chain connector layers, watchers, transaction listeners, and UI notifications.

A. Quasar Authentication Bridge (QuasarSDKAuthProvider.tsx)

Bridges the connection status of the wallets with the Quasar mini-sessions.

// src/providers/QuasarSDKAuthProvider.tsx import { SatelliteStoreContext, useSatelliteConnectStore } from '@tuwaio/nova-connect/satellite'; import { QuasarActiveConnection, QuasarAuthBridge as QuasarSDKAuthBridge } from '@tuwaio/quasar-sdk/react'; import { useContext, useEffect } from 'react'; import { wagmiConfig } from '@/configs/appConfig'; import { useAuthStore } from '@/hooks/useAuthStore'; export function QuasarAuthBridge() { const activeConnection = useSatelliteConnectStore((s) => s.activeConnection); const store = useContext(SatelliteStoreContext); const session = useAuthStore((s) => s.miniSession); const setSession = useAuthStore((s) => s.setMiniSession); const clearSession = useAuthStore((state) => state.clearSession); useEffect(() => { if (!activeConnection?.isConnected) { clearSession(); } }, [activeConnection?.isConnected, clearSession]); if (!activeConnection || !store) return null; return ( <QuasarSDKAuthBridge activeConnection={activeConnection as QuasarActiveConnection} store={store} wagmiConfig={wagmiConfig} session={session} setSession={setSession} /> ); }

B. Nova Transactions Observer (NovaTransactionsProvider.tsx)

Binds stateful transactional events to pre-built UI toast notifications.

// src/providers/NovaTransactionsProvider.tsx import { useSatelliteConnectStore } from '@tuwaio/nova-connect/satellite'; import { NovaTransactionsProvider as NTP } from '@tuwaio/nova-transactions/providers'; import { getAdapterFromConnectorType } from '@tuwaio/orbit-core'; import { TxInMemoryPagination } from '@tuwaio/pulsar-core'; import { useInitializeTransactionsPool } from '@tuwaio/pulsar-react'; import { usePulsarInMemoryStore, usePulsarStore } from '@/hooks/pulsarStoreHook'; export function NovaTransactionsProvider({ pagination }: { pagination: TxInMemoryPagination }) { 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 activeConnection = useSatelliteConnectStore((state) => state.activeConnection); const getAdapter = usePulsarStore((state) => state.getAdapter); const transactionsPool = usePulsarInMemoryStore((state) => state.transactionsPool); useInitializeTransactionsPool({ initializeTransactionsPool }); return ( <NTP transactionsPool={transactionsPool} initialTx={initialTx} closeTxTrackedModal={closeTxTrackedModal} executeTxAction={executeTxAction} connectedWalletAddress={activeConnection?.isConnected ? activeConnection.address : undefined} connectedAdapterType={getAdapterFromConnectorType(activeConnection?.connectorType ?? 'evm:')} adapter={getAdapter()} pagination={pagination} /> ); }

C. Connect Provider Setup (SatelliteConnectProviders.tsx)

Wires up EVM and Solana wallet listeners, triggers the initial loading sequence, and wraps the cross-chain executing adapter.

// src/providers/SatelliteConnectProviders.tsx 'use client'; import { NovaConnectProvider } from '@tuwaio/nova-connect'; import { EVMConnectorsWatcher } from '@tuwaio/nova-connect/evm'; import { SatelliteConnectProvider } from '@tuwaio/nova-connect/satellite'; import { SolanaConnectorsWatcher } from '@tuwaio/nova-connect/solana'; import { getMiniSessionAuth } from '@tuwaio/quasar-sdk/react'; import { satelliteEVMAdapter } from '@tuwaio/satellite-evm'; import { satelliteSolanaAdapter } from '@tuwaio/satellite-solana'; import { useSiweAuth } from '@tuwaio/satellite-siwe-next-auth'; import { appEVMChains, solanaRPCUrls, wagmiConfig } from '@/configs/appConfig'; import { usePulsarInMemoryStore, usePulsarStore } from '@/hooks/pulsarStoreHook'; import { NovaTransactionsProvider } from '@/providers/NovaTransactionsProvider'; import { QuasarAuthBridge } from '@/providers/QuasarSDKAuthProvider'; export function SatelliteConnectProviders({ children }: { children: React.ReactNode }) { const { signInWithSiwe, enabled, isRejected, isSignedIn } = useSiweAuth(); const getAdapter = usePulsarStore((state) => state.getAdapter); const transactionsPool = usePulsarInMemoryStore((state) => state.transactionsPool); const isLoading = usePulsarInMemoryStore((state) => state.isLoading); const isError = usePulsarInMemoryStore((state) => state.isError); const currentPage = usePulsarInMemoryStore((state) => state.currentPage); const hasMore = usePulsarInMemoryStore((state) => state.hasMore); const fetchNextPage = usePulsarInMemoryStore((state) => state.fetchNextPage); const fetchInitial = usePulsarInMemoryStore((state) => state.fetchInitial); const pagination = { isLoading, isError, currentPage, hasMore, fetchNextPage }; return ( <SatelliteConnectProvider adapter={[ satelliteEVMAdapter(wagmiConfig, appEVMChains, enabled ? signInWithSiwe : undefined), satelliteSolanaAdapter({ rpcUrls: solanaRPCUrls }), ]} autoConnect={true} callbackAfterConnected={async (connection) => { try { await getMiniSessionAuth(); setTimeout(() => fetchInitial(connection.address), 2000); } catch (err) { console.error('[QuasarAuth] Session auth failed:', err); setTimeout(() => fetchInitial(connection.address), 2000); } }} > <EVMConnectorsWatcher wagmiConfig={wagmiConfig} siwe={{ isSignedIn, isRejected, enabled }} /> <SolanaConnectorsWatcher /> <QuasarAuthBridge /> <NovaTransactionsProvider pagination={pagination} /> <NovaConnectProvider appChains={appEVMChains} solanaRPCUrls={solanaRPCUrls} transactionPool={transactionsPool} pulsarAdapter={getAdapter() as any} withImpersonated withBalance withChain pagination={pagination} > {children} </NovaConnectProvider> </SatelliteConnectProvider> ); }

D. Root Assembly (providers.tsx)

Finally, export the root provider that wraps standard dependencies.

// src/providers/index.tsx 'use client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { SiweNextAuthProvider } from '@tuwaio/satellite-siwe-next-auth'; import { ReactNode } from 'react'; import { WagmiProvider } from 'wagmi'; import { wagmiConfig } from '@/configs/appConfig'; import { SatelliteConnectProviders } from '@/providers/SatelliteConnectProviders'; const queryClient = new QueryClient(); export function Providers({ children }: { children: ReactNode }) { return ( <WagmiProvider config={wagmiConfig}> <QueryClientProvider client={queryClient}> <SiweNextAuthProvider wagmiConfig={wagmiConfig} enabled={false} onSignOut={() => console.log('sign out')} onSignIn={(session) => console.log('sign in', session)} > <SatelliteConnectProviders> {children} </SatelliteConnectProviders> </SiweNextAuthProvider> </QueryClientProvider> </WagmiProvider> ); }

Wrap your root layout with this <Providers> component. Your dApp is now fully configured for automatic multi-chain transaction tracking, local-session persistence, and cloud synchronization.

Last updated on