@shogun-sdk/one-shot
Version:
Shogun SDK - One Shot: React Components and hooks for cross-chain swaps
954 lines (799 loc) • 27.3 kB
Markdown
# @shogun-sdk/one-shot
React hooks and components for cross-chain swap interfaces with support for both EVM and Solana chains.
## Quick Start
### 1. Install the Package
Choose your preferred package manager:
**npm**
```bash
npm install @shogun-sdk/one-shot
```
**pnpm**
```bash
pnpm add @shogun-sdk/one-shot
```
**yarn**
```bash
yarn add @shogun-sdk/one-shot
```
### 2. Set Up Providers and Build Your Swap Interface
Wrap your app with providers and start building:
```typescript
'use client';
import { ShogunBalancesProvider, ShogunQuoteProvider } from '@shogun-sdk/one-shot';
import { useShogunQuote, useTokenBalances } from '@shogun-sdk/one-shot';
import { useState } from 'react';
// 1. Set up providers at your app root
export function ShogunProvider({ children }: { children: React.ReactNode }) {
const api = {
key: process.env.SHOGUN_API_KEY!,
url: process.env.SHOGUN_API_URL!
};
const affiliateWallets = {
solana: process.env.SOLANA_AFFILIATE_WALLET!,
evm: process.env.EVM_AFFILIATE_WALLET!
};
return (
<ShogunBalancesProvider apiKey={process.env.CODEX_API_KEY!}>
<ShogunQuoteProvider
swap={{
tokenIn: { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', decimals: 18, chainId: 1 },
tokenOut: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6, chainId: 8453 },
setLatestSuggestedAutoSlippageValue: (value) => console.log('Slippage:', value),
inputAmount: '1000000000000000000', // 1 ETH
setInputAmount: (amount) => console.log('Amount:', amount),
recipientAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
slippage: 0.5,
dynamicSlippage: false,
}}
system={{
api,
systemFeePercent: 0.01,
userEVMAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
userSolanaAddress: 'YourSolanaAddress',
affiliateWallets,
}}
>
{children}
</ShogunQuoteProvider>
</ShogunBalancesProvider>
);
}
// 2. Build your swap component
function SwapInterface() {
const [amount, setAmount] = useState('1000000000000000000');
// Get quote data
const {
quotes,
isLoading,
errors,
fees,
handleMaxBalanceInput,
inputValue,
setInputValue
} = useShogunQuote();
// Get token balances
const { evmBalances, isLoadingEVM } = useTokenBalances({
userEVMAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
userSolanaAddress: '',
});
if (isLoading) return <div>Loading quote...</div>;
if (errors.balanceError) return <div>Error: {errors.balanceError}</div>;
return (
<div className="swap-interface">
<h2>Cross-Chain Swap</h2>
<div className="input-section">
<label>Amount (ETH)</label>
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="0.0"
/>
<button onClick={() => handleMaxBalanceInput(quotes, fees, true)}>
MAX
</button>
</div>
{quotes && (
<div className="quote-info">
<p>Output: {quotes.outputAmount}</p>
<p>Rate: 1 ETH = {quotes.rate} USDC</p>
<button onClick={() => console.log('Execute swap:', quotes)}>
Swap Now
</button>
</div>
)}
</div>
);
}
// 3. Use in your app
export default function App() {
return (
<ShogunProvider>
<SwapInterface />
</ShogunProvider>
);
}
```
### 3. Explore Advanced Features
Check the sections below for comprehensive examples:
- [Wagmi Integration](#wagmi-integration) - Connect wallets and handle transactions
- [Advanced Components](#advanced-components) - Complete swap interfaces
- [Hooks Reference](#hooks-reference) - All available hooks and their usage
- [Best Practices](#best-practices) - Optimization and error handling
## Features
- 🔄 **Cross-Chain Swaps** - Swap between EVM chains and Solana
- 💰 **Real-Time Balances** - Live token balance tracking across chains
- 📊 **Dynamic Quotes** - Real-time pricing with automatic updates
- 🎯 **Smart Slippage** - Automatic slippage calculation and optimization
- 💸 **Fee Management** - Built-in fee validation and affiliate support
- ⚡ **React Query** - Efficient data fetching and caching
- 🛡️ **TypeScript** - Full type safety and IntelliSense support
- 🎨 **Headless UI** - Unstyled components for maximum customization
## Core Concepts
### Providers Architecture
The SDK uses a two-provider architecture for optimal performance:
1. **ShogunBalancesProvider** - Manages token balances across all supported chains
2. **ShogunQuoteProvider** - Handles swap quotes, fees, and transaction logic
### Key Hooks
- **useShogunQuote** - Access quote data, loading states, and swap operations
- **useShogunBalances** - Direct access to the balance client for custom queries
- **useTokenBalances** - Simplified hook for getting specific token balances
### State Management
The SDK manages complex cross-chain state internally while exposing simple interfaces:
- Automatic quote refetching based on input changes
- Balance updates on wallet/network changes
- Fee calculations with affiliate support
- Error handling and validation
## Wagmi Integration
### Basic Wallet Connection
```typescript
import { useAccount, useConnect, useDisconnect } from 'wagmi';
import { InjectedConnector } from 'wagmi/connectors/injected';
import { useShogunQuote } from '@shogun-sdk/one-shot';
function WalletConnect() {
const { address, isConnected } = useAccount();
const { connect } = useConnect({
connector: new InjectedConnector(),
});
const { disconnect } = useDisconnect();
return (
<div className="wallet-section">
{isConnected ? (
<div>
<p>Connected: {address}</p>
<button onClick={() => disconnect()}>Disconnect</button>
</div>
) : (
<button onClick={() => connect()}>Connect Wallet</button>
)}
</div>
);
}
```
### Complete Swap Interface with Wagmi
```typescript
import { useAccount, useConnect, useDisconnect, useNetwork } from 'wagmi';
import { InjectedConnector } from 'wagmi/connectors/injected';
import { useShogunQuote, useTokenBalances } from '@shogun-sdk/one-shot';
import { useState, useEffect } from 'react';
function AdvancedSwapInterface() {
const { address, isConnected } = useAccount();
const { connect } = useConnect({
connector: new InjectedConnector(),
});
const { disconnect } = useDisconnect();
const { chain } = useNetwork();
const [amount, setAmount] = useState('1000000000000000000'); // 1 ETH
const [selectedTokenIn, setSelectedTokenIn] = useState({
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
decimals: 18,
chainId: 1,
symbol: 'WETH'
});
const [selectedTokenOut, setSelectedTokenOut] = useState({
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
decimals: 6,
chainId: 8453,
symbol: 'USDC'
});
// Get quote with connected wallet
const {
quotes,
isLoading: quoteLoading,
errors,
fees,
handleMaxBalanceInput,
inputValue,
setInputValue
} = useShogunQuote();
// Get balances for connected wallet
const {
evmBalances,
solanaBalances,
isLoadingEVM,
isLoadingSolana
} = useTokenBalances({
userEVMAddress: address || '',
userSolanaAddress: '', // Add your Solana address if needed
tokenIn: selectedTokenIn,
tokenOut: selectedTokenOut
});
// Handle max button click
const handleMaxClick = () => {
handleMaxBalanceInput(quotes, fees, true);
};
if (!isConnected) {
return (
<div className="wallet-connect">
<h2>Connect Your Wallet</h2>
<p>Connect your wallet to start swapping tokens across chains</p>
<button onClick={() => connect()}>Connect Wallet</button>
</div>
);
}
return (
<div className="advanced-swap-interface">
{/* Wallet Info */}
<div className="wallet-info">
<div className="wallet-details">
<p>Connected: {address?.slice(0, 6)}...{address?.slice(-4)}</p>
<p>Network: {chain?.name}</p>
</div>
<button onClick={() => disconnect()}>Disconnect</button>
</div>
{/* Token Selection */}
<div className="token-selection">
<div className="input-token">
<h3>From</h3>
<select
value={selectedTokenIn.address}
onChange={(e) => {
const token = tokenOptions.find(t => t.address === e.target.value);
if (token) setSelectedTokenIn(token);
}}
>
<option value="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2">WETH (Ethereum)</option>
<option value="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48">USDC (Ethereum)</option>
</select>
<div className="amount-input">
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="0.0"
min="0"
step="0.000000000000000001"
/>
<button onClick={handleMaxClick} disabled={isLoadingEVM}>
{isLoadingEVM ? 'Loading...' : 'MAX'}
</button>
</div>
<p className="balance">
Balance: {evmBalances?.[selectedTokenIn.address] || '0'} {selectedTokenIn.symbol}
</p>
</div>
<div className="swap-arrow">↓</div>
<div className="output-token">
<h3>To</h3>
<select
value={selectedTokenOut.address}
onChange={(e) => {
const token = tokenOptions.find(t => t.address === e.target.value);
if (token) setSelectedTokenOut(token);
}}
>
<option value="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913">USDC (Base)</option>
<option value="0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8">USDC (Arbitrum)</option>
</select>
<div className="output-amount">
<span>{quotes?.outputAmount || '0'}</span>
<span className="symbol">{selectedTokenOut.symbol}</span>
</div>
<p className="balance">
Balance: {evmBalances?.[selectedTokenOut.address] || '0'} {selectedTokenOut.symbol}
</p>
</div>
</div>
{/* Quote Information */}
{quotes && (
<div className="quote-details">
<div className="rate">
<span>Rate: 1 {selectedTokenIn.symbol} = {quotes.rate} {selectedTokenOut.symbol}</span>
</div>
<div className="fees">
<span>Network Fee: {fees?.networkFee || '0'}</span>
<span>Protocol Fee: {fees?.protocolFee || '0'}</span>
</div>
</div>
)}
{/* Error Display */}
{errors.balanceError && (
<div className="error">
<p>Balance Error: {errors.balanceError}</p>
</div>
)}
{errors.feeValidationError && (
<div className="error">
<p>Fee Error: {errors.feeValidationError}</p>
</div>
)}
{/* Swap Button */}
<button
className="swap-button"
onClick={() => {
console.log('Executing swap:', quotes);
// Implement swap execution logic here
}}
disabled={!quotes || quoteLoading || !!errors.balanceError || !!errors.feeValidationError}
>
{quoteLoading ? 'Getting Quote...' :
errors.balanceError ? 'Insufficient Balance' :
errors.feeValidationError ? 'Fee Error' :
'Swap Tokens'}
</button>
</div>
);
}
// Token options for the example
const tokenOptions = [
{ address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', decimals: 18, chainId: 1, symbol: 'WETH' },
{ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', decimals: 6, chainId: 1, symbol: 'USDC' },
{ address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6, chainId: 8453, symbol: 'USDC' },
{ address: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', decimals: 6, chainId: 42161, symbol: 'USDC' },
];
```
## Advanced Components
### Custom Provider with State Management
```typescript
import { ShogunBalancesProvider, ShogunQuoteProvider } from '@shogun-sdk/one-shot';
import { createContext, useContext, useState, useMemo } from 'react';
// Custom swap state management
interface SwapState {
tokenIn: Token;
tokenOut: Token;
inputAmount: string;
slippage: number;
recipientAddress: string;
}
const SwapStateContext = createContext<{
state: SwapState;
updateState: (updates: Partial<SwapState>) => void;
} | null>(null);
export function CustomShogunProvider({ children }: { children: React.ReactNode }) {
const [swapState, setSwapState] = useState<SwapState>({
tokenIn: { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', decimals: 18, chainId: 1 },
tokenOut: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6, chainId: 8453 },
inputAmount: '0',
slippage: 0.5,
recipientAddress: ''
});
const updateState = (updates: Partial<SwapState>) => {
setSwapState(prev => ({ ...prev, ...updates }));
};
// Memoize configurations
const api = useMemo(() => ({
key: process.env.SHOGUN_API_KEY!,
url: process.env.SHOGUN_API_URL!
}), []);
const affiliateWallets = useMemo(() => ({
solana: process.env.SOLANA_AFFILIATE_WALLET!,
evm: process.env.EVM_AFFILIATE_WALLET!
}), []);
return (
<SwapStateContext.Provider value={{ state: swapState, updateState }}>
<ShogunBalancesProvider apiKey={process.env.CODEX_API_KEY!}>
<ShogunQuoteProvider
swap={{
tokenIn: swapState.tokenIn,
tokenOut: swapState.tokenOut,
setLatestSuggestedAutoSlippageValue: (value) => updateState({ slippage: value }),
inputAmount: swapState.inputAmount,
setInputAmount: (amount) => updateState({ inputAmount: amount }),
recipientAddress: swapState.recipientAddress,
slippage: swapState.slippage,
dynamicSlippage: false,
}}
system={{
api,
systemFeePercent: 0.01,
userEVMAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
userSolanaAddress: 'YourSolanaAddress',
affiliateWallets,
notifyAboutError: (error) => console.error('Shogun SDK Error:', error)
}}
>
{children}
</ShogunQuoteProvider>
</ShogunBalancesProvider>
</SwapStateContext.Provider>
);
}
// Hook to use swap state
export function useSwapState() {
const context = useContext(SwapStateContext);
if (!context) {
throw new Error('useSwapState must be used within CustomShogunProvider');
}
return context;
}
```
### Multi-Chain Balance Display
```typescript
import { useTokenBalances, useShogunBalances } from '@shogun-sdk/one-shot';
import { useState, useEffect } from 'react';
function MultiChainBalanceDisplay({ userEVMAddress, userSolanaAddress }: {
userEVMAddress: string;
userSolanaAddress: string;
}) {
const [selectedTokens, setSelectedTokens] = useState([
{ address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', decimals: 18, chainId: 1, symbol: 'WETH' },
{ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', decimals: 6, chainId: 1, symbol: 'USDC' },
]);
const { evmBalances, solanaBalances, isLoadingEVM, isLoadingSolana } = useTokenBalances({
userEVMAddress,
userSolanaAddress,
});
const balancesClient = useShogunBalances();
// Calculate total portfolio value
const [totalValue, setTotalValue] = useState(0);
useEffect(() => {
const calculateTotalValue = async () => {
let total = 0;
// Calculate EVM balances value
if (evmBalances) {
for (const [tokenAddress, balance] of Object.entries(evmBalances)) {
try {
const price = await balancesClient.getTokenUSDPrice(tokenAddress, 1);
total += parseFloat(balance) * price.usd;
} catch (error) {
console.error('Error fetching price for', tokenAddress, error);
}
}
}
setTotalValue(total);
};
calculateTotalValue();
}, [evmBalances, balancesClient]);
if (isLoadingEVM || isLoadingSolana) {
return <div className="loading">Loading balances...</div>;
}
return (
<div className="balance-display">
<h2>Portfolio Overview</h2>
<div className="total-value">
<h3>Total Value: ${totalValue.toFixed(2)}</h3>
</div>
<div className="chain-balances">
<div className="evm-balances">
<h4>EVM Chains</h4>
{evmBalances && Object.entries(evmBalances).map(([tokenAddress, balance]) => {
const token = selectedTokens.find(t => t.address === tokenAddress);
return (
<div key={tokenAddress} className="balance-item">
<span>{token?.symbol || tokenAddress.slice(0, 6)}</span>
<span>{parseFloat(balance).toFixed(6)}</span>
</div>
);
})}
</div>
<div className="solana-balances">
<h4>Solana</h4>
{solanaBalances && Object.entries(solanaBalances).map(([tokenAddress, balance]) => (
<div key={tokenAddress} className="balance-item">
<span>{tokenAddress.slice(0, 6)}...</span>
<span>{parseFloat(balance).toFixed(6)}</span>
</div>
))}
</div>
</div>
</div>
);
}
```
## Hooks Reference
### useShogunQuote
The main hook for accessing quote data and swap operations.
```typescript
const {
quotes, // Current quote data
errors, // Error states (feeValidationError, balanceError)
quoteRefetch, // Function to manually refetch quote
fees, // Fee breakdown
isMaxBtnClicked, // Whether max button was clicked
handleMaxBalanceInput, // Function to handle max balance input
setIsMaxBtnClicked, // Set max button state
inputValue, // Current input value
setInputValue, // Set input value
needRecalculateMaxValue, // Whether max value needs recalculation
isLoading, // Loading state
isRefetching, // Refetching state
userInputAddress, // User's input token address
userOutputAddress // User's output token address
} = useShogunQuote();
```
### useTokenBalances
Simplified hook for getting token balances.
```typescript
const {
evmBalances, // EVM token balances object
solanaBalances, // Solana token balances object
isLoadingEVM, // EVM loading state
isLoadingSolana // Solana loading state
} = useTokenBalances({
userEVMAddress: string,
userSolanaAddress: string,
tokenIn?: Token, // Optional: specific input token
tokenOut?: Token // Optional: specific output token
});
```
### useShogunBalances
Direct access to the balance client for custom queries.
```typescript
const balancesClient = useShogunBalances();
// Use client methods directly
const evmBalance = await balancesClient.getEvmWalletBalance(address);
const tokenPrice = await balancesClient.getTokenUSDPrice(tokenAddress, chainId);
```
## Core Types
```typescript
interface Token {
address: string; // Token contract address
decimals: number; // Token decimals (e.g., 18 for ETH)
chainId: number; // Chain ID (e.g., 1 for Ethereum)
symbol?: string; // Token symbol (optional)
}
interface SwapConfig {
tokenIn: Token; // Source token
tokenOut: Token; // Destination token
setLatestSuggestedAutoSlippageValue: (value: number) => void;
inputAmount: string;
setInputAmount: (amount: string) => void;
recipientAddress: string;
slippage: number;
dynamicSlippage?: boolean; // Optional: Enable dynamic slippage for Solana
}
interface SystemConfig {
api: {
key: string;
url: string;
};
systemFeePercent: number;
userEVMAddress: string;
userSolanaAddress: string;
affiliateWallets: {
solana: string;
evm: string;
};
notifyAboutError?: (error: Error) => void; // Optional: Error notification callback
}
interface QuoteContextValue {
quotes: QuoteTypes | undefined;
errors: {
feeValidationError: string | null;
balanceError: string | null;
};
quoteRefetch: () => any;
fees: ICollectedFees | undefined;
isMaxBtnClicked: boolean;
handleMaxBalanceInput: (quote: QuoteTypes | undefined, fees: ICollectedFees | undefined, maxBtnClicked: boolean) => void;
setIsMaxBtnClicked: (isMaxBtnClicked: boolean) => void;
inputValue: string;
setInputValue: (inputValue: string) => void;
needRecalculateMaxValue: boolean;
isLoading: boolean;
isRefetching: boolean;
userInputAddress: string;
userOutputAddress: string;
}
```
## Examples
### Basic Usage
```typescript
import { useShogunQuote } from '@shogun-sdk/one-shot';
function SwapComponent() {
const { quotes, isLoading, errors } = useShogunQuote();
if (isLoading) {
return <div>Loading quote...</div>;
}
if (errors.balanceError) {
return <div>Error: {errors.balanceError}</div>;
}
return (
<div>
<h2>Swap Details</h2>
<pre>{JSON.stringify(quotes, null, 2)}</pre>
</div>
);
}
```
### Complete Example
Here's a complete example of a swap interface:
```typescript
import { useShogunQuote, useShogunBalances } from '@shogun-sdk/one-shot';
import { useState } from 'react';
function SwapInterface() {
const [amount, setAmount] = useState('1000000000000000000'); // 1 ETH
// Get quote
const { quotes, isLoading: quoteLoading, errors } = useShogunQuote();
// Get balances
const balancesClient = useShogunBalances();
if (quoteLoading) {
return <div>Loading...</div>;
}
if (errors.balanceError) {
return <div>Error fetching balances: {errors.balanceError}</div>;
}
if (errors.feeValidationError) {
return <div>Error with fees: {errors.feeValidationError}</div>;
}
return (
<div className="swap-interface">
<h2>Cross-chain Swap</h2>
{/* Amount Input */}
<div className="input-group">
<label>Amount (ETH)</label>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
min="0"
step="0.000000000000000001"
/>
</div>
{/* Swap Button */}
<button
onClick={() => {/* Handle swap */}}
disabled={!quotes || quoteLoading}
>
{quoteLoading ? 'Loading...' : 'Swap'}
</button>
</div>
);
}
```
## Best Practices
### 1. Provider Setup
- Always wrap your app with both providers in the correct order
- Use environment variables for sensitive data
- Memoize configuration objects to prevent unnecessary re-renders
```typescript
// ✅ Good
const api = useMemo(() => ({
key: process.env.SHOGUN_API_KEY!,
url: process.env.SHOGUN_API_URL!
}), []);
// ❌ Bad - creates new object on every render
const api = {
key: process.env.SHOGUN_API_KEY!,
url: process.env.SHOGUN_API_URL!
};
```
### 2. Error Handling
- Implement proper error boundaries
- Use the provided error states from hooks
- Implement the `notifyAboutError` callback for centralized error tracking
```typescript
// ✅ Good error handling
const { quotes, errors, isLoading } = useShogunQuote();
if (errors.balanceError) {
return <div className="error">Insufficient balance: {errors.balanceError}</div>;
}
if (errors.feeValidationError) {
return <div className="error">Fee validation failed: {errors.feeValidationError}</div>;
}
```
### 3. Performance Optimization
- Use `useMemo` for expensive calculations
- Implement proper loading states to improve user experience
- Use the provided refetch functions when needed instead of forcing re-renders
```typescript
// ✅ Good performance
const expensiveCalculation = useMemo(() => {
return calculateComplexValue(quotes, fees);
}, [quotes, fees]);
// ✅ Proper loading states
if (isLoading) return <LoadingSpinner />;
if (isRefetching) return <div>Updating quote...</div>;
```
### 4. Type Safety
- Use TypeScript for better type checking
- Properly type your token configurations
- Use the provided interfaces for better type safety
```typescript
// ✅ Good typing
const tokenIn: Token = {
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
decimals: 18,
chainId: 1,
symbol: 'WETH'
};
```
### 5. State Management
- Keep provider state minimal
- Use local component state for UI-specific data
- Implement proper cleanup in useEffect hooks
```typescript
// ✅ Good state management
useEffect(() => {
const timer = setInterval(refetchQuote, 30000); // Refetch every 30s
return () => clearInterval(timer); // Cleanup
}, [refetchQuote]);
```
## Troubleshooting
### Common Issues and Solutions
#### 1. Provider Issues
```typescript
// ❌ Problem: Providers not in correct order
<ShogunQuoteProvider>
<ShogunBalancesProvider>
<App />
</ShogunBalancesProvider>
</ShogunQuoteProvider>
// ✅ Solution: Correct provider order
<ShogunBalancesProvider apiKey="...">
<ShogunQuoteProvider swap={...} system={...}>
<App />
</ShogunQuoteProvider>
</ShogunBalancesProvider>
```
#### 2. Hook Usage Issues
```typescript
// ❌ Problem: Using hooks outside provider context
function Component() {
const { quotes } = useShogunQuote(); // Error: Hook used outside provider
}
// ✅ Solution: Ensure component is wrapped with providers
<ShogunBalancesProvider>
<ShogunQuoteProvider>
<Component /> {/* Now hooks work properly */}
</ShogunQuoteProvider>
</ShogunBalancesProvider>
```
#### 3. Environment Variables
```typescript
// ✅ Check environment variables are properly set
if (!process.env.SHOGUN_API_KEY) {
throw new Error('SHOGUN_API_KEY environment variable is required');
}
if (!process.env.CODEX_API_KEY) {
throw new Error('CODEX_API_KEY environment variable is required');
}
```
#### 4. Balance Loading Issues
```typescript
// ✅ Handle loading states properly
const { evmBalances, isLoadingEVM, isLoadingSolana } = useTokenBalances({
userEVMAddress: address || '',
userSolanaAddress: solanaAddress || '',
});
// Show loading state
if (isLoadingEVM || isLoadingSolana) {
return <div>Loading balances...</div>;
}
// Handle empty balances
if (!evmBalances || Object.keys(evmBalances).length === 0) {
return <div>No balances found</div>;
}
```
#### 5. Quote Issues
```typescript
// ✅ Debug quote problems
const { quotes, errors, isLoading, quoteRefetch } = useShogunQuote();
// Check for specific errors
if (errors.feeValidationError) {
console.error('Fee validation error:', errors.feeValidationError);
}
if (errors.balanceError) {
console.error('Balance error:', errors.balanceError);
}
// Manual refetch if needed
const handleRetry = () => {
quoteRefetch();
};
```
## Support
- [GitHub Issues](https://github.com/shogun-network/shogun-sdk/issues)
- [Main SDK Documentation](../../README.md)
- [Discord Community](https://discord.gg/gundotfun)
## License
ISC