React components and hooks for connecting dapps to TEN Protocol, featuring wallet connection, session key management, and privacy-preserving transactions.
Built on wagmi - This library provides a complete wagmi setup pre-configured for TEN Protocol, so you get all the power of wagmi with zero configuration needed.
- Wallet Connection: Easy wallet connection with TEN Protocol network detection
- Session Keys: Secure session key management for privacy-preserving transactions
- UI Components: Pre-built, customizable React components
- TypeScript: Full TypeScript support with comprehensive type definitions
- Storybook: Interactive component documentation and testing
- Zustand: Efficient state management with persistence
- Wagmi Integration: Built on top of wagmi v2 - get all wagmi hooks with TEN Protocol pre-configured
npm install @ten-protocol/ten-kit
# or
yarn add @ten-protocol/ten-kit
# or
pnpm add @ten-protocol/ten-kitMake sure you have the required peer dependencies installed:
npm install react react-dom wagmi viem @tanstack/react-query zustand
# or
yarn add react react-dom wagmi viem @tanstack/react-query zustand
# or
pnpm add react react-dom wagmi viem @tanstack/react-query zustandRequired versions:
react>= 16.8.0react-dom>= 16.8.0wagmi^2.0.0 - Core Ethereum hooks library (you'll configure withTENWagmiConfig)viem^2.0.0 - Ethereum utilities used by wagmi@tanstack/react-query^5.0.0 - Data fetching library used by wagmizustand^4.4.0 - State management for session keys
Note: The library exports
TENWagmiConfigfor easy wagmi setup. You'll need to wrap your app withWagmiProviderandQueryClientProvideras shown in the Quick Start examples.
Import the required CSS file in your app's entry point (e.g., _app.tsx, main.tsx, or index.tsx):
import '@tenprotocol/ten-kit/styles.css';This CSS file includes all the necessary styles for the components, including Tailwind utilities and custom component styles.
This library is built on top of wagmi and provides:
Pre-configured wagmi setup for TEN Protocol - TENWagmiConfig ready to use
All wagmi hooks available - use useAccount, useBalance, useReadContract, etc.
Custom connectors - includes injected wallet connector with TEN Protocol support
TEN Chain configuration - pre-configured chain and transport settings
Setup is simple - just create your wagmi config with TENWagmiConfig and wrap your app with WagmiProvider and QueryClientProvider:
import { TENWagmiConfig, useSessionKeyStore } from '@tenprotocol/ten-kit';
import { useAccount } from 'wagmi';
import { WagmiProvider, createConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const config = createConfig(TENWagmiConfig);
const queryClient = new QueryClient();
function MyApp() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<MyComponent />
</QueryClientProvider>
</WagmiProvider>
);
}
function MyComponent() {
const { address } = useAccount(); // wagmi hook
const { sessionKey } = useSessionKeyStore(); // ten-kit hook
// Both work seamlessly together!
}The easiest way to add wallet connection to your dApp. This all-in-one button handles wallet connection, displays balance, and includes an integrated dropdown menu.
import React from 'react';
import { TENWagmiConfig, TenConnectButton } from '@tenprotocol/ten-kit';
import { useAccount } from 'wagmi';
import { WagmiProvider, createConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const config = createConfig(TENWagmiConfig);
const queryClient = new QueryClient();
const MyDApp = () => (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<div className="p-8 max-w-md mx-auto">
<header className="flex flex-col justify-center items-center">
<h1 className="text-2xl font-bold mb-4">My TEN dApp</h1>
<TenConnectButton />
</header>
<AppContent />
</div>
</QueryClientProvider>
</WagmiProvider>
);
const AppContent = () => {
const { isConnected } = useAccount();
if (!isConnected) {
return <p>Please connect your wallet.</p>;
}
return (
<div className="text-center space-y-4">
<p className="text-gray-600">
Your dApp content goes here. It will only be visible when
the user is connected to TEN Protocol.
</p>
<button className="w-full bg-blue-500 text-white py-2 px-4 rounded">
Private Action
</button>
</div>
);
};
export default MyDApp;For privacy-preserving transactions, enable session key support:
import React from 'react';
import {
TENWagmiConfig,
TenConnectButton,
useSessionKeyStore
} from '@tenprotocol/ten-kit';
import { useAccount } from 'wagmi';
import { WagmiProvider, createConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const config = createConfig(TENWagmiConfig);
const queryClient = new QueryClient();
const MyDApp = () => (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<div className="p-8 max-w-md mx-auto">
<header className="flex flex-col justify-center items-center">
<h1 className="text-2xl font-bold mb-4">My TEN dApp</h1>
{/* Enable session key management in the dropdown */}
<TenConnectButton enableSessionKey />
</header>
<AppContent />
</div>
</QueryClientProvider>
</WagmiProvider>
);
const AppContent = () => {
const { isConnected } = useAccount();
const { sessionKey } = useSessionKeyStore();
if (!isConnected || !sessionKey) {
return <p>Please connect your wallet and create a session key.</p>;
}
return (
<div className="text-center space-y-4">
<p className="text-gray-600">
Your dApp content goes here. All transactions are
privacy-preserving thanks to TEN Protocol.
</p>
<button className="w-full bg-blue-500 text-white py-2 px-4 rounded">
Private Action
</button>
</div>
);
};
export default MyDApp;If you prefer more control, you can use ConnectWalletButton and SessionKeyManager as separate components:
import { ConnectWalletButton, SessionKeyManager } from '@tenprotocol/ten-kit';
// In your component:
<div className="flex gap-4">
<ConnectWalletButton />
<SessionKeyManager />
</div>Use the sendTransaction method from useSessionKeyStore to send privacy-preserving transactions:
import React, { useState } from 'react';
import { useSessionKeyStore } from '@tenprotocol/ten-kit';
import { encodeFunctionData, parseEther } from 'viem';
import { useAccount } from 'wagmi';
const MyContractComponent = () => {
const { isConnected } = useAccount();
const { sendTransaction, sessionKey } = useSessionKeyStore();
const [amount, setAmount] = useState('0.01');
const handleTransaction = async () => {
if (!isConnected || !sessionKey) {
alert('Please connect wallet and create a session key');
return;
}
try {
// Encode contract function call
const data = encodeFunctionData({
abi: YOUR_CONTRACT_ABI,
functionName: 'yourFunction',
args: [/* your args */],
});
// Send transaction through session key
const txHash = await sendTransaction({
to: '0xYourContractAddress',
value: `0x${parseEther(amount).toString(16)}`,
data,
});
console.log('Transaction sent:', txHash);
} catch (error) {
console.error('Transaction failed:', error);
}
};
return (
<div>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
/>
<button onClick={handleTransaction}>
Send Transaction
</button>
</div>
);
};The library exports TENWagmiConfig which provides a pre-configured wagmi setup for TEN Protocol.
What it includes:
- TEN Protocol chain configuration
- Injected wallet connector (MetaMask, Rabby, etc.)
- Transport configuration with fallback support
- All necessary settings to work with TEN Protocol
Basic Usage:
import { TENWagmiConfig } from '@tenprotocol/ten-kit';
import { WagmiProvider, createConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const config = createConfig(TENWagmiConfig);
const queryClient = new QueryClient();
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourDappContent />
</QueryClientProvider>
</WagmiProvider>
);
}Advanced Usage with Custom QueryClient:
import { TENWagmiConfig } from '@tenprotocol/ten-kit';
import { WagmiProvider, createConfig } from 'wagmi';
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});
const config = createConfig(TENWagmiConfig);
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourDappContent />
</QueryClientProvider>
</WagmiProvider>
);
}Customizing the Configuration:
import { TENWagmiConfig, tenChain, TENTransports } from '@tenprotocol/ten-kit';
import { WagmiProvider, createConfig } from 'wagmi';
import { injected, walletConnect } from 'wagmi/connectors';
// Extend the default config with additional connectors
const config = createConfig({
...TENWagmiConfig,
connectors: [
injected(),
walletConnect({ projectId: 'YOUR_PROJECT_ID' }),
],
});
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourDappContent />
</QueryClientProvider>
</WagmiProvider>
);
}The recommended all-in-one button for wallet connection. Displays balance, handles network switching, and includes a dropdown menu with wallet info and disconnect options. Optionally supports session key management.
interface TenConnectButtonProps {
className?: string;
style?: React.CSSProperties;
onChainChange?: (chainId: number, isCorrect: boolean) => void;
gatewayUrl?: string;
onTrackEvent?: (event: string, data: any) => void;
enableSessionKey?: boolean; // Default: false
}Props:
className: Additional CSS classesstyle: Inline styles for reliable style overridesonChainChange: Callback when chain changesgatewayUrl: Custom TEN Gateway URL (optional)onTrackEvent: Callback for tracking eventsenableSessionKey: Enable session key features in dropdown (default:false)
Features:
- Shows "Connect Wallet" button when disconnected
- Displays ETH balance when connected
- Shows session status when
enableSessionKeyis true - Dropdown menu with:
- Wallet Info (address, network details)
- Start/End Session (when
enableSessionKeyis true) - Session Key Manager (when
enableSessionKeyis true) - Disconnect
Usage:
import { TenConnectButton } from '@tenprotocol/ten-kit';
// Basic usage (wallet only)
<TenConnectButton />
// With session key support
<TenConnectButton enableSessionKey />
// With custom styling
<TenConnectButton
enableSessionKey
style={{ backgroundColor: '#3b82f6' }}
className="!rounded-full"
/>A wrapper component that shows your content only when connected to TEN Protocol.
interface ConnectWalletWrapperProps {
children: React.ReactNode;
className?: string;
errorState?: boolean;
loading: boolean;
gatewayUrl?: string;
}Props:
children: Content to show when connectedclassName: Additional CSS classeserrorState: Show error state UIloading: Show loading stategatewayUrl: Custom TEN Gateway URL (optional)
A button component for wallet connection with built-in network detection.
interface ConnectWalletButtonProps {
className?: string;
style?: React.CSSProperties;
onChainChange?: (chainId: number, isCorrect: boolean) => void;
gatewayUrl?: string;
}Props:
className: Additional CSS classes (use!prefix for overrides, e.g.!bg-red-500)style: Inline styles for reliable style overridesonChainChange: Callback when chain changesgatewayUrl: Custom TEN Gateway URL (optional)
Usage:
import { ConnectWalletButton } from '@tenprotocol/ten-kit';
function Header() {
return <ConnectWalletButton />;
}
// With custom styling
function StyledHeader() {
return (
<ConnectWalletButton
style={{ backgroundColor: '#3b82f6' }}
className="!rounded-full"
/>
);
}A complete session key management UI component with create, fund, and delete capabilities.
interface SessionKeyManagerProps {
className?: string;
style?: React.CSSProperties;
}Props:
className: Additional CSS classes (use!prefix for overrides, e.g.!bg-red-500)style: Inline styles for reliable style overrides
Features:
- Create new session keys
- Fund session keys with ETH
- Display session key balance
- Delete session keys
- Automatic balance updates
Usage:
import { SessionKeyManager } from '@tenprotocol/ten-kit';
function Header() {
return (
<div className="flex gap-4">
<ConnectWalletButton />
<SessionKeyManager />
</div>
);
}
// With custom styling
function StyledHeader() {
return (
<div className="flex gap-4">
<ConnectWalletButton style={{ backgroundColor: '#3b82f6' }} />
<SessionKeyManager style={{ backgroundColor: '#10b981' }} />
</div>
);
}Recommended: The main hook for accessing and managing session keys. Use this for sending transactions and checking session key state.
const {
// State
sessionKey,
isActive,
balance,
isLoading,
error,
// Actions
sendTransaction,
createSessionKey,
fundSessionKey,
deleteSessionKey,
updateBalance,
reset,
} = useSessionKeyStore();State:
sessionKey: Current session key address (string | null)isActive: Whether session key is active (boolean)balance: Session key balance object withethandweipropertiesisLoading: Loading state for operations (boolean)error: Error state (Error | null)
Actions:
sendTransaction(txParams): Send a privacy-preserving transaction through the session keytxParams:{ to: string, value: string, data: string }- Returns:
Promise<string>(transaction hash)
waitForReceipt(txHash, timeout?): Wait for transaction receipt and confirmationtxHash: Transaction hash fromsendTransactiontimeout: Optional timeout in milliseconds (default: 30000)- Returns:
Promise<TransactionReceipt>(full receipt with logs and status) - Throws: Error if transaction reverts or timeout is exceeded
createSessionKey(): Create a new session keyfundSessionKey(address, amount, userAddress): Fund a session key with ETHdeleteSessionKey(): Delete the current session keyupdateBalance(): Manually refresh the session key balancereset(): Reset all state
Example:
import { useSessionKeyStore } from '@tenprotocol/ten-kit';
import { encodeFunctionData, parseEther, parseEventLogs } from 'viem';
function MyComponent() {
const { sessionKey, sendTransaction, waitForReceipt } = useSessionKeyStore();
const handleTransaction = async () => {
const data = encodeFunctionData({
abi: MyContractABI,
functionName: 'myFunction',
});
const txHash = await sendTransaction({
to: '0xContractAddress',
value: `0x${parseEther('0.01').toString(16)}`,
data,
});
// Wait for confirmation and get receipt
const receipt = await waitForReceipt(txHash);
console.log('Status:', receipt.status);
// Parse event logs
const events = parseEventLogs({
abi: MyContractABI,
logs: receipt.logs,
});
console.log('Events:', events);
};
return <button onClick={handleTransaction}>Send TX</button>;
}A simplified hook that wraps useSessionKeyStore. Use useSessionKeyStore directly for better performance.
const {
sessionKey,
isActive,
balance,
isLoading,
error,
createSessionKey,
fundSessionKey,
deleteSessionKey,
cleanupSessionKey,
updateBalance,
sendTransaction,
reset,
} = useSessionKey();import {
TEN_ADDRESSES,
TEN_CHAIN_ID,
DEFAULT_GATEWAY_URL,
DEFAULT_TEN_CONFIG,
tenChain,
TENWagmiConfig,
TENTransports,
} from '@ten-protocol/ten-kit';The package exports several utility functions for common operations:
import { formatBalance, shortenAddress } from '@tenprotocol/ten-kit';
// Format ETH balance for display
const formatted = formatBalance('1.23456789'); // '1.23'
// Shorten Ethereum address
const short = shortenAddress('0x1234...5678'); // '0x12...5678'import {
parseEther,
formatEther,
toHex,
hexToBytes,
bytesToHex,
hexToAscii,
} from '@tenprotocol/ten-kit';
// Convert ETH to wei
const wei = parseEther('1.0'); // 1000000000000000000n
// Convert wei to ETH
const eth = formatEther(wei); // '1.0'
// Convert number to hex
const hex = toHex(255); // '0xff'
// Convert hex to bytes
const bytes = hexToBytes('0xff');
// Convert bytes to hex
const hexStr = bytesToHex(bytes);
// Convert hex to ASCII
const ascii = hexToAscii('0x48656c6c6f'); // 'Hello'import {
checkTenNetwork,
calculateGasFees,
getLatestBlockNumber,
} from '@tenprotocol/ten-kit';
// Verify connected to TEN network
await checkTenNetwork(provider);
// Calculate gas fees for transaction
const { maxFeePerGas, maxPriorityFeePerGas } = await calculateGasFees(provider);
// Get latest block number
const blockNumber = await getLatestBlockNumber(provider);Creates configured viem public clients for reading public on-chain data and listening to events without requiring wallet connection.
import { CreatePublicClients, type TENPublicClients } from '@tenprotocol/ten-kit';
// Initialize public clients
const clients: TENPublicClients = await CreatePublicClients();
// Use HTTP client for reading contract data
const totalSupply = await clients.httpsClient.readContract({
address: '0xYourContract',
abi: YOUR_ABI,
functionName: 'totalSupply',
});
const balance = await clients.httpsClient.readContract({
address: '0xYourContract',
abi: YOUR_ABI,
functionName: 'balanceOf',
args: ['0xUserAddress'],
});
// Use WebSocket client for listening to events
const unwatch = clients.websocketClient.watchContractEvent({
address: '0xYourContract',
abi: YOUR_ABI,
eventName: 'Transfer',
onLogs: (logs) => {
console.log('New transfer events:', logs);
},
});
// Clean up when done
unwatch();Returns:
interface TENPublicClients {
httpsClient: PublicClient<Transport, Chain>;
websocketClient: PublicClient<Transport, Chain>;
}Features:
- Automatically manages TEN network authentication tokens
- Caches tokens in localStorage with 24-hour expiration
- Handles token refresh and revocation automatically
- Retry logic with exponential backoff for network failures
- Full viem public client API support
Use Cases:
- Reading contract state without wallet connection
- Watching blockchain events in real-time
- Building read-only dashboards and analytics
- Displaying token balances and supply information
- Monitoring contract events for notifications
See the Fetching Public Data and Listening for Events example for a complete implementation.
This package is written in TypeScript and exports all necessary types:
import type {
// Component Props
ConnectWalletButtonProps,
ConnectWalletWrapperProps,
// Session Key Types
SessionKeyStore,
TransactionParams,
TransactionReceipt,
// Public Clients
TENPublicClients,
// Configuration
TenConfig,
EIP1193Provider,
} from '@tenprotocol/ten-kit';
// Example: Transaction Parameters
const txParams: TransactionParams = {
to: '0x...',
value: '0x0',
data: '0x...',
};
// Example: Working with receipts
const receipt: TransactionReceipt = await waitForReceipt(txHash);
console.log('Status:', receipt.status);
console.log('Gas used:', receipt.gasUsed);
console.log('Logs:', receipt.logs);This package uses Tailwind CSS for styling. You can customize the appearance by:
- Using CSS custom properties (recommended for theming):
:root {
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
/* ... other custom properties */
}- Using the
styleprop (recommended for component-level overrides):
The style prop uses inline styles which reliably override the library's CSS:
<ConnectWalletButton
style={{ backgroundColor: '#ef4444' }}
/>
// With hover simulation using state
<ConnectWalletButton
style={{
backgroundColor: isHovered ? '#dc2626' : '#ef4444',
transition: 'background-color 150ms'
}}
/>- Using Tailwind's
!importantmodifier:
Due to the library's CSS specificity, use Tailwind's ! prefix to override styles:
<ConnectWalletButton className="!bg-blue-500 hover:!bg-blue-600" />Note: Standard Tailwind classes like
bg-red-500without!won't override the library's default styles due to CSS specificity. Use either thestyleprop or the!modifier.
- Using your own Tailwind configuration that includes the component styles.
This package includes Storybook for component development and testing:
npm run storybookVisit http://localhost:6006 to see all components with interactive examples.
npm run buildnpm run devnpm run lint
npm run lint:fixnpm run type-checkThis repository includes standalone example applications in the examples/ directory that use the local TEN Kit package for testing and development.
To run an example:
-
Build the TEN Kit library:
npm run build
-
Navigate to an example:
cd examples/basic-app -
Install dependencies:
npm install
-
Run the example:
npm run dev
See the examples README for more information about available examples and how to create new ones.
For Next.js applications, set up your providers in a client component:
// app/layout.tsx
import '@tenprotocol/ten-kit/styles.css';
import './globals.css';
import Providers from './providers';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}// app/providers.tsx
'use client';
import { TENWagmiConfig } from '@tenprotocol/ten-kit';
import { WagmiProvider, createConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactNode } from 'react';
const config = createConfig(TENWagmiConfig);
const queryClient = new QueryClient();
export default function Providers({ children }: { children: ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}Since this library is built on wagmi, you can use any wagmi hook alongside TEN-specific features:
import { TENWagmiConfig, useSessionKeyStore, ConnectWalletButton } from '@tenprotocol/ten-kit';
import { WagmiProvider, createConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
useAccount,
useBalance,
useBlockNumber,
useReadContract,
useWatchBlockNumber
} from 'wagmi';
const config = createConfig(TENWagmiConfig);
const queryClient = new QueryClient();
function MyComponent() {
// Wagmi hooks work out of the box
const { address, isConnected } = useAccount();
const { data: balance } = useBalance({ address });
const { data: blockNumber } = useBlockNumber();
// TEN-specific hooks
const { sessionKey, sendTransaction } = useSessionKeyStore();
// Read from a contract
const { data: contractData } = useReadContract({
address: '0xYourContract',
abi: YOUR_ABI,
functionName: 'getData',
});
return (
<div>
<p>Address: {address}</p>
<p>Balance: {balance?.formatted} ETH</p>
<p>Block: {blockNumber?.toString()}</p>
<p>Session Key: {sessionKey ? 'Active' : 'None'}</p>
<p>Contract Data: {contractData}</p>
</div>
);
}
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<ConnectWalletButton />
<MyComponent />
</QueryClientProvider>
</WagmiProvider>
);
}Real-world example showing how to interact with a smart contract using session keys:
import { useState } from 'react';
import { useSessionKeyStore } from '@tenprotocol/ten-kit';
import { encodeFunctionData, parseEther } from 'viem';
import { useAccount } from 'wagmi';
import { toast } from 'sonner';
const CONTRACT_ADDRESS = '0xYourContractAddress';
const CONTRACT_ABI = [/* your ABI */];
export default function BettingComponent() {
const { isConnected } = useAccount();
const { sendTransaction, sessionKey } = useSessionKeyStore();
const [betAmount, setBetAmount] = useState('0.01');
const [isLoading, setIsLoading] = useState(false);
const handlePlaceBet = async () => {
if (!isConnected || !sessionKey) {
toast.error('Please connect your wallet and create a session key');
return;
}
setIsLoading(true);
try {
// Encode the contract function call
const data = encodeFunctionData({
abi: CONTRACT_ABI,
functionName: 'placeBet',
});
// Send transaction through session key
const txHash = await sendTransaction({
to: CONTRACT_ADDRESS,
value: `0x${parseEther(betAmount).toString(16)}`,
data,
});
toast.success(`Bet placed! TX: ${txHash}`);
console.log('Transaction hash:', txHash);
} catch (error) {
console.error('Error placing bet:', error);
toast.error('Failed to place bet');
} finally {
setIsLoading(false);
}
};
return (
<div>
<input
type="number"
value={betAmount}
onChange={(e) => setBetAmount(e.target.value)}
placeholder="Bet Amount (ETH)"
min="0.001"
step="0.001"
/>
<button
onClick={handlePlaceBet}
disabled={isLoading || !sessionKey}
>
{isLoading ? 'Processing...' : 'Place Bet'}
</button>
</div>
);
}If you're building complex applications with multiple Zustand stores, you can access the session key store from within your other stores using getState():
import { create } from 'zustand';
import { useSessionKeyStore } from '@tenprotocol/ten-kit';
import { Address, formatEther } from 'viem';
export type GameEvent = {
gameId: bigint;
player: Address;
amount: bigint;
};
export type GameStore = {
playerHasBet: boolean;
bets: Array<{ player: Address; amount: number }>;
handleBetPlaced: (event: GameEvent) => void;
};
export const useGameStore = create<GameStore>()((set, get) => ({
playerHasBet: false,
bets: [],
handleBetPlaced: (event) => {
// Access session key from the session key store
const { sessionKey } = useSessionKeyStore.getState();
// Check if the bet was placed by the current user's session key
const isPlayerBet = sessionKey?.toUpperCase() === event.player.toUpperCase();
if (isPlayerBet) {
set({ playerHasBet: true });
}
// Add bet to the list
set({
bets: [
{
player: event.player,
amount: Number(formatEther(event.amount)),
},
...get().bets,
],
});
},
}));Key Points:
- Use
useSessionKeyStore.getState()to access the store from outside React components - This is useful for event handlers, middleware, or other Zustand stores
- The session key is stored as a string (the address) or
nullif not created - You can also access
sendTransaction,balance, and other store properties this way
Another Example - Checking if User is Connected:
import { create } from 'zustand';
import { useSessionKeyStore } from '@tenprotocol/ten-kit';
export const useAppStore = create()((set) => ({
isReady: false,
checkReadyState: () => {
const { sessionKey, isActive } = useSessionKeyStore.getState();
// Check if user has an active session key
if (sessionKey && isActive) {
set({ isReady: true });
} else {
set({ isReady: false });
}
},
}));For reading public on-chain data and listening to events without requiring a wallet connection, you can use CreatePublicClients to get configured viem public clients:
import { useEffect, useState } from 'react';
import { CreatePublicClients, type TENPublicClients } from '@tenprotocol/ten-kit';
import { formatEther, parseAbi } from 'viem';
const CONTRACT_ADDRESS = '0xYourContractAddress';
const CONTRACT_ABI = parseAbi([
'function totalSupply() view returns (uint256)',
'function balanceOf(address) view returns (uint256)',
'event Transfer(address indexed from, address indexed to, uint256 value)',
]);
function PublicDataComponent() {
const [clients, setClients] = useState<TENPublicClients | null>(null);
const [totalSupply, setTotalSupply] = useState<string>('0');
const [userBalance, setUserBalance] = useState<string>('0');
const [latestTransfer, setLatestTransfer] = useState<string | null>(null);
useEffect(() => {
const initClients = async () => {
try {
const publicClients = await CreatePublicClients();
setClients(publicClients);
} catch (error) {
console.error('Failed to initialize public clients:', error);
}
};
initClients();
}, []);
useEffect(() => {
if (!clients) return;
const fetchData = async () => {
try {
const supply = await clients.httpsClient.readContract({
address: CONTRACT_ADDRESS,
abi: CONTRACT_ABI,
functionName: 'totalSupply',
});
setTotalSupply(formatEther(supply as bigint));
const balance = await clients.httpsClient.readContract({
address: CONTRACT_ADDRESS,
abi: CONTRACT_ABI,
functionName: 'balanceOf',
args: ['0xUserAddress'],
});
setUserBalance(formatEther(balance as bigint));
} catch (error) {
console.error('Error fetching contract data:', error);
}
};
fetchData();
const unwatch = clients.websocketClient.watchContractEvent({
address: CONTRACT_ADDRESS,
abi: CONTRACT_ABI,
eventName: 'Transfer',
onLogs: (logs) => {
logs.forEach((log) => {
if (log.args) {
const { from, to, value } = log.args;
setLatestTransfer(
`${from} → ${to}: ${formatEther(value as bigint)} tokens`
);
}
});
},
});
return () => {
unwatch();
};
}, [clients]);
return (
<div>
<h2>Token Information</h2>
<p>Total Supply: {totalSupply} tokens</p>
<p>User Balance: {userBalance} tokens</p>
{latestTransfer && (
<div>
<h3>Latest Transfer:</h3>
<p>{latestTransfer}</p>
</div>
)}
</div>
);
}
export default PublicDataComponent;Key Features:
- No wallet connection required -
CreatePublicClientsworks independently - Automatic token management - Handles TEN network authentication tokens with caching and expiration
- HTTP and WebSocket support - Get both client types for different use cases
- Standard viem API - Use all viem public client methods (
readContract,watchContractEvent,getBlockNumber, etc.) - Event listening - Real-time event monitoring via WebSocket with automatic reconnection
Common Use Cases:
// Read contract state
const balance = await clients.httpsClient.readContract({
address: CONTRACT_ADDRESS,
abi: YOUR_ABI,
functionName: 'balanceOf',
args: ['0xUserAddress'],
});
// Read multiple contract values
const [supply, paused] = await Promise.all([
clients.httpsClient.readContract({
address: CONTRACT_ADDRESS,
abi: YOUR_ABI,
functionName: 'totalSupply',
}),
clients.httpsClient.readContract({
address: CONTRACT_ADDRESS,
abi: YOUR_ABI,
functionName: 'paused',
}),
]);
// Watch for contract events in real-time
const unwatchEvents = clients.websocketClient.watchContractEvent({
address: CONTRACT_ADDRESS,
abi: CONTRACT_ABI,
eventName: 'MyEvent',
onLogs: (logs) => {
logs.forEach((log) => console.log('New event:', log));
},
});You can customize the TEN configuration for different networks:
import { TENWagmiConfig, TENTransports } from '@tenprotocol/ten-kit';
import { WagmiProvider, createConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { defineChain, http, fallback } from 'viem';
import { injected, unstable_connector } from 'wagmi';
// Define a custom TEN chain
const customTenChain = defineChain({
id: 8443,
name: 'Custom TEN Network',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: {
default: {
http: ['https://your-custom-rpc.com'],
},
},
});
// Create custom transports
const customTransports = {
[customTenChain.id]: fallback([
unstable_connector(injected),
http('https://your-custom-rpc.com'),
]),
};
// Create wagmi config with custom settings
const config = createConfig({
chains: [customTenChain],
connectors: [injected()],
transports: customTransports,
});
const queryClient = new QueryClient();
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
</WagmiProvider>
);
}MIT
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
For support, please visit our documentation or join our Discord community.