Whisky React SDK
The Whisky React SDK provides React components and hooks for seamless integration of the Whisky Gaming Protocol into frontend applications.Overview
The React SDK serves as the UI layer, providing:- React Hooks: Custom hooks for protocol interaction
- UI Components: Pre-built gaming components
- Wallet Integration: Seamless wallet connectivity
- State Management: Application state handling
Features
- Unified Package: Combines UI components and React hooks in a single package
- Whisky Protocol Integration: Built specifically for the Whisky Gaming Protocol
- TypeScript Support: Full TypeScript support with comprehensive type definitions
- Wallet Integration: Seamless integration with Solana wallet adapters
- Plugin System: Extensible plugin system for custom functionality
- Transaction Management: Built-in transaction handling and state management
Installation
Copy
npm install @whisky-gaming/ui
Quick Start
Copy
import { WhiskyProvider, useWhisky } from '@whisky-gaming/ui'
function App() {
return (
<WhiskyProvider>
<YourGame />
</WhiskyProvider>
)
}
function YourGame() {
const { play, isPlaying, game } = useWhisky()
const handlePlay = async () => {
await play({
wager: 1000000, // 0.001 SOL
bet: [50, 50], // Equal probability
creator: 'YOUR_CREATOR_ADDRESS',
})
}
return (
<div>
<button onClick={handlePlay} disabled={isPlaying}>
{isPlaying ? 'Playing...' : 'Play'}
</button>
</div>
)
}
Core Components
WhiskyProvider
The main provider that wraps your app and provides access to the Whisky protocol.Copy
import { WhiskyProvider } from '@whisky-gaming/ui'
<WhiskyProvider plugins={[yourCustomPlugin]}>
{children}
</WhiskyProvider>
Copy
interface WhiskyProviderProps {
children: React.ReactNode;
plugins?: WhiskyPlugin[];
config?: WhiskyConfig;
}
WhiskyPlatformProvider
Provides platform-specific functionality like token selection and pool management.Copy
import { WhiskyPlatformProvider } from '@whisky-gaming/ui'
<WhiskyPlatformProvider>
{children}
</WhiskyPlatformProvider>
Hooks
useWhisky()
Main hook for interacting with the Whisky protocol.Copy
const { play, result, isPlaying, game, error } = useWhisky()
Copy
interface WhiskyHookReturn {
play: (params: PlayParams) => Promise<void>;
result: GameResult | null;
isPlaying: boolean;
game: Game | null;
error: Error | null;
reset: () => void;
}
Copy
function GamingComponent() {
const { play, result, isPlaying, game, error } = useWhisky()
const handlePlay = async () => {
try {
await play({
wager: 1000000,
bet: [50, 50],
creator: 'CREATOR_ADDRESS',
clientSeed: 'my_seed_123'
})
} catch (err) {
console.error('Game failed:', err)
}
}
return (
<div>
<button onClick={handlePlay} disabled={isPlaying}>
{isPlaying ? 'Playing...' : 'Play Game'}
</button>
{result && (
<div>
{result.won ? 'You won!' : 'You lost!'}
<p>Payout: {result.payout}</p>
</div>
)}
{error && <div>Error: {error.message}</div>}
</div>
)
}
usePool()
Get pool information for a specific token.Copy
const { pool, loading, error } = usePool(tokenMint, poolAuthority)
tokenMint: Token mint addresspoolAuthority: Pool authority address
Copy
interface PoolHookReturn {
pool: PoolInfo | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
Copy
function PoolInfo({ tokenMint, poolAuthority }) {
const { pool, loading, error } = usePool(tokenMint, poolAuthority)
if (loading) return <div>Loading pool...</div>
if (error) return <div>Error: {error.message}</div>
if (!pool) return <div>Pool not found</div>
return (
<div>
<h3>Pool Information</h3>
<p>Total Liquidity: {pool.totalLiquidity}</p>
<p>Total Plays: {pool.totalPlays}</p>
<p>Min Wager: {pool.minWager}</p>
</div>
)
}
useBalance()
Get balance information for a wallet and token.Copy
const { balance, nativeBalance } = useBalance(wallet, token)
wallet: Wallet public keytoken: Token mint address (optional)
Copy
interface BalanceHookReturn {
balance: number | null;
nativeBalance: number | null;
bonusBalance: number | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
Copy
function BalanceDisplay({ wallet, token }) {
const { balance, nativeBalance, loading } = useBalance(wallet, token)
if (loading) return <div>Loading balance...</div>
return (
<div>
<p>Token Balance: {balance}</p>
<p>SOL Balance: {nativeBalance}</p>
</div>
)
}
useWalletAddress()
Get the current wallet address.Copy
const walletAddress = useWalletAddress()
string | null
Example:
Copy
function WalletInfo() {
const walletAddress = useWalletAddress()
if (!walletAddress) {
return <div>Please connect your wallet</div>
}
return (
<div>
<p>Connected: {walletAddress.substring(0, 8)}...</p>
</div>
)
}
UI Components
TokenValue
Display token values with proper formatting.Copy
import { TokenValue } from '@whisky-gaming/ui'
<TokenValue amount={1000000} token={tokenMeta} />
Copy
interface TokenValueProps {
amount: number;
token: TokenMetadata;
showSymbol?: boolean;
decimals?: number;
className?: string;
}
Copy
function TokenDisplay() {
const usdcToken = {
symbol: 'USDC',
decimals: 6,
name: 'USD Coin'
}
return (
<div>
<TokenValue
amount={1000000}
token={usdcToken}
showSymbol={true}
/>
{/* Displays: "1.00 USDC" */}
</div>
)
}
Canvas
Game canvas component for rendering games.Copy
import { Canvas } from '@whisky-gaming/ui'
<Canvas game={gameBundle} />
Copy
interface CanvasProps {
game: GameBundle;
width?: number;
height?: number;
onGameEnd?: (result: GameResult) => void;
className?: string;
}
Copy
function GameCanvas({ gameBundle }) {
const handleGameEnd = (result) => {
console.log('Game ended:', result)
}
return (
<Canvas
game={gameBundle}
width={800}
height={600}
onGameEnd={handleGameEnd}
/>
)
}
GameButton
Pre-styled button component for gaming actions.Copy
import { GameButton } from '@whisky-gaming/ui'
<GameButton onClick={handlePlay} disabled={isPlaying}>
Play Game
</GameButton>
Copy
interface GameButtonProps {
children: React.ReactNode;
onClick: () => void;
disabled?: boolean;
variant?: 'primary' | 'secondary' | 'success' | 'danger';
size?: 'small' | 'medium' | 'large';
loading?: boolean;
className?: string;
}
TokenSelector
Component for selecting tokens for gaming.Copy
import { TokenSelector } from '@whisky-gaming/ui'
<TokenSelector
selectedToken={selectedToken}
onTokenSelect={setSelectedToken}
tokens={availableTokens}
/>
Copy
interface TokenSelectorProps {
selectedToken: TokenMetadata | null;
onTokenSelect: (token: TokenMetadata) => void;
tokens: TokenMetadata[];
disabled?: boolean;
className?: string;
}
Plugin System
Create custom plugins to extend functionality:Copy
import { WhiskyPlugin } from '@whisky-gaming/ui'
const myPlugin: WhiskyPlugin = async (input, context) => {
// Your custom logic here
return [] // Return additional transaction instructions
}
<WhiskyProvider plugins={[myPlugin]}>
{children}
</WhiskyProvider>
Copy
interface WhiskyPlugin {
name: string;
version: string;
execute: (input: any, context: PluginContext) => Promise<any[]>;
}
Copy
const referralPlugin: WhiskyPlugin = {
name: 'referral',
version: '1.0.0',
execute: async (input, context) => {
const { referralCode } = input
if (referralCode) {
// Add referral logic
return [referralInstruction]
}
return []
}
}
function App() {
return (
<WhiskyProvider plugins={[referralPlugin]}>
<GamingApp />
</WhiskyProvider>
)
}
Referral System
Built-in referral system support:Copy
import { ReferralProvider, useReferral } from '@whisky-gaming/ui'
<ReferralProvider>
<YourApp />
</ReferralProvider>
// In your component
const { referralCode, setReferralCode } = useReferral()
Copy
interface ReferralHookReturn {
referralCode: string | null;
setReferralCode: (code: string) => void;
clearReferralCode: () => void;
referralStats: ReferralStats | null;
}
Copy
function ReferralSystem() {
const { referralCode, setReferralCode, referralStats } = useReferral()
const handleReferralInput = (code: string) => {
setReferralCode(code)
}
return (
<div>
<input
placeholder="Enter referral code"
onChange={(e) => handleReferralInput(e.target.value)}
value={referralCode || ''}
/>
{referralStats && (
<div>
<p>Referrals: {referralStats.totalReferrals}</p>
<p>Earnings: {referralStats.totalEarnings}</p>
</div>
)}
</div>
)
}
Advanced Usage Examples
Complete Gaming Application
Copy
import React from 'react'
import {
WhiskyProvider,
useWhisky,
usePool,
useBalance,
TokenValue,
GameButton,
TokenSelector
} from '@whisky-gaming/ui'
function GamingApp() {
return (
<WhiskyProvider>
<div className="gaming-app">
<h1>Whisky Gaming</h1>
<GameInterface />
</div>
</WhiskyProvider>
)
}
function GameInterface() {
const { play, isPlaying, result, error } = useWhisky()
const [selectedToken, setSelectedToken] = useState(null)
const [wager, setWager] = useState(1000000)
const [bet, setBet] = useState([50, 50])
const { pool } = usePool(selectedToken?.mint)
const { balance } = useBalance(wallet, selectedToken?.mint)
const handlePlay = async () => {
if (!selectedToken || !pool) return
await play({
poolAddress: pool.address,
wager,
bet,
creator: 'CREATOR_ADDRESS',
clientSeed: `game_${Date.now()}`,
creatorFee: 100,
jackpotFee: 50,
metadata: 'Custom Game'
})
}
return (
<div className="game-interface">
<div className="token-section">
<TokenSelector
selectedToken={selectedToken}
onTokenSelect={setSelectedToken}
tokens={availableTokens}
/>
{balance && (
<div className="balance">
Balance: <TokenValue amount={balance} token={selectedToken} />
</div>
)}
</div>
<div className="game-controls">
<input
type="number"
value={wager}
onChange={(e) => setWager(Number(e.target.value))}
placeholder="Wager amount"
/>
<GameButton
onClick={handlePlay}
disabled={isPlaying || !selectedToken || !pool}
loading={isPlaying}
>
{isPlaying ? 'Playing...' : 'Play Game'}
</GameButton>
</div>
{result && (
<div className="game-result">
<h3>{result.won ? 'You Won!' : 'You Lost!'}</h3>
<p>Payout: <TokenValue amount={result.payout} token={selectedToken} /></p>
<p>Multiplier: {result.multiplier}x</p>
</div>
)}
{error && (
<div className="error">
Error: {error.message}
</div>
)}
</div>
)
}
Multi-Game Interface
Copy
function MultiGameInterface() {
const [selectedGame, setSelectedGame] = useState('coinflip')
const games = {
coinflip: {
name: 'Coin Flip',
bet: [50, 50],
description: 'Simple 50/50 chance'
},
dice: {
name: 'Dice Roll',
bet: [16, 16, 16, 16, 16, 20],
description: 'Six-sided dice with slight bias'
},
slots: {
name: 'Slot Machine',
bet: [500, 300, 150, 40, 9, 1],
description: 'Complex slot machine simulation'
}
}
const { play, isPlaying } = useWhisky()
const handleGameSelect = (gameKey) => {
setSelectedGame(gameKey)
}
const handlePlay = async () => {
const game = games[selectedGame]
await play({
wager: 1000000,
bet: game.bet,
creator: 'CREATOR_ADDRESS',
metadata: game.name
})
}
return (
<div className="multi-game">
<div className="game-selector">
{Object.entries(games).map(([key, game]) => (
<button
key={key}
onClick={() => handleGameSelect(key)}
className={selectedGame === key ? 'active' : ''}
>
{game.name}
</button>
))}
</div>
<div className="game-info">
<h3>{games[selectedGame].name}</h3>
<p>{games[selectedGame].description}</p>
</div>
<GameButton onClick={handlePlay} disabled={isPlaying}>
Play {games[selectedGame].name}
</GameButton>
</div>
)
}
Pool Management Interface
Copy
function PoolManagement() {
const [selectedPool, setSelectedPool] = useState(null)
const [depositAmount, setDepositAmount] = useState('')
const { pools, loading } = usePools()
const { depositLiquidity, withdrawLiquidity } = usePoolActions()
const handleDeposit = async () => {
if (!selectedPool || !depositAmount) return
await depositLiquidity(selectedPool.address, Number(depositAmount))
setDepositAmount('')
}
const handleWithdraw = async (lpTokenAmount) => {
if (!selectedPool) return
await withdrawLiquidity(selectedPool.address, lpTokenAmount)
}
return (
<div className="pool-management">
<h2>Pool Management</h2>
<div className="pool-list">
{pools.map(pool => (
<div
key={pool.address}
className={`pool-item ${selectedPool?.address === pool.address ? 'selected' : ''}`}
onClick={() => setSelectedPool(pool)}
>
<TokenValue amount={pool.totalLiquidity} token={pool.token} />
<p>Total Plays: {pool.totalPlays}</p>
<p>Fee: {pool.feeBps / 100}%</p>
</div>
))}
</div>
{selectedPool && (
<div className="pool-actions">
<h3>Manage {selectedPool.token.symbol} Pool</h3>
<div className="deposit-section">
<input
type="number"
value={depositAmount}
onChange={(e) => setDepositAmount(e.target.value)}
placeholder="Amount to deposit"
/>
<GameButton onClick={handleDeposit}>
Deposit
</GameButton>
</div>
<div className="withdraw-section">
<p>Your LP Tokens: {selectedPool.userLpTokens}</p>
<GameButton
onClick={() => handleWithdraw(selectedPool.userLpTokens)}
variant="secondary"
>
Withdraw All
</GameButton>
</div>
</div>
)}
</div>
)
}
Styling and Theming
Custom Styling
Copy
// Custom CSS
.game-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 8px;
color: white;
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.game-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.game-button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
Theme Provider
Copy
import { WhiskyThemeProvider } from '@whisky-gaming/ui'
const customTheme = {
colors: {
primary: '#667eea',
secondary: '#764ba2',
success: '#48bb78',
danger: '#f56565',
background: '#1a202c',
text: '#ffffff'
},
borderRadius: '8px',
spacing: {
small: '8px',
medium: '16px',
large: '24px'
}
}
<WhiskyThemeProvider theme={customTheme}>
<GamingApp />
</WhiskyThemeProvider>
Error Handling
Global Error Boundary
Copy
import { WhiskyErrorBoundary } from '@whisky-gaming/ui'
function App() {
return (
<WhiskyErrorBoundary
fallback={<div>Something went wrong with the gaming system</div>}
>
<WhiskyProvider>
<GamingApp />
</WhiskyProvider>
</WhiskyErrorBoundary>
)
}
Hook Error Handling
Copy
function SafeGamingComponent() {
const { play, error, reset } = useWhisky()
useEffect(() => {
if (error) {
// Log error to monitoring service
console.error('Gaming error:', error)
// Show user-friendly message
toast.error('Game failed. Please try again.')
}
}, [error])
const handlePlay = async () => {
try {
await play(gameParams)
} catch (err) {
// Handle specific errors
if (err.code === 'INSUFFICIENT_BALANCE') {
toast.error('Insufficient balance')
} else if (err.code === 'POOL_NOT_FOUND') {
toast.error('Pool not available')
} else {
toast.error('Unexpected error occurred')
}
}
}
return (
<div>
<GameButton onClick={handlePlay}>
Play Game
</GameButton>
{error && (
<button onClick={reset}>
Reset Game State
</button>
)}
</div>
)
}
Performance Optimization
Memoization
Copy
import React, { useMemo, useCallback } from 'react'
function OptimizedGamingComponent() {
const { play, isPlaying } = useWhisky()
// Memoize expensive calculations
const gameConfig = useMemo(() => ({
bet: [25, 25, 25, 25],
creatorFee: 100,
jackpotFee: 50
}), [])
// Memoize callback
const handlePlay = useCallback(async () => {
await play({
wager: 1000000,
...gameConfig,
metadata: 'Optimized Game'
})
}, [play, gameConfig])
return (
<GameButton onClick={handlePlay} disabled={isPlaying}>
Play Optimized Game
</GameButton>
)
}
Lazy Loading
Copy
import React, { lazy, Suspense } from 'react'
const GameCanvas = lazy(() => import('./GameCanvas'))
const PoolManagement = lazy(() => import('./PoolManagement'))
function LazyGamingApp() {
return (
<WhiskyProvider>
<Suspense fallback={<div>Loading...</div>}>
<GameCanvas />
<PoolManagement />
</Suspense>
</WhiskyProvider>
)
}
Testing
Component Testing
Copy
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { WhiskyProvider } from '@whisky-gaming/ui'
function TestGamingComponent() {
const { play, isPlaying } = useWhisky()
return (
<button onClick={() => play(gameParams)} disabled={isPlaying}>
{isPlaying ? 'Playing...' : 'Play'}
</button>
)
}
test('renders play button', () => {
render(
<WhiskyProvider>
<TestGamingComponent />
</WhiskyProvider>
)
expect(screen.getByText('Play')).toBeInTheDocument()
})
test('handles play action', async () => {
render(
<WhiskyProvider>
<TestGamingComponent />
</WhiskyProvider>
)
const button = screen.getByText('Play')
fireEvent.click(button)
await waitFor(() => {
expect(screen.getByText('Playing...')).toBeInTheDocument()
})
})
Hook Testing
Copy
import { renderHook, act } from '@testing-library/react-hooks'
import { useWhisky } from '@whisky-gaming/ui'
test('useWhisky hook', () => {
const { result } = renderHook(() => useWhisky(), {
wrapper: WhiskyProvider
})
expect(result.current.isPlaying).toBe(false)
expect(result.current.result).toBe(null)
act(() => {
result.current.play(gameParams)
})
expect(result.current.isPlaying).toBe(true)
})
Dependencies
This package requires the following peer dependencies:@whisky-gaming/core: The core Whisky protocol SDK@solana/wallet-adapter-react: Solana wallet integration@solana/web3.js: Solana web3 utilitiesreact: React libraryreact-dom: React DOM
Development
Copy
# Install dependencies
npm install
# Build the package
npm run build
# Development mode with watch
npm run dev
# Type checking
npm run lint
Support
For React SDK support and questions:- Documentation: This documentation
- Examples: GitHub Examples
- Community: Discord Server
- Issues: GitHub Issues
- Email: support@whisky.casino
