import { SimpforFunSDK } from 'simpfor-fun-sdk';
interface TraderCriteria {
minROI: number;
maxDrawdown: number;
minWinRate: number;
minFollowValue: number;
}
class CopyTradingBot {
private sdk: SimpforFunSDK;
private userId: number;
private activeTrades = new Map<string, number>(); // trader -> copyTradeId
constructor() {
this.sdk = new SimpforFunSDK();
}
async initialize(email: string, otp: string) {
const loginResult = await this.sdk.verifyOtp(email, otp);
this.userId = loginResult.data.user.uid;
console.log('Bot initialized for user:', this.userId);
}
async findQualifyingTraders(criteria: TraderCriteria) {
const traders = await this.sdk.fetchTopTraders();
return traders.filter(trader =>
trader.roi >= criteria.minROI &&
trader.maxDrawdown <= criteria.maxDrawdown &&
trader.winRate >= criteria.minWinRate &&
trader.followValue >= criteria.minFollowValue
);
}
async startCopyTrade(traderAddress: string, walletAddress: string) {
try {
// Create copy trade relationship
const copyTrade = await this.sdk.createCopyTrade(
this.userId,
traderAddress,
walletAddress
);
// Start copying
await this.sdk.runCopyTrade(this.userId, copyTrade.data.info.copyTradeId);
// Track active trade
this.activeTrades.set(traderAddress, copyTrade.data.info.copyTradeId);
console.log(`Started copying ${traderAddress}`);
return copyTrade.data.info.copyTradeId;
} catch (error) {
console.error(`Failed to copy ${traderAddress}:`, error);
throw error;
}
}
async monitorPerformance(stopLossPercent: number = 15) {
const monitorInterval = setInterval(async () => {
for (const [traderAddress, copyTradeId] of this.activeTrades) {
try {
const pnl = await this.sdk.getCopyTradeTotalPnl(
this.userId,
'your-wallet-address'
);
// Calculate drawdown (simplified)
const currentDrawdown = Math.abs(pnl.data.pnl) / 1000; // Assuming 1000 USDC base
console.log(`${traderAddress}: ${pnl.data.pnl} USDC PnL`);
// Stop loss check
if (currentDrawdown > stopLossPercent / 100) {
console.log(`Stop loss triggered for ${traderAddress} at ${currentDrawdown * 100}%`);
await this.sdk.stopCopyTrade(this.userId, copyTradeId, true);
this.activeTrades.delete(traderAddress);
}
} catch (error) {
console.error(`Error monitoring ${traderAddress}:`, error);
}
}
// Stop monitoring if no active trades
if (this.activeTrades.size === 0) {
clearInterval(monitorInterval);
console.log('No active trades to monitor');
}
}, 60000); // Check every minute
return monitorInterval;
}
async getPortfolioSummary() {
const copyTrades = await this.sdk.listCopyTrades(this.userId);
const summary = {
activeTrades: 0,
totalPnl: 0,
trades: [] as Array<{
leader: string;
follower: string;
pnl: number;
active: boolean;
}>
};
for (const trade of copyTrades.data.infos) {
if (trade.active) {
summary.activeTrades++;
try {
const pnl = await this.sdk.getCopyTradeTotalPnl(this.userId, trade.followerAccount);
summary.totalPnl += pnl.data.pnl;
summary.trades.push({
leader: trade.leaderAccount,
follower: trade.followerAccount,
pnl: pnl.data.pnl,
active: trade.active
});
} catch (error) {
console.error(`Error getting PnL for ${trade.leaderAccount}:`, error);
}
}
}
return summary;
}
}
// Usage Example
async function runBot() {
const bot = new CopyTradingBot();
// Initialize with your credentials
await bot.initialize('your-email@example.com', '123456');
// Find qualifying traders
const criteria: TraderCriteria = {
minROI: 0.20, // 20% minimum ROI
maxDrawdown: 0.15, // 15% max drawdown
minWinRate: 0.65, // 65% win rate
minFollowValue: 1000 // At least 1000 USDC following
};
const topTraders = await bot.findQualifyingTraders(criteria);
console.log(`Found ${topTraders.length} qualifying traders`);
// Start copying the best traders (limit to top 3)
for (const trader of topTraders.slice(0, 3)) {
try {
await bot.startCopyTrade(trader.address, 'your-wallet-address');
console.log(`Started copying ${trader.address} with ${trader.roi * 100}% ROI`);
} catch (error) {
console.error(`Failed to copy ${trader.address}:`, error);
}
}
// Start monitoring with 15% stop loss
await bot.monitorPerformance(15);
// Get portfolio summary every 5 minutes
setInterval(async () => {
const summary = await bot.getPortfolioSummary();
console.log('Portfolio Summary:', summary);
}, 300000);
}
// Run the bot
runBot().catch(console.error);
import React, { useState, useEffect } from 'react';
import { SimpforFunSDK } from 'simpfor-fun-sdk';
interface DashboardState {
user: any;
traders: any[];
copyTrades: any[];
loading: boolean;
error: string | null;
}
const CopyTradingDashboard: React.FC = () => {
const [sdk] = useState(() => new SimpforFunSDK());
const [state, setState] = useState<DashboardState>({
user: null,
traders: [],
copyTrades: [],
loading: false,
error: null
});
// Login handler
const handleLogin = async (email: string, otp: string) => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const result = await sdk.verifyOtp(email, otp);
setState(prev => ({ ...prev, user: result.data.user }));
await loadDashboardData(result.data.user.uid);
} catch (error: any) {
setState(prev => ({
...prev,
error: `Login failed: ${error.message}`
}));
} finally {
setState(prev => ({ ...prev, loading: false }));
}
};
// Load dashboard data
const loadDashboardData = async (userId: number) => {
try {
const [topTraders, userCopyTrades] = await Promise.all([
sdk.fetchTopTraders(),
sdk.listCopyTrades(userId)
]);
setState(prev => ({
...prev,
traders: topTraders.slice(0, 10),
copyTrades: userCopyTrades.data.infos
}));
} catch (error: any) {
setState(prev => ({
...prev,
error: `Failed to load data: ${error.message}`
}));
}
};
// Start copy trading
const startCopyTrade = async (traderAddress: string) => {
if (!state.user) return;
setState(prev => ({ ...prev, loading: true }));
try {
const copyTrade = await sdk.createCopyTrade(
state.user.uid,
traderAddress,
'your-wallet-address' // Replace with actual wallet
);
await sdk.runCopyTrade(state.user.uid, copyTrade.data.info.copyTradeId);
// Refresh data
await loadDashboardData(state.user.uid);
alert('Copy trading started successfully!');
} catch (error: any) {
setState(prev => ({
...prev,
error: `Failed to start copy trading: ${error.message}`
}));
} finally {
setState(prev => ({ ...prev, loading: false }));
}
};
// Stop copy trading
const stopCopyTrade = async (copyTradeId: number) => {
if (!state.user) return;
setState(prev => ({ ...prev, loading: true }));
try {
await sdk.stopCopyTrade(state.user.uid, copyTradeId, true);
await loadDashboardData(state.user.uid);
alert('Copy trading stopped');
} catch (error: any) {
setState(prev => ({
...prev,
error: `Failed to stop copy trading: ${error.message}`
}));
} finally {
setState(prev => ({ ...prev, loading: false }));
}
};
return (
<div className="dashboard">
<h1>Copy Trading Dashboard</h1>
{state.error && (
<div className="error-banner">
{state.error}
<button onClick={() => setState(prev => ({ ...prev, error: null }))}>
β
</button>
</div>
)}
{!state.user ? (
<LoginForm onLogin={handleLogin} loading={state.loading} />
) : (
<>
<UserInfo user={state.user} />
<TopTraders
traders={state.traders}
onCopy={startCopyTrade}
loading={state.loading}
/>
<CopyTradesList
trades={state.copyTrades}
onStop={stopCopyTrade}
loading={state.loading}
/>
</>
)}
</div>
);
};
// Login Form Component
const LoginForm: React.FC<{
onLogin: (email: string, otp: string) => void;
loading: boolean;
}> = ({ onLogin, loading }) => {
const [email, setEmail] = useState('');
const [otp, setOtp] = useState('');
const [otpSent, setOtpSent] = useState(false);
const [sdk] = useState(() => new SimpforFunSDK());
const sendOtp = async () => {
try {
await sdk.sendVerificationCode(email);
setOtpSent(true);
alert('OTP sent to your email');
} catch (error: any) {
alert(`Failed to send OTP: ${error.message}`);
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onLogin(email, otp);
};
return (
<form onSubmit={handleSubmit} className="login-form">
<div>
<input
type="email"
placeholder="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button type="button" onClick={sendOtp} disabled={!email || otpSent}>
{otpSent ? 'OTP Sent' : 'Send OTP'}
</button>
</div>
{otpSent && (
<div>
<input
type="text"
placeholder="Enter 6-digit OTP"
value={otp}
onChange={(e) => setOtp(e.target.value)}
maxLength={6}
required
/>
<button type="submit" disabled={loading || otp.length !== 6}>
{loading ? 'Logging in...' : 'Login'}
</button>
</div>
)}
</form>
);
};
// User Info Component
const UserInfo: React.FC<{ user: any }> = ({ user }) => (
<div className="user-info">
<h2>Welcome, {user.email}</h2>
<p>User ID: {user.uid}</p>
<p>Referral Code: {user.referralCode}</p>
</div>
);
// Top Traders Component
const TopTraders: React.FC<{
traders: any[];
onCopy: (address: string) => void;
loading: boolean;
}> = ({ traders, onCopy, loading }) => (
<div className="section">
<h2>Top Traders</h2>
<div className="traders-grid">
{traders.map(trader => (
<div key={trader.address} className="trader-card">
<h3>{trader.address.slice(0, 8)}...{trader.address.slice(-6)}</h3>
<div className="trader-stats">
<p><strong>ROI:</strong> {(trader.roi * 100).toFixed(2)}%</p>
<p><strong>PnL:</strong> {trader.pnl.toFixed(2)} USDC</p>
<p><strong>Win Rate:</strong> {(trader.winRate * 100).toFixed(2)}%</p>
<p><strong>Max Drawdown:</strong> {(trader.maxDrawdown * 100).toFixed(2)}%</p>
<p><strong>Followers:</strong> {trader.followValue.toFixed(0)} USDC</p>
</div>
<button
onClick={() => onCopy(trader.address)}
disabled={loading || trader.copying}
className={`copy-btn ${trader.copying ? 'active' : ''}`}
>
{trader.copying ? 'Copying' : loading ? 'Starting...' : 'Copy Trade'}
</button>
</div>
))}
</div>
</div>
);
// Copy Trades List Component
const CopyTradesList: React.FC<{
trades: any[];
onStop: (id: number) => void;
loading: boolean;
}> = ({ trades, onStop, loading }) => (
<div className="section">
<h2>Your Copy Trades</h2>
{trades.length === 0 ? (
<p>No active copy trades</p>
) : (
<div className="trades-list">
{trades.map(trade => (
<div key={trade.copyTradeId} className="trade-item">
<div className="trade-info">
<p><strong>Leader:</strong> {trade.leaderAccount.slice(0, 8)}...{trade.leaderAccount.slice(-6)}</p>
<p><strong>Follower:</strong> {trade.followerAccount.slice(0, 8)}...{trade.followerAccount.slice(-6)}</p>
<p><strong>Status:</strong>
<span className={`status ${trade.active ? 'active' : 'stopped'}`}>
{trade.active ? 'Active' : 'Stopped'}
</span>
</p>
<p><strong>Created:</strong> {new Date(trade.createTime).toLocaleDateString()}</p>
</div>
{trade.active && (
<button
onClick={() => onStop(trade.copyTradeId)}
disabled={loading}
className="stop-btn"
>
{loading ? 'Stopping...' : 'Stop'}
</button>
)}
</div>
))}
</div>
)}
</div>
);
export default CopyTradingDashboard;
import { SimpforFunSDK } from 'simpfor-fun-sdk';
interface TraderAnalytics {
address: string;
roi30d: number;
roi90d: number;
sharpeRatio: number;
maxDrawdown: number;
winRate: number;
totalTrades: number;
avgHoldTime: number;
riskScore: number;
}
class TraderAnalyzer {
private sdk: SimpforFunSDK;
private cache = new Map<string, { data: any; timestamp: number }>();
private readonly CACHE_TTL = 300000; // 5 minutes
constructor() {
this.sdk = new SimpforFunSDK();
}
async getTraderAnalytics(address: string): Promise<TraderAnalytics> {
// Check cache first
const cached = this.cache.get(address);
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
return this.calculateAnalytics(cached.data);
}
// Fetch fresh data
const [snapshot30d, snapshot90d, overview] = await Promise.all([
this.sdk.traderSnapshot(address, '30d'),
this.sdk.traderSnapshot(address, '90d'),
this.sdk.traderOverview(address)
]);
const data = { snapshot30d, snapshot90d, overview };
this.cache.set(address, { data, timestamp: Date.now() });
return this.calculateAnalytics(data);
}
private calculateAnalytics(data: any): TraderAnalytics {
const { snapshot30d, snapshot90d, overview } = data;
// Calculate risk score (0-100, lower is better)
const riskScore = this.calculateRiskScore(
snapshot30d.trader.maxDrawdown,
snapshot30d.trader.sharpeRatio,
overview.traderOverview.averageLeverage
);
return {
address: snapshot30d.trader.address,
roi30d: snapshot30d.trader.roi,
roi90d: snapshot90d.trader.roi,
sharpeRatio: snapshot30d.trader.sharpeRatio,
maxDrawdown: snapshot30d.trader.maxDrawdown,
winRate: snapshot30d.trader.winRate,
totalTrades: snapshot30d.trader.totalTrades || 0,
avgHoldTime: this.calculateAvgHoldTime(snapshot30d.snapshot),
riskScore
};
}
private calculateRiskScore(maxDrawdown: number, sharpeRatio: number, avgLeverage: number): number {
// Weighted risk score
const drawdownScore = Math.min(maxDrawdown * 100, 100); // 0-100
const sharpeScore = Math.max(0, 50 - (sharpeRatio * 10)); // 0-50
const leverageScore = Math.min(avgLeverage * 10, 50); // 0-50
return Math.round((drawdownScore * 0.5) + (sharpeScore * 0.3) + (leverageScore * 0.2));
}
private calculateAvgHoldTime(snapshots: any[]): number {
// Simplified calculation - would need more detailed trade data for accuracy
return snapshots.length > 0 ? snapshots.length * 24 : 0; // Hours
}
async compareTraders(addresses: string[]): Promise<TraderAnalytics[]> {
const analytics = await Promise.all(
addresses.map(addr => this.getTraderAnalytics(addr))
);
// Sort by risk-adjusted return (ROI / risk score)
return analytics.sort((a, b) => {
const scoreA = a.roi30d / (a.riskScore || 1);
const scoreB = b.roi30d / (b.riskScore || 1);
return scoreB - scoreA;
});
}
async findBestTraders(criteria: {
minROI?: number;
maxRisk?: number;
minWinRate?: number;
maxDrawdown?: number;
} = {}): Promise<TraderAnalytics[]> {
const topTraders = await this.sdk.fetchTopTraders();
const analyses = await Promise.all(
topTraders.slice(0, 20).map(trader => this.getTraderAnalytics(trader.address))
);
return analyses.filter(trader => {
if (criteria.minROI && trader.roi30d < criteria.minROI) return false;
if (criteria.maxRisk && trader.riskScore > criteria.maxRisk) return false;
if (criteria.minWinRate && trader.winRate < criteria.minWinRate) return false;
if (criteria.maxDrawdown && trader.maxDrawdown > criteria.maxDrawdown) return false;
return true;
});
}
generateReport(analytics: TraderAnalytics[]): string {
let report = "TRADER ANALYSIS REPORT\n";
report += "=".repeat(50) + "\n\n";
analytics.forEach((trader, index) => {
report += `${index + 1}. ${trader.address.slice(0, 8)}...${trader.address.slice(-6)}\n`;
report += ` 30-day ROI: ${(trader.roi30d * 100).toFixed(2)}%\n`;
report += ` 90-day ROI: ${(trader.roi90d * 100).toFixed(2)}%\n`;
report += ` Sharpe Ratio: ${trader.sharpeRatio.toFixed(2)}\n`;
report += ` Max Drawdown: ${(trader.maxDrawdown * 100).toFixed(2)}%\n`;
report += ` Win Rate: ${(trader.winRate * 100).toFixed(2)}%\n`;
report += ` Risk Score: ${trader.riskScore}/100\n`;
report += ` Avg Hold Time: ${trader.avgHoldTime.toFixed(1)} hours\n\n`;
});
return report;
}
}
// Usage Example
async function analyzeTraders() {
const analyzer = new TraderAnalyzer();
// Find best conservative traders
const conservativeTraders = await analyzer.findBestTraders({
minROI: 0.15, // 15% minimum
maxRisk: 30, // Low risk score
minWinRate: 0.65, // 65% win rate
maxDrawdown: 0.20 // 20% max drawdown
});
console.log(`Found ${conservativeTraders.length} conservative traders`);
console.log(analyzer.generateReport(conservativeTraders.slice(0, 5)));
// Compare specific traders
const specificTraders = [
'trader-address-1',
'trader-address-2',
'trader-address-3'
];
const comparison = await analyzer.compareTraders(specificTraders);
console.log('\nCOMPARISON RESULTS:');
console.log(analyzer.generateReport(comparison));
}
analyzeTraders().catch(console.error);
import { SimpforFunSDK } from 'simpfor-fun-sdk';
interface PortfolioConfig {
maxTraders: number;
rebalanceInterval: number; // milliseconds
performanceWindow: number; // days
stopLossThreshold: number; // percentage
minAllocation: number; // USDC
maxAllocation: number; // USDC
}
class PortfolioRebalancer {
private sdk: SimpforFunSDK;
private userId: number;
private config: PortfolioConfig;
private rebalanceTimer?: NodeJS.Timeout;
constructor(config: PortfolioConfig) {
this.sdk = new SimpforFunSDK();
this.config = config;
}
async initialize(userId: number) {
this.userId = userId;
console.log('Portfolio rebalancer initialized');
}
async startAutoRebalancing() {
console.log('Starting automatic rebalancing...');
this.rebalanceTimer = setInterval(async () => {
try {
await this.rebalancePortfolio();
} catch (error) {
console.error('Rebalancing failed:', error);
}
}, this.config.rebalanceInterval);
// Run initial rebalance
await this.rebalancePortfolio();
}
async stopAutoRebalancing() {
if (this.rebalanceTimer) {
clearInterval(this.rebalanceTimer);
this.rebalanceTimer = undefined;
console.log('Auto-rebalancing stopped');
}
}
async rebalancePortfolio() {
console.log('Starting portfolio rebalance...');
// Get current positions
const currentTrades = await this.sdk.listCopyTrades(this.userId);
const activeTrades = currentTrades.data.infos.filter(trade => trade.active);
// Analyze performance of current traders
const traderPerformance = await Promise.all(
activeTrades.map(async (trade) => {
try {
const snapshot = await this.sdk.traderSnapshot(
trade.leaderAccount,
`${this.config.performanceWindow}d` as any
);
const pnl = await this.sdk.getCopyTradeTotalPnl(
this.userId,
trade.followerAccount
);
return {
copyTradeId: trade.copyTradeId,
leaderAccount: trade.leaderAccount,
followerAccount: trade.followerAccount,
performance: snapshot.trader.roi,
currentPnl: pnl.data.pnl,
sharpeRatio: snapshot.trader.sharpeRatio,
maxDrawdown: snapshot.trader.maxDrawdown,
winRate: snapshot.trader.winRate
};
} catch (error) {
console.error(`Error analyzing ${trade.leaderAccount}:`, error);
return null;
}
})
);
const validPerformance = traderPerformance.filter(p => p !== null);
// Remove underperforming traders
await this.removeUnderperformers(validPerformance);
// Add new high-performing traders if needed
await this.addNewTraders(validPerformance.length);
console.log('Portfolio rebalance complete');
}
private async removeUnderperformers(performance: any[]) {
for (const trader of performance) {
const shouldRemove =
trader.performance < -this.config.stopLossThreshold / 100 ||
trader.currentPnl < -this.config.stopLossThreshold * 10 || // Absolute loss threshold
trader.maxDrawdown > 0.30; // 30% max drawdown
if (shouldRemove) {
console.log(`Removing underperformer: ${trader.leaderAccount}`);
try {
await this.sdk.stopCopyTrade(
this.userId,
trader.copyTradeId,
true // Close positions
);
} catch (error) {
console.error(`Failed to remove ${trader.leaderAccount}:`, error);
}
}
}
}
private async addNewTraders(currentCount: number) {
const spotsAvailable = this.config.maxTraders - currentCount;
if (spotsAvailable <= 0) return;
// Find new high-performing traders
const topTraders = await this.sdk.fetchTopTraders();
const currentTraders = new Set(
(await this.sdk.listCopyTrades(this.userId)).data.infos
.filter(t => t.active)
.map(t => t.leaderAccount)
);
// Filter for traders we're not already copying
const availableTraders = topTraders.filter(trader =>
!currentTraders.has(trader.address) &&
trader.roi > 0.10 && // 10% minimum ROI
trader.winRate > 0.60 && // 60% win rate
trader.maxDrawdown < 0.25 && // 25% max drawdown
trader.followValue > 500 // Some following
);
// Add top performers
const tradersToAdd = availableTraders.slice(0, spotsAvailable);
for (const trader of tradersToAdd) {
try {
console.log(`Adding new trader: ${trader.address}`);
const copyTrade = await this.sdk.createCopyTrade(
this.userId,
trader.address,
'your-wallet-address' // Replace with actual wallet
);
await this.sdk.runCopyTrade(this.userId, copyTrade.data.info.copyTradeId);
} catch (error) {
console.error(`Failed to add ${trader.address}:`, error);
}
}
}
async getPortfolioStatus() {
const trades = await this.sdk.listCopyTrades(this.userId);
const activeTrades = trades.data.infos.filter(t => t.active);
const status = {
totalTrades: activeTrades.length,
maxTrades: this.config.maxTraders,
utilization: (activeTrades.length / this.config.maxTraders * 100).toFixed(1),
trades: [] as any[]
};
for (const trade of activeTrades) {
try {
const pnl = await this.sdk.getCopyTradeTotalPnl(this.userId, trade.followerAccount);
const snapshot = await this.sdk.traderSnapshot(trade.leaderAccount, '30d');
status.trades.push({
leader: trade.leaderAccount.slice(0, 8) + '...',
pnl: pnl.data.pnl.toFixed(2),
roi: (snapshot.trader.roi * 100).toFixed(2),
active: trade.active
});
} catch (error) {
console.error(`Error getting status for ${trade.leaderAccount}:`, error);
}
}
return status;
}
}
// Usage Example
async function runPortfolioManager() {
const config: PortfolioConfig = {
maxTraders: 5,
rebalanceInterval: 3600000, // 1 hour
performanceWindow: 7, // 7 days
stopLossThreshold: 15, // 15%
minAllocation: 100, // 100 USDC minimum
maxAllocation: 1000 // 1000 USDC maximum
};
const rebalancer = new PortfolioRebalancer(config);
await rebalancer.initialize(123); // Your user ID
// Start automatic rebalancing
await rebalancer.startAutoRebalancing();
// Check status every 10 minutes
setInterval(async () => {
const status = await rebalancer.getPortfolioStatus();
console.log('Portfolio Status:', status);
}, 600000);
// Stop after 24 hours (for demo)
setTimeout(async () => {
await rebalancer.stopAutoRebalancing();
console.log('Demo ended');
}, 86400000);
}
runPortfolioManager().catch(console.error);
import { SimpforFunSDK } from 'simpfor-fun-sdk';
interface RetryOptions {
maxRetries: number;
baseDelay: number;
maxDelay: number;
backoffFactor: number;
}
class RobustSDK {
private sdk: SimpforFunSDK;
private defaultRetryOptions: RetryOptions = {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
backoffFactor: 2
};
constructor() {
this.sdk = new SimpforFunSDK();
}
// Retry wrapper for any SDK method
async withRetry<T>(
operation: () => Promise<T>,
options: Partial<RetryOptions> = {}
): Promise<T> {
const opts = { ...this.defaultRetryOptions, ...options };
let lastError: any;
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
lastError = error;
// Don't retry on client errors (4xx)
if (error.code >= 400 && error.code < 500 && error.code !== 429) {
throw error;
}
// Don't retry on last attempt
if (attempt === opts.maxRetries) {
break;
}
// Calculate delay with exponential backoff
const delay = Math.min(
opts.baseDelay * Math.pow(opts.backoffFactor, attempt),
opts.maxDelay
);
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
await this.sleep(delay);
}
}
throw lastError;
}
// Authentication with retry and refresh
async authenticateWithRetry(email: string, otp: string) {
return this.withRetry(async () => {
return await this.sdk.verifyOtp(email, otp);
});
}
// Fetch traders with caching and retry
private traderCache: { data: any[]; timestamp: number } | null = null;
private readonly CACHE_TTL = 300000; // 5 minutes
async fetchTradersRobust(): Promise<any[]> {
// Return cached data if fresh
if (this.traderCache && Date.now() - this.traderCache.timestamp < this.CACHE_TTL) {
return this.traderCache.data;
}
const traders = await this.withRetry(async () => {
return await this.sdk.fetchTopTraders();
});
// Update cache
this.traderCache = {
data: traders,
timestamp: Date.now()
};
return traders;
}
// Copy trade with comprehensive error handling
async createCopyTradeRobust(
userId: number,
leaderAddress: string,
followerAddress: string
) {
try {
// Validate inputs
if (!this.isValidAddress(leaderAddress)) {
throw new Error('Invalid leader address');
}
if (!this.isValidAddress(followerAddress)) {
throw new Error('Invalid follower address');
}
// Check if already copying this trader
const existingTrades = await this.withRetry(async () => {
return await this.sdk.listCopyTrades(userId);
});
const alreadyCopying = existingTrades.data.infos.some(
trade => trade.leaderAccount === leaderAddress && trade.active
);
if (alreadyCopying) {
throw new Error('Already copying this trader');
}
// Create copy trade with retry
const copyTrade = await this.withRetry(async () => {
return await this.sdk.createCopyTrade(userId, leaderAddress, followerAddress);
});
// Start copy trading with retry
await this.withRetry(async () => {
return await this.sdk.runCopyTrade(userId, copyTrade.data.info.copyTradeId);
});
return copyTrade;
} catch (error: any) {
console.error('Copy trade creation failed:', error);
// Provide user-friendly error messages
switch (error.code) {
case 400:
throw new Error('Invalid request. Please check your wallet addresses and try again.');
case 401:
throw new Error('Authentication expired. Please log in again.');
case 409:
throw new Error('Copy trade relationship already exists.');
case 429:
throw new Error('Too many requests. Please wait a moment and try again.');
case 500:
throw new Error('Server error. Please try again later.');
default:
throw new Error(`Copy trade failed: ${error.message || 'Unknown error'}`);
}
}
}
// Batch operations with parallel processing and error isolation
async processBatchOperations<T, R>(
items: T[],
operation: (item: T) => Promise<R>,
maxConcurrency: number = 5
): Promise<Array<{ item: T; result?: R; error?: Error }>> {
const results: Array<{ item: T; result?: R; error?: Error }> = [];
// Process items in batches to control concurrency
for (let i = 0; i < items.length; i += maxConcurrency) {
const batch = items.slice(i, i + maxConcurrency);
const batchPromises = batch.map(async (item) => {
try {
const result = await this.withRetry(() => operation(item));
return { item, result };
} catch (error) {
return { item, error: error as Error };
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
// Health check and status monitoring
async healthCheck(): Promise<{
status: 'healthy' | 'degraded' | 'unhealthy';
checks: Record<string, boolean>;
responseTime: number;
}> {
const startTime = Date.now();
const checks: Record<string, boolean> = {};
try {
// Test basic API connectivity
checks.api = await this.testApiCall();
// Test authentication (if we have credentials)
checks.auth = await this.testAuthentication();
// Test trader data fetching
checks.data = await this.testDataFetching();
} catch (error) {
console.error('Health check failed:', error);
}
const responseTime = Date.now() - startTime;
const healthyChecks = Object.values(checks).filter(Boolean).length;
const totalChecks = Object.keys(checks).length;
let status: 'healthy' | 'degraded' | 'unhealthy';
if (healthyChecks === totalChecks) {
status = 'healthy';
} else if (healthyChecks > 0) {
status = 'degraded';
} else {
status = 'unhealthy';
}
return { status, checks, responseTime };
}
private async testApiCall(): Promise<boolean> {
try {
await this.sdk.fetchTopTraders();
return true;
} catch {
return false;
}
}
private async testAuthentication(): Promise<boolean> {
// This would test if current auth is valid
// Implementation depends on how you store auth state
return true;
}
private async testDataFetching(): Promise<boolean> {
try {
const traders = await this.sdk.fetchTopTraders();
return traders.length > 0;
} catch {
return false;
}
}
private isValidAddress(address: string): boolean {
// Basic address validation
return address && address.length > 20 && address.startsWith('0x');
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage Example
async function demonstrateRobustSDK() {
const robustSDK = new RobustSDK();
// Health check
const health = await robustSDK.healthCheck();
console.log('System Health:', health);
if (health.status === 'unhealthy') {
console.error('System is unhealthy, aborting operations');
return;
}
// Robust trader fetching
try {
const traders = await robustSDK.fetchTradersRobust();
console.log(`Fetched ${traders.length} traders`);
} catch (error) {
console.error('Failed to fetch traders:', error);
}
// Batch processing multiple copy trades
const tradersToFollow = ['trader1', 'trader2', 'trader3'];
const results = await robustSDK.processBatchOperations(
tradersToFollow,
async (traderAddress) => {
return await robustSDK.createCopyTradeRobust(
123, // userId
traderAddress,
'your-wallet-address'
);
}
);
// Process results
results.forEach(({ item, result, error }) => {
if (error) {
console.error(`Failed to copy ${item}:`, error.message);
} else {
console.log(`Successfully started copying ${item}`);
}
});
}
demonstrateRobustSDK().catch(console.error);