UNPKG

@nexuspay/sdk

Version:

🚀 Ultra-simple cross-chain wallet SDK - Initialize with just projectName + apiKey. Bulletproof gasless transactions across EVM/SVM chains with ANY social identifier support

333 lines (332 loc) 13.3 kB
/** * NexusSDK - Ultimate Cross-Chain Wallet Infrastructure * SIMPLE & POWERFUL: Initialize with just projectName + apiKey */ export class NexusSDK { constructor(config) { this.initialized = false; // Validate required parameters if (!config.projectName || !config.apiKey) { throw new Error('❌ NexusSDK requires projectName and apiKey. Get them from your dashboard.'); } this.config = { enableBridging: true, enableGasless: true, ...config, }; // Auto-extract project ID from API key this.projectId = this.extractProjectId(config.apiKey); this.baseURL = config.baseURL || 'https://backend-amber-zeta-94.vercel.app'; this.defaultTimeout = config.timeout || 30000; console.log('🚀 NexusSDK initialized'); console.log(`📁 Project: ${config.projectName}`); console.log(`🔑 Project ID: ${this.projectId}`); console.log(`💳 Gasless: ${this.config.enableGasless ? 'Enabled' : 'Disabled'}`); console.log(`🌉 Bridging: ${this.config.enableBridging ? 'Enabled' : 'Disabled'}`); } /** * Auto-extract project ID from API key with enhanced validation */ extractProjectId(apiKey) { if (!apiKey.startsWith('npay_proj_')) { throw new Error('❌ Invalid API key format. Must start with "npay_proj_"'); } // Format: npay_proj_[project_id]_[key_id]_[type]_[hash] const parts = apiKey.split('_'); if (parts.length < 6) { throw new Error('❌ Invalid API key format. Key appears to be truncated or corrupted.'); } // Extract project ID (everything between proj_ and the last 3 parts) const projectIdParts = parts.slice(2, -3); const projectId = projectIdParts.join('_'); if (!projectId) { throw new Error('❌ Unable to extract project ID from API key.'); } return projectId; } /** * Enhanced API request with bulletproof error handling */ async makeRequest(endpoint, options = {}, timeout) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout || this.defaultTimeout); try { const response = await fetch(`${this.baseURL}${endpoint}`, { ...options, headers: { 'Content-Type': 'application/json', 'X-API-Key': this.config.apiKey, ...options.headers, }, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorData = await response.json().catch(() => ({})); // Enhanced error handling with suggestions const error = { code: errorData.error?.code || 'API_ERROR', message: errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`, statusCode: response.status, retryable: response.status >= 500 || response.status === 429, suggestions: errorData.error?.suggestions || [], details: errorData.error?.details || undefined, }; // Add SDK-specific error suggestions if (error.code === 'PROJECT_NAME_MISMATCH') { error.suggestions = [ `Your API key belongs to a different project than "${this.config.projectName}"`, 'Check your project name in the dashboard', 'Verify you\'re using the correct API key for this project', 'Make sure project name matches exactly (case-sensitive)' ]; } return { success: false, error }; } const data = await response.json(); return { success: true, data }; } catch (error) { clearTimeout(timeoutId); const isAbortError = error instanceof Error && error.name === 'AbortError'; return { success: false, error: { code: isAbortError ? 'TIMEOUT' : 'NETWORK_ERROR', message: isAbortError ? 'Request timed out' : 'Network request failed', details: error instanceof Error ? error.message : String(error), retryable: true, suggestions: isAbortError ? ['Increase timeout in SDK config', 'Check your internet connection'] : ['Check your internet connection', 'Verify the backend URL is correct'] }, }; } } /** * Validate project name against API key (called automatically) */ async validateProjectConnection() { if (this.initialized) return; try { const response = await this.makeRequest(`/v1/projects/validate`, { method: 'POST', body: JSON.stringify({ projectName: this.config.projectName, projectId: this.projectId }), }); if (!response.success) { throw new Error(`❌ Project validation failed: ${response.error?.message}`); } this.initialized = true; console.log('✅ Project validation successful'); } catch (error) { console.error('❌ Project validation failed:', error); throw error; } } // ==================== WALLET OPERATIONS ==================== /** * Create wallet with just socialId and socialType * Everything else is auto-configured */ async createWallet(data) { await this.validateProjectConnection(); const response = await this.makeRequest('/v1/wallets/create', { method: 'POST', body: JSON.stringify({ ...data, projectName: this.config.projectName, enableGasless: data.enableGasless !== false, chains: data.chains || ['ethereum', 'arbitrum', 'solana'], // Default to all chains }), }); if (!response.success) { throw new Error(`❌ Failed to create wallet: ${response.error?.message}`); } console.log('✅ Wallet created successfully'); return response.data; } /** * Get wallet by social ID (simplified) */ async getWallet(socialId, socialType) { await this.validateProjectConnection(); const response = await this.makeRequest(`/v1/wallets/social?socialId=${encodeURIComponent(socialId)}&socialType=${encodeURIComponent(socialType)}&projectName=${encodeURIComponent(this.config.projectName)}`); if (!response.success) { throw new Error(`❌ Failed to get wallet: ${response.error?.message}`); } return response.data; } /** * Get wallet balances across all chains */ async getWalletBalances(socialId, socialType) { const wallet = await this.getWallet(socialId, socialType); const response = await this.makeRequest(`/v1/wallets/${wallet.id}/balances`); if (!response.success) { throw new Error(`❌ Failed to get wallet balances: ${response.error?.message}`); } return response.data; } // ==================== TRANSACTION OPERATIONS ==================== /** * Execute gasless transaction (simplified) */ async sendTransaction(data) { await this.validateProjectConnection(); // Get user wallet address const wallet = await this.getWallet(data.socialId, data.socialType); const userWalletAddress = wallet.addresses[data.chain]; if (!userWalletAddress) { throw new Error(`❌ No wallet found for chain ${data.chain}`); } const response = await this.makeRequest(`/v1/transactions/send`, { method: 'POST', body: JSON.stringify({ project: this.config.projectName, // Use project instead of projectName chain: data.chain, socialId: data.socialId, socialType: data.socialType, to: data.to, value: data.value || '0', data: data.data || undefined, usePaymaster: data.usePaymaster !== false, // Default to gasless }), }); if (!response.success) { throw new Error(`❌ Transaction failed: ${response.error?.message}`); } console.log('✅ Transaction sent successfully'); return response.data; } /** * Transfer tokens (simplified) */ async transferTokens(data) { const token = data.token || 'native'; // For native tokens, send directly if (token === 'native') { return this.sendTransaction({ socialId: data.socialId, socialType: data.socialType, chain: data.chain, to: data.to, value: data.amount, usePaymaster: data.usePaymaster, }); } // For ERC-20 tokens, encode transfer const transferData = this.encodeTokenTransfer(data.to, data.amount); return this.sendTransaction({ socialId: data.socialId, socialType: data.socialType, chain: data.chain, to: token, value: '0', data: transferData, usePaymaster: data.usePaymaster, }); } // ==================== CROSS-CHAIN OPERATIONS ==================== /** * Bridge tokens across chains (simplified) */ async bridgeTokens(data) { if (!this.config.enableBridging) { throw new Error('❌ Bridging is disabled for this SDK instance'); } await this.validateProjectConnection(); const response = await this.makeRequest(`/v1/projects/${this.projectId}/bridge`, { method: 'POST', body: JSON.stringify({ projectName: this.config.projectName, ...data, }), }); if (!response.success) { throw new Error(`❌ Bridge failed: ${response.error?.message}`); } console.log('✅ Bridge transaction initiated'); return response.data; } // ==================== ANALYTICS & MONITORING ==================== /** * Get project analytics */ async getAnalytics(days = 30) { await this.validateProjectConnection(); const response = await this.makeRequest(`/v1/projects/${this.projectId}/analytics?days=${days}&projectName=${encodeURIComponent(this.config.projectName)}`); if (!response.success) { throw new Error(`❌ Failed to get analytics: ${response.error?.message}`); } return response.data; } /** * Get paymaster balances */ async getPaymasterBalances() { await this.validateProjectConnection(); const response = await this.makeRequest(`/v1/projects/${this.projectId}/paymaster/balances?projectName=${encodeURIComponent(this.config.projectName)}`); if (!response.success) { throw new Error(`❌ Failed to get paymaster balances: ${response.error?.message}`); } return response.data; } /** * Health check for all systems */ async healthCheck() { const response = await this.makeRequest(`/v1/health?projectName=${encodeURIComponent(this.config.projectName)}`); if (!response.success) { throw new Error(`❌ Health check failed: ${response.error?.message}`); } return response.data; } // ==================== UTILITY METHODS ==================== /** * Encode ERC-20 token transfer */ encodeTokenTransfer(to, amount) { // ERC-20 transfer function signature: transfer(address,uint256) const methodId = '0xa9059cbb'; const paddedTo = to.replace('0x', '').padStart(64, '0'); const paddedAmount = BigInt(amount).toString(16).padStart(64, '0'); return `${methodId}${paddedTo}${paddedAmount}`; } // ==================== DEVELOPER UTILITIES ==================== /** * Get project configuration */ getProjectInfo() { return { projectName: this.config.projectName, projectId: this.projectId, chains: ['ethereum', 'arbitrum', 'solana'], // Will be dynamic in future }; } /** * Check if gasless transactions are enabled */ isGaslessEnabled() { return this.config.enableGasless !== false; } /** * Check if bridging is enabled */ isBridgingEnabled() { return this.config.enableBridging !== false; } /** * Get SDK version and configuration */ getSDKInfo() { return { version: '1.2.0', config: this.config, }; } }