UNPKG

@shogun-sdk/money-legos

Version:

Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.

1,749 lines (1,434 loc) 45.1 kB
# @shogun-sdk/money-legos Core SDK for cross-chain swaps, NFT purchases, token balances, and DeFi operations across multiple blockchains. ## Quick Start ### 1. Install the Package Choose your preferred package manager: **npm** ```bash npm install @shogun-sdk/money-legos ``` **pnpm** ```bash pnpm add @shogun-sdk/money-legos ``` **yarn** ```bash yarn add @shogun-sdk/money-legos ``` ### 2. Initialize and Use Set up the SDK and start making cross-chain operations: ```typescript import { init, LegoClient, OneShotClient, ShogunBalancesApiClient } from '@shogun-sdk/money-legos'; // 1. Initialize SDK with RPC URLs (recommended) init({ rpcUrls: { 1: ['https://your-ethereum-rpc-url'], 8453: ['https://your-base-rpc-url'], 42161: ['https://your-arbitrum-rpc-url'], 7565164: ['https://your-solana-rpc-url'], } }); // 2. Initialize clients const legoClient = new LegoClient({ apiKey: 'YOUR_API_KEY' // Get from Shogun dashboard }); const oneShotClient = new OneShotClient( 'YOUR_API_KEY', 'SHOGUN_API_URL' ); const balancesClient = new ShogunBalancesApiClient('YOUR_API_KEY'); // 3. Example: Purchase NFT with any token async function purchaseNFT() { const result = await legoClient.MagicEden.SwapForNFT({ items: [{ address: '0x7E0b0363804C6C79AAb9aB51850bF8F3D561f868', tokenId: '42' }], token: { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH decimals: 18, chainId: 1 }, userAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' }); if (result.status) { console.log('Success! Transaction steps:', result.data.steps); } } // 4. Example: Check token balances async function checkBalances() { const evmBalances = await balancesClient.getEvmWalletBalance( '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' ); console.log('EVM Balances:', evmBalances); } ``` ### 3. Explore All Features Check the sections below for comprehensive examples: - [Configuration Options](#configuration-options) - RPC URLs, environment variables - [Usage Examples](#usage-examples) - NFT purchases, cross-chain swaps, balance checking - [Transaction Signing](#transaction-signing) - EVM and Solana transaction execution - [API Reference](#api-reference) - Complete method documentation ## Features - 🔄 **Cross-Chain Swaps** - Swap tokens between EVM chains and Solana - 🖼️ **NFT Purchases** - Buy NFTs on any chain using any supported token - 💰 **Balance Checking** - Check token balances across multiple chains - 🔗 **Multi-Chain Support** - Ethereum, Base, Arbitrum, BSC, Bera Chain, Solana, Sonic - ⚡ **Transaction Signing** - Built-in support for EVM and Solana transactions - 📊 **Price Feeds** - Real-time token pricing and market data - 🛡️ **Type Safety** - Full TypeScript support with comprehensive types - 🔧 **Flexible Configuration** - Multiple RPC configuration options ## Configuration Options ### RPC URL Configuration #### Option 1: Initialization Function (Recommended) Best for Next.js and when you want full control over RPC URLs: ```typescript import { init } from '@shogun-sdk/money-legos'; init({ rpcUrls: { 1: ['https://your-ethereum-rpc-url'], 8453: ['https://your-base-rpc-url'], 42161: ['https://your-arbitrum-rpc-url'], 56: ['https://your-bsc-rpc-url'], 80094: ['https://your-berachain-rpc-url'], 7565164: ['https://your-solana-rpc-url'], 146: ['https://your-sonic-rpc-url'], } }); ``` #### Option 2: Environment Variables **Node.js (.env)** ```bash RPC_URL_1=https://your-ethereum-rpc-url RPC_URL_8453=https://your-base-rpc-url RPC_URL_42161=https://your-arbitrum-rpc-url RPC_URL_56=https://your-bsc-rpc-url RPC_URL_80094=https://your-berachain-rpc-url RPC_URL_7565164=https://your-solana-rpc-url RPC_URL_146=https://your-sonic-rpc-url ``` **Next.js (.env.local)** ```bash NEXT_PUBLIC_RPC_URL_1=https://your-ethereum-rpc-url NEXT_PUBLIC_RPC_URL_8453=https://your-base-rpc-url NEXT_PUBLIC_RPC_URL_42161=https://your-arbitrum-rpc-url NEXT_PUBLIC_RPC_URL_56=https://your-bsc-rpc-url NEXT_PUBLIC_RPC_URL_80094=https://your-berachain-rpc-url NEXT_PUBLIC_RPC_URL_7565164=https://your-solana-rpc-url NEXT_PUBLIC_RPC_URL_146=https://your-sonic-rpc-url ``` **Vite (.env)** ```bash VITE_RPC_URL_1=https://your-ethereum-rpc-url VITE_RPC_URL_8453=https://your-base-rpc-url VITE_RPC_URL_42161=https://your-arbitrum-rpc-url # ... and so on ``` #### Option 3: Vite Plugin Configuration For Vite applications, use the env-compatible plugin: ```typescript // vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import envCompatible from 'vite-plugin-env-compatible'; export default defineConfig({ plugins: [ react(), envCompatible({ prefix: 'RPC_URL_', }), ], }); ``` ## Supported Chains ```typescript import { ETHEREUM_CHAIN_ID, // 1 BASE_CHAIN_ID, // 8453 ARBITRUM_CHAIN_ID, // 42161 BSC_CHAIN_ID, // 56 BERA_CHAIN_ID, // 80094 SOLANA_CHAIN_ID, // 7565164 SONIC_CHAIN_ID, // 146 getSupportedChains, CHAIN_MAP } from '@shogun-sdk/money-legos'; // Get all supported chains const supportedChains = getSupportedChains(); console.log('Supported chains:', supportedChains); ``` ## Usage Examples ### NFT Purchase Examples #### Basic NFT Purchase ```typescript import { LegoClient } from '@shogun-sdk/money-legos'; const legoClient = new LegoClient({ apiKey: 'YOUR_API_KEY' }); // Purchase single NFT with ETH async function purchaseNFTWithETH() { const result = await legoClient.MagicEden.SwapForNFT({ items: [{ address: '0x7E0b0363804C6C79AAb9aB51850bF8F3D561f868', tokenId: '42' }], token: { address: '0x0000000000000000000000000000000000000000', // ETH decimals: 18, chainId: 1 }, userAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' }); if (result.status) { console.log('Success! Transaction steps:', result.data.steps); // Execute transaction steps... } else { console.error('Failed:', result.error); } } ``` #### Multiple NFTs Purchase ```typescript // Purchase multiple NFTs in one transaction async function purchaseMultipleNFTs() { const result = await legoClient.MagicEden.SwapForNFT({ items: [ { address: '0x7E0b0363804C6C79AAb9aB51850bF8F3D561f868', tokenId: '42' }, { address: '0x7E0b0363804C6C79AAb9aB51850bF8F3D561f868', tokenId: '43' }, { address: '0x7E0b0363804C6C79AAb9aB51850bF8F3D561f868', tokenId: '44' } ], token: { address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', // WBTC decimals: 8, chainId: 1 }, userAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' }); console.log('Multiple NFT purchase result:', result); } ``` #### Different Token Purchases ```typescript // Using USDC async function purchaseWithUSDC() { const result = await legoClient.MagicEden.SwapForNFT({ items: [{ address: '0x...', tokenId: '1' }], token: { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC decimals: 6, chainId: 1 }, userAddress: '0x...' }); } // Using DAI async function purchaseWithDAI() { const result = await legoClient.MagicEden.SwapForNFT({ items: [{ address: '0x...', tokenId: '1' }], token: { address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI decimals: 18, chainId: 1 }, userAddress: '0x...' }); } ``` ### Balance Checking Examples ```typescript import { ShogunBalancesApiClient } from '@shogun-sdk/money-legos'; const balancesClient = new ShogunBalancesApiClient('YOUR_API_KEY'); // Check EVM wallet balances async function checkEVMBalances() { const balances = await balancesClient.getEvmWalletBalance( '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' ); console.log('EVM Balances:', balances); // Access specific token balances balances.forEach(balance => { console.log(`${balance.symbol}: ${balance.balance} (${balance.usdValue} USD)`); }); } // Check Solana token balances async function checkSolanaBalances() { const balances = await balancesClient.getSolanaTokenBalances( 'YourSolanaWalletAddress' ); console.log('Solana Balances:', balances); } // Get token price information async function getTokenPrice() { const tokenPrice = await balancesClient.getTokenUSDPrice( '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH 1 // Ethereum mainnet ); console.log('WETH Price:', tokenPrice); } // Get comprehensive token information async function getTokenInfo() { const tokenInfo = await balancesClient.getTokenInfo( '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH 1 // Ethereum mainnet ); console.log('Token Info:', { name: tokenInfo.name, symbol: tokenInfo.symbol, decimals: tokenInfo.decimals, totalSupply: tokenInfo.totalSupply }); } ``` ### Cross-Chain Swap Examples ```typescript import { OneShotClient } from '@shogun-sdk/money-legos'; const oneShotClient = new OneShotClient('YOUR_API_KEY', 'SHOGUN_API_URL'); // Cross-chain swap: ETH to USDC on Base async function swapETHToUSDCOnBase() { const quote = await oneShotClient.fetchQuote({ srcChain: 1, // Ethereum mainnet destChain: 8453, // Base chain srcToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH destToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base amount: '1000000000000000000', // 1 ETH (in wei) senderAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', slippage: 0.5, // 0.5% slippage tolerance }); if (quote.status) { console.log('Cross-chain swap quote:', quote.data); // Execute the swap... } else { console.error('Failed to get quote:', quote.error); } } // Solana to Ethereum swap async function swapSOLToETH() { const quote = await oneShotClient.fetchQuote({ srcChain: 7565164, // Solana destChain: 1, // Ethereum mainnet srcToken: 'So11111111111111111111111111111111111111112', // SOL destToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH amount: '1000000000', // 1 SOL (in lamports) senderAddress: 'YourSolanaAddress', slippage: 0.5, }); console.log('SOL to ETH quote:', quote); } ``` ## Transaction Signing ### EVM Transaction Execution with Viem ```typescript import { createWalletClient, http, getPublicClient } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { base } from 'viem/chains'; import { LegoClient } from '@shogun-sdk/money-legos'; async function executeEVMTransactionWithViem() { // 1. Setup wallet and client const account = privateKeyToAccount('0x...'); // Your private key const client = createWalletClient({ chain: base, transport: http(), account, }); const legoClient = new LegoClient({ apiKey: 'YOUR_API_KEY' }); try { // 2. Get transaction data const { data } = await legoClient.MagicEden.SwapForNFT({ token: { address: '0x0000000000000000000000000000000000000000', // ETH decimals: 18, chainId: 8453, // Base chain }, items: [{ address: '0x72d876d9cdf4001b836f8e47254d0551eda2eebb', tokenId: '32', }], userAddress: account.address, }); if (!data) { console.error('No transaction data returned'); return; } // 3. Execute each transaction step for (const step of data.steps) { console.log(`Executing: ${step.description || 'Transaction step'}`); const txHash = await client.sendTransaction({ to: step.to as `0x${string}`, data: step.data as `0x${string}`, value: BigInt(step.value), chainId: step.chainId, gas: BigInt(step.gas), maxFeePerGas: BigInt(step.maxFeePerGas), maxPriorityFeePerGas: BigInt(step.maxPriorityFeePerGas), }); console.log(`Transaction successful! Hash: ${txHash}`); } } catch (error) { console.error('Transaction execution failed:', error); } } ``` ### Advanced EVM Transaction Handling ```typescript import { QuoteTypes } from '@shogun-sdk/money-legos'; async function handleAdvancedEVMTransaction( quote: QuoteTypes, signer: any, chain: any ): Promise<{ hash: string; swapPlacementTimestamp: number; swapExecutionTimestamp: number } | null> { try { if (Array.isArray(quote.calldatas) || !signer) return null; const provider = getPublicClient({ chain }); // Estimate gas with buffer const estimatedGas = await provider.estimateGas({ to: quote.calldatas.to as `0x${string}`, data: quote.calldatas.data as `0x${string}`, value: BigInt(quote.calldatas?.value ?? 0), account: signer.account, }); // Add 20% buffer to estimated gas const gas = BigInt(Math.ceil(Number(estimatedGas) * 1.2)); const swapPlacementTimestamp = Date.now(); const hash = await signer.sendTransaction({ to: quote.calldatas.to as `0x${string}`, data: quote.calldatas.data as `0x${string}`, value: BigInt(quote.calldatas?.value ?? 0), account: signer.account, gas, chain, }); // Wait for confirmation const tx = await provider.waitForTransactionReceipt({ hash, retryCount: 5, confirmations: 1, }); const swapExecutionTimestamp = Date.now(); if (tx.status !== 'success') { throw new Error(`Transaction failed: ${tx.status}`); } return { hash, swapPlacementTimestamp, swapExecutionTimestamp }; } catch (error) { console.error('EVM transaction error:', error); throw error; } } ``` ### Solana Transaction Execution ```typescript import { VersionedTransaction } from '@solana/web3.js'; import bs58 from 'bs58'; import { sendTransactionUsingJito, sendBundleUsingJito, isValidSolanaSignature, checkTransactionConfirmation, confirmTransaction, } from '@shogun-sdk/money-legos'; async function handleSolanaTransaction( quote: QuoteTypes, address: string, walletSigner: any ): Promise<{ transactionHash: string; swapPlacementTimestamp: number; swapExecutionTimestamp: number } | null> { if (!Array.isArray(quote.calldatas)) return null; try { // Deserialize transactions const transactions = await Promise.all( quote.calldatas.map(async (calldata) => { const messageBuffer = Buffer.from(calldata.data, 'base64'); return VersionedTransaction.deserialize(messageBuffer); }), ); // Sign all transactions const signedTransactions = await walletSigner.signAllTransactions!( transactions, address, ); if (!signedTransactions) { throw new Error('Failed to sign transactions'); } // Convert to base58 format const base58Transactions = signedTransactions.map((tx) => bs58.encode(tx.serialize()) ); const swapPlacementTimestamp = Date.now(); // Handle single vs bundle transactions let result; if (quote.calldatas.length === 1) { result = await handleSingleSolanaTransaction(base58Transactions[0]); } else { result = await handleSolanaBundleTransaction( base58Transactions, transactions[0] ); } const swapExecutionTimestamp = Date.now(); return { transactionHash: result.transactionHash, swapPlacementTimestamp, swapExecutionTimestamp, }; } catch (error) { console.error('Solana transaction error:', error); throw error; } } // Single transaction execution async function handleSingleSolanaTransaction( base58Transaction: string ): Promise<{ transactionHash: string }> { const transactionHash = await sendTransactionUsingJito(base58Transaction); const confirmation = await confirmTransaction(transactionHash, { maxRetries: 50, commitment: 'confirmed', checkInterval: 200, }); if (!confirmation.success) { throw new Error(`Transaction failed: ${confirmation.error}`); } return { transactionHash }; } // Bundle transaction execution async function handleSolanaBundleTransaction( base58Transactions: string[], firstTransaction: VersionedTransaction, ): Promise<{ transactionHash: string }> { // Extract signature from first transaction const transactionSignature = firstTransaction?.signatures?.[0]; if (!transactionSignature) { throw new Error('Missing transaction signature'); } const transactionHash = bs58.encode(transactionSignature); if (!isValidSolanaSignature(transactionHash)) { throw new Error('Invalid transaction signature format'); } // Send bundle to Jito const bundleID = await sendBundleUsingJito(base58Transactions); // Wait for confirmation const confirmed = await checkTransactionConfirmation( provider, firstTransaction, transactionHash ); if (!confirmed) { throw new Error('Bundle transaction failed to confirm'); } return { transactionHash }; } ``` ## Error Handling ### Comprehensive Error Handling ```typescript async function robustNFTPurchase() { try { const result = await legoClient.MagicEden.SwapForNFT({ items: [{ address: '0x123...', tokenId: '1' }], token: { address: '0xabc...', decimals: 18, chainId: 1 }, userAddress: '0xdef...' }); if (!result.status) { // Handle specific API errors switch (result.error) { case 'INSUFFICIENT_BALANCE': console.error('❌ Not enough tokens to complete purchase'); // Show user-friendly message to top up balance break; case 'INVALID_NFT': console.error('❌ NFT not found or unavailable'); // Redirect to valid NFT selection break; case 'PRICE_CHANGED': console.error('❌ Price changed during transaction'); // Offer to retry with new price break; case 'NETWORK_ERROR': console.error('❌ Network connection issues'); // Suggest checking internet connection break; case 'API_RATE_LIMIT': console.error('❌ Too many requests, please wait'); // Implement exponential backoff break; default: console.error(`❌ Purchase failed: ${result.error}`); } return { success: false, error: result.error }; } // Success case console.log('✅ Purchase data ready:', result.data); return { success: true, data: result.data }; } catch (error) { // Handle unexpected errors if (error instanceof Error) { console.error('💥 Unexpected error:', { message: error.message, stack: error.stack, name: error.name }); // Network errors if (error.message.includes('fetch')) { console.error('Network issue detected'); } // JSON parsing errors if (error.message.includes('JSON')) { console.error('API response format issue'); } } else { console.error('💥 Unknown error type:', error); } return { success: false, error: 'Unexpected error occurred' }; } } // Retry mechanism with exponential backoff async function purchaseWithRetry(maxRetries = 3) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`🔄 Attempt ${attempt}/${maxRetries}`); const result = await robustNFTPurchase(); if (result.success) { return result; } lastError = result.error; // Don't retry on certain errors if (['INSUFFICIENT_BALANCE', 'INVALID_NFT'].includes(result.error)) { break; } } catch (error) { lastError = error; console.error(`❌ Attempt ${attempt} failed:`, error); } // Exponential backoff delay if (attempt < maxRetries) { const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s... console.log(`⏳ Waiting ${delay}ms before retry...`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw new Error(`All ${maxRetries} attempts failed. Last error: ${lastError}`); } ``` ## API Reference ### LegoClient #### Constructor ```typescript new LegoClient(config: LegoClientConfig) ``` **Parameters:** - `config.apiKey` (string): Your API key from Shogun dashboard - `config.baseUrl` (string, optional): Custom API base URL #### Methods ##### `MagicEden.SwapForNFT(props: FetchLegoProps): Promise<LegoResult>` Purchase NFTs using any supported token. **Parameters:** - `props.items` (NFTItem[]): Array of NFT items to purchase - `props.token` (Token): Token to use for payment - `props.userAddress` (string): Buyer's wallet address **Returns:** ```typescript interface LegoResult { status: boolean; error?: string; data?: { steps: TransactionStep[]; details: PurchaseDetails; fees: FeeInformation; }; isLoading: boolean; refetch: () => Promise<LegoResult>; } ``` ### OneShotClient #### Constructor ```typescript new OneShotClient(apiKey: string, baseUrl: string) ``` #### Methods ##### `fetchQuote(params: QuoteParams): Promise<QuoteResponse>` Get a quote for cross-chain token swaps. **Parameters:** ```typescript interface QuoteParams { srcChain: number; // Source chain ID destChain: number; // Destination chain ID srcToken: string; // Source token address destToken: string; // Destination token address amount: string; // Amount to swap (in smallest unit) senderAddress: string; // Sender's wallet address slippage: number; // Slippage tolerance (0.5 = 0.5%) } ``` **Returns:** ```typescript interface QuoteResponse { status: boolean; error?: string; data?: QuoteTypes; } ``` ### ShogunBalancesApiClient #### Constructor ```typescript new ShogunBalancesApiClient(apiKey: string) ``` #### Methods ##### `getEvmWalletBalance(address: string): Promise<EvmBalance[]>` Get token balances for an EVM wallet. **Returns:** ```typescript interface EvmBalance { tokenAddress: string; symbol: string; name: string; decimals: number; balance: string; usdValue: number; chainId: number; } ``` ##### `getSolanaTokenBalances(address: string): Promise<SolanaBalance[]>` Get token balances for a Solana wallet. ##### `getTokenUSDPrice(tokenAddress: string, chainId: number): Promise<TokenPrice>` Get current USD price for a token. ##### `getTokenInfo(tokenAddress: string, chainId: number): Promise<TokenInfo>` Get comprehensive token information. ### Core Types ```typescript interface NFTItem { address: string; // NFT contract address tokenId: string; // NFT token ID } interface Token { address: string; // Token contract address (use '0x0000...' for native ETH) decimals: number; // Token decimals (18 for ETH, 6 for USDC, etc.) chainId: number; // Chain ID (1 for Ethereum, 8453 for Base, etc.) } interface TransactionStep { to: string; // Contract address to call data: string; // Transaction data value: string; // ETH value to send chainId: number; // Chain to execute on gas: string; // Gas limit maxFeePerGas: string; // Max fee per gas maxPriorityFeePerGas: string; // Max priority fee description?: string; // Human-readable description } ``` ## Best Practices ### 1. Always Validate Response Status ```typescript const result = await legoClient.MagicEden.SwapForNFT({...}); if (!result.status) { console.error('Operation failed:', result.error); return; } // Proceed with result.data ``` ### 2. Use TypeScript for Type Safety ```typescript import { LegoClient, NFTItem, Token, TransactionStep } from '@shogun-sdk/money-legos'; const items: NFTItem[] = [ { address: '0x...', tokenId: '42' } ]; const token: Token = { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', decimals: 18, chainId: 1 }; ``` ### 3. Secure API Key Management ```typescript // ❌ Don't hardcode API keys const client = new LegoClient({ apiKey: 'sk_live_abc123...' }); // ✅ Use environment variables const client = new LegoClient({ apiKey: process.env.SHOGUN_API_KEY! }); // ✅ Validate API key exists if (!process.env.SHOGUN_API_KEY) { throw new Error('SHOGUN_API_KEY environment variable is required'); } ``` ### 4. Implement Proper Error Boundaries ```typescript class ShogunSDKError extends Error { constructor( message: string, public code: string, public originalError?: any ) { super(message); this.name = 'ShogunSDKError'; } } async function safeExecute<T>(operation: () => Promise<T>): Promise<T> { try { return await operation(); } catch (error) { if (error instanceof ShogunSDKError) { throw error; } throw new ShogunSDKError( 'Unexpected error in Shogun SDK operation', 'UNKNOWN_ERROR', error ); } } ``` ### 5. Gas Optimization ```typescript // Add buffer to gas estimates const estimatedGas = await provider.estimateGas({...}); const gasWithBuffer = BigInt(Math.ceil(Number(estimatedGas) * 1.2)); // 20% buffer // Monitor gas prices for optimal timing const gasPrice = await provider.getGasPrice(); console.log(`Current gas price: ${gasPrice} gwei`); ``` ## Common Use Cases ### 1. Cross-Chain NFT Marketplace ```typescript class CrossChainNFTMarketplace { constructor(private apiKey: string) {} async purchaseNFTFromAnyChain( nftContract: string, tokenId: string, paymentToken: Token, buyerAddress: string ) { const legoClient = new LegoClient({ apiKey: this.apiKey }); const result = await legoClient.MagicEden.SwapForNFT({ items: [{ address: nftContract, tokenId }], token: paymentToken, userAddress: buyerAddress }); if (!result.status) { throw new Error(`Purchase failed: ${result.error}`); } return result.data; } } ``` ### 2. Portfolio Tracker ```typescript class PortfolioTracker { constructor(private apiKey: string) { this.balancesClient = new ShogunBalancesApiClient(apiKey); } async getFullPortfolio(evmAddress: string, solanaAddress: string) { const [evmBalances, solanaBalances] = await Promise.all([ this.balancesClient.getEvmWalletBalance(evmAddress), this.balancesClient.getSolanaTokenBalances(solanaAddress) ]); const totalUSDValue = [ ...evmBalances, ...solanaBalances ].reduce((sum, balance) => sum + balance.usdValue, 0); return { evmBalances, solanaBalances, totalUSDValue, lastUpdated: new Date().toISOString() }; } } ``` ### 3. Cross-Chain DeFi Aggregator ```typescript class DeFiAggregator { constructor(private apiKey: string, private baseUrl: string) { this.oneShotClient = new OneShotClient(apiKey, baseUrl); } async findBestSwapRoute( fromToken: Token, toToken: Token, amount: string, userAddress: string ) { // Get quotes for different routes const routes = [ { srcChain: fromToken.chainId, destChain: toToken.chainId }, { srcChain: fromToken.chainId, destChain: 1 }, // Via Ethereum { srcChain: fromToken.chainId, destChain: 8453 }, // Via Base ]; const quotes = await Promise.allSettled( routes.map(route => this.oneShotClient.fetchQuote({ srcChain: route.srcChain, destChain: route.destChain, srcToken: fromToken.address, destToken: toToken.address, amount, senderAddress: userAddress, slippage: 0.5 }) ) ); // Find the best quote (highest output amount) const validQuotes = quotes .filter((result): result is PromiseFulfilledResult<any> => result.status === 'fulfilled' && result.value.status ) .map(result => result.value.data); return validQuotes.sort((a, b) => parseFloat(b.outputAmount) - parseFloat(a.outputAmount) )[0]; } } ``` ## Hyperliquid Integration The Hyperliquid integration provides comprehensive trading capabilities for perpetual futures and spot trading on the Hyperliquid exchange. ### Quick Start ```typescript import { Hyperliquid } from '@shogun-sdk/money-legos'; // Initialize Hyperliquid client const hyperliquid = new Hyperliquid({ privateKey: 'YOUR_PRIVATE_KEY', // Optional, required for trading testnet: false, // Set to true for testnet walletAddress: '0x...', // Optional, if different from private key address vaultAddress: '0x...', // Optional, for vault operations }); // Connect and initialize await hyperliquid.connect(); ``` ### Configuration Options ```typescript interface HyperliquidConfig { privateKey?: string; // Private key for trading operations testnet?: boolean; // Use testnet environment walletAddress?: string; // Wallet address if different from private key vaultAddress?: string; // Vault address for vault operations maxReconnectAttempts?: number; // Max reconnection attempts } ``` ### Trading Operations #### Place Orders ```typescript // Market order (IoC with slippage) const marketOrder = await hyperliquid.custom.marketOpen( 'BTC', // Symbol true, // is_buy 0.1, // Size undefined, // Price (optional, uses market price) 0.05, // Slippage (5%) 'cloid-123' // Client order ID (optional) ); // Limit order const limitOrder = await hyperliquid.exchange.placeOrder({ coin: 'BTC', is_buy: true, sz: 0.1, limit_px: 45000, order_type: { limit: { tif: 'Gtc' } }, // Good-til-canceled reduce_only: false, cloid: 'my-order-123' }); // Stop-loss order const stopLossOrder = await hyperliquid.exchange.placeOrder({ coin: 'BTC', is_buy: false, sz: 0.1, limit_px: 43000, order_type: { trigger: { triggerPx: 44000, isMarket: true, tpsl: 'sl' } }, reduce_only: true }); // Take-profit order const takeProfitOrder = await hyperliquid.exchange.placeOrder({ coin: 'BTC', is_buy: false, sz: 0.1, limit_px: 47000, order_type: { trigger: { triggerPx: 46000, isMarket: false, tpsl: 'tp' } }, reduce_only: true }); ``` #### Order Types and Time-in-Force ```typescript // Available order types type OrderType = { limit?: { tif: 'Alo' | 'Ioc' | 'Gtc' }; // Add liquidity only, Immediate or cancel, Good til canceled trigger?: { triggerPx: string | number; isMarket: boolean; tpsl: 'tp' | 'sl'; // Take profit or stop loss }; }; // Multiple orders in one transaction const multipleOrders = await hyperliquid.exchange.placeOrder({ orders: [ { coin: 'BTC', is_buy: true, sz: 0.1, limit_px: 45000, order_type: { limit: { tif: 'Gtc' } }, reduce_only: false }, { coin: 'ETH', is_buy: true, sz: 1.0, limit_px: 3000, order_type: { limit: { tif: 'Gtc' } }, reduce_only: false } ], grouping: 'na' // 'na' | 'normalTpsl' | 'positionTpsl' }); ``` #### Cancel Orders ```typescript // Cancel specific order const cancelResponse = await hyperliquid.exchange.cancelOrder({ coin: 'BTC', o: 123456789 // Order ID }); // Cancel by client order ID const cancelByCloid = await hyperliquid.exchange.cancelOrderByCloid( 'BTC', 'my-order-123' ); // Cancel multiple orders const cancelMultiple = await hyperliquid.exchange.cancelOrder([ { coin: 'BTC', o: 123456789 }, { coin: 'ETH', o: 987654321 } ]); // Cancel all orders for a symbol const cancelAllBTC = await hyperliquid.custom.cancelAllOrders('BTC'); // Cancel all orders for all symbols const cancelAll = await hyperliquid.custom.cancelAllOrders(); ``` #### Modify Orders ```typescript // Modify single order const modifyResponse = await hyperliquid.exchange.modifyOrder( 123456789, // Order ID { coin: 'BTC', is_buy: true, sz: 0.2, // New size limit_px: 45500, // New price order_type: { limit: { tif: 'Gtc' } }, reduce_only: false } ); // Batch modify orders const batchModify = await hyperliquid.exchange.batchModifyOrders([ { oid: 123456789, order: { coin: 'BTC', is_buy: true, sz: 0.2, limit_px: 45500, order_type: { limit: { tif: 'Gtc' } }, reduce_only: false } }, { oid: 987654321, order: { coin: 'ETH', is_buy: true, sz: 2.0, limit_px: 3100, order_type: { limit: { tif: 'Gtc' } }, reduce_only: false } } ]); ``` ### Position Management #### Close Positions ```typescript // Close specific position const closePosition = await hyperliquid.custom.marketClose( 'BTC', // Symbol 0.1, // Size (optional, closes entire position if not specified) undefined, // Price (optional) 0.05, // Slippage 'close-123' // Client order ID (optional) ); // Close all positions const closeAllPositions = await hyperliquid.custom.closeAllPositions(0.05); ``` #### Update Leverage ```typescript // Update leverage for a symbol const updateLeverage = await hyperliquid.exchange.updateLeverage( 'BTC', // Symbol 'cross', // 'cross' or 'isolated' 10 // Leverage amount ); // Update isolated margin const updateMargin = await hyperliquid.exchange.updateIsolatedMargin( 'BTC', // Symbol true, // is_buy 1000 // New margin amount ); ``` ### Market Data and Information #### Get Market Data ```typescript // Get all market prices const allMids = await hyperliquid.info.getAllMids(); console.log('BTC Price:', allMids['BTC']); // Get order book const orderBook = await hyperliquid.info.getL2Book('BTC'); console.log('Bids:', orderBook.levels[0]); console.log('Asks:', orderBook.levels[1]); // Get candlestick data const candles = await hyperliquid.info.getCandleSnapshot( 'BTC', '1h', // Interval: '1m', '5m', '15m', '1h', '4h', '1d' Date.now() - 86400000, // Start time (24 hours ago) Date.now() // End time ); // Get perpetual market info const perpMeta = await hyperliquid.info.perpetuals.getPerpMeta(); console.log('Available perpetuals:', perpMeta.universe); // Get spot market info const spotMeta = await hyperliquid.info.spot.getSpotMeta(); console.log('Available spot pairs:', spotMeta.universe); ``` #### Get User Data ```typescript const userAddress = '0x...'; // Get user positions const positions = await hyperliquid.info.perpetuals.getClearinghouseState(userAddress); console.log('Positions:', positions.assetPositions); // Get open orders const openOrders = await hyperliquid.info.getUserOpenOrders(userAddress); console.log('Open orders:', openOrders); // Get trade history const fills = await hyperliquid.info.getUserFills(userAddress); console.log('Recent fills:', fills); // Get fills by time range const fillsByTime = await hyperliquid.info.getUserFillsByTime( userAddress, Date.now() - 86400000, // Start time Date.now() // End time ); // Get user portfolio const portfolio = await hyperliquid.info.getUserPortfolio(userAddress); console.log('Portfolio data:', portfolio); // Get spot balances const spotBalances = await hyperliquid.info.spot.getSpotClearinghouseState(userAddress); console.log('Spot balances:', spotBalances.balances); ``` ### TWAP Orders ```typescript // Place TWAP order const twapOrder = await hyperliquid.exchange.placeTwapOrder({ coin: 'BTC', is_buy: true, sz: 1.0, // Total size reduce_only: false, minutes: 30, // Execute over 30 minutes randomize: true // Randomize execution timing }); console.log('TWAP ID:', twapOrder.response.data.status.running.twapId); // Cancel TWAP order const cancelTwap = await hyperliquid.exchange.cancelTwapOrder({ coin: 'BTC', twap_id: twapOrder.response.data.status.running.twapId }); // Get TWAP fills const twapFills = await hyperliquid.info.getUserTwapSliceFills(userAddress); console.log('TWAP fills:', twapFills); ``` ### Transfers and Withdrawals #### USDC Transfers ```typescript // Transfer USDC to another address const usdcTransfer = await hyperliquid.exchange.usdTransfer( '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // Destination 100 // Amount in USDC ); // Initiate withdrawal const withdrawal = await hyperliquid.exchange.initiateWithdrawal( '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // Destination 100 // Amount in USDC ); ``` #### Spot Token Transfers ```typescript // Transfer spot tokens const spotTransfer = await hyperliquid.exchange.spotTransfer( '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // Destination 'PURR', // Token symbol '1000' // Amount ); // Transfer between spot and perp accounts const spotPerpTransfer = await hyperliquid.exchange.transferBetweenSpotAndPerp( 100, // USDC amount true // true = to perp, false = to spot ); ``` ### Vault Operations ```typescript // Initialize with vault address const vaultHyperliquid = new Hyperliquid({ privateKey: 'YOUR_PRIVATE_KEY', vaultAddress: '0x...' }); // Deposit to vault const vaultDeposit = await hyperliquid.exchange.vaultTransfer( '0x...', // Vault address true, // is_deposit 1000 // Amount in USDC ); // Withdraw from vault const vaultWithdraw = await hyperliquid.exchange.vaultTransfer( '0x...', // Vault address false, // is_deposit 500 // Amount in USDC ); // Get vault details const vaultDetails = await hyperliquid.info.getVaultDetails( '0x...', // Vault address userAddress // User address (optional) ); // Get user vault equities const vaultEquities = await hyperliquid.info.getUserVaultEquities(userAddress); ``` ### Advanced Features #### Spot Trading ```typescript // Get spot assets const spotAssets = await hyperliquid.custom.getAllAssets(); console.log('Spot assets:', spotAssets.spot); // Spot market buy const spotBuy = await hyperliquid.exchange.placeOrder({ coin: 'PURR/USDC', is_buy: true, sz: 1000, limit_px: 0.001, order_type: { limit: { tif: 'Gtc' } }, reduce_only: false }); ``` #### Batch Operations ```typescript // Schedule order cancellation const scheduleCancel = await hyperliquid.exchange.scheduleCancel( Date.now() + 3600000 // Cancel all orders in 1 hour ); // Set referrer code const setReferrer = await hyperliquid.exchange.setReferrer('YOUR_REFERRER_CODE'); // Approve agent for trading const approveAgent = await hyperliquid.exchange.approveAgent({ agentAddress: '0x...', agentName: 'Trading Bot' }); ``` #### Rate Limiting and Status ```typescript // Get user rate limit status const rateLimit = await hyperliquid.info.getUserRateLimit(userAddress); console.log('Rate limit status:', rateLimit); // Get order status const orderStatus = await hyperliquid.info.getOrderStatus( userAddress, 123456789 // Order ID ); console.log('Order status:', orderStatus); // Check if authenticated const isAuthenticated = hyperliquid.isAuthenticated(); console.log('Authenticated:', isAuthenticated); ``` ### Error Handling ```typescript async function safeTrading() { try { const order = await hyperliquid.exchange.placeOrder({ coin: 'BTC', is_buy: true, sz: 0.1, limit_px: 45000, order_type: { limit: { tif: 'Gtc' } }, reduce_only: false }); if (order.status === 'ok') { console.log('Order placed successfully:', order.response); } else { console.error('Order failed:', order.response); } } catch (error) { if (error.message.includes('Invalid or missing private key')) { console.error('Authentication required for trading operations'); } else if (error.message.includes('Unknown asset')) { console.error('Invalid symbol provided'); } else if (error.message.includes('Invalid price')) { console.error('Price does not meet tick size requirements'); } else { console.error('Trading error:', error); } } } ``` ### Best Practices #### 1. Price Precision ```typescript // Hyperliquid has specific tick sizes for each asset // Use the built-in price validation const isValidPrice = await hyperliquid.custom.isValidPrice('BTC', 45000.5); if (!isValidPrice) { // Adjust price to valid tick size const adjustedPrice = await hyperliquid.custom.adjustPrice('BTC', 45000.5); } ``` #### 2. Order Management ```typescript // Always use client order IDs for tracking const cloid = `order-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const order = await hyperliquid.exchange.placeOrder({ coin: 'BTC', is_buy: true, sz: 0.1, limit_px: 45000, order_type: { limit: { tif: 'Gtc' } }, reduce_only: false, cloid: cloid }); // Store the mapping for later reference orderTracker.set(cloid, order); ``` #### 3. Position Sizing ```typescript // Check account balance before placing orders const positions = await hyperliquid.info.perpetuals.getClearinghouseState(userAddress); const accountValue = parseFloat(positions.marginSummary.accountValue); const availableBalance = parseFloat(positions.withdrawable); // Calculate position size based on risk management const riskPercentage = 0.02; // 2% risk per trade const stopLossDistance = 1000; // $1000 stop loss const maxPositionSize = (accountValue * riskPercentage) / stopLossDistance; ``` #### 4. Symbol Conversion ```typescript // The SDK handles symbol conversion automatically // Internal symbols may differ from display symbols const internalSymbol = await hyperliquid.info.getInternalName('BTC-PERP'); const displaySymbol = await hyperliquid.symbolConversion.convertSymbol(internalSymbol, 'forward'); ``` ### Types and Interfaces ```typescript // Key interfaces for trading interface HyperliquidOrder { coin: string; // Asset symbol is_buy: boolean; // Buy or sell sz: number; // Size limit_px: number; // Limit price order_type: OrderType; // Order type and TIF reduce_only: boolean; // Reduce only flag cloid?: string; // Client order ID } interface OrderResponse { status: string; response: { type: string; data: { statuses: Array<{ resting?: { oid: number }; filled?: { oid: number; totalSz: string; avgPx: string }; }>; }; }; } // Position data interface Position { coin: string; entryPx: string; leverage: { type: string; value: number }; liquidationPx: string; marginUsed: string; positionValue: string; szi: string; // Signed size unrealizedPnl: string; } ``` ## Troubleshooting ### Common Issues and Solutions #### 1. API Key Issues ```typescript // Validate API key format const isValidAPIKey = (key: string) => { return key.startsWith('sk_') && key.length > 20; }; if (!isValidAPIKey(process.env.SHOGUN_API_KEY!)) { throw new Error('Invalid API key format'); } ``` #### 2. RPC Configuration Issues ```typescript // Test RPC connectivity async function testRPCConnection(chainId: number) { try { const provider = getPublicClient({ chain: { id: chainId } as any, transport: http() }); const blockNumber = await provider.getBlockNumber(); console.log(`✅ Chain ${chainId} connected, latest block: ${blockNumber}`); return true; } catch (error) { console.error(`❌ Chain ${chainId} connection failed:`, error); return false; } } ``` #### 3. Transaction Failures ```typescript // Debug transaction failures async function debugTransaction(txHash: string, chainId: number) { const provider = getPublicClient({ chain: { id: chainId } as any }); try { const receipt = await provider.getTransactionReceipt({ hash: txHash as `0x${string}` }); if (receipt.status === 'reverted') { console.error('Transaction reverted'); // Try to get revert reason const tx = await provider.getTransaction({ hash: txHash as `0x${string}` }); console.log('Transaction data:', tx); } } catch (error) { console.error('Failed to get transaction receipt:', error); } } ``` #### 4. Balance Issues ```typescript // Check if user has sufficient balance async function validateBalance( userAddress: string, tokenAddress: string, requiredAmount: string, chainId: number ) { const balancesClient = new ShogunBalancesApiClient(process.env.SHOGUN_API_KEY!); if (chainId === 7565164) { // Solana const balances = await balancesClient.getSolanaTokenBalances(userAddress); const tokenBalance = balances.find(b => b.tokenAddress === tokenAddress); return tokenBalance && parseFloat(tokenBalance.balance) >= parseFloat(requiredAmount); } else { // EVM const balances = await balancesClient.getEvmWalletBalance(userAddress); const tokenBalance = balances.find(b => b.tokenAddress === tokenAddress); return tokenBalance && parseFloat(tokenBalance.balance) >= parseFloat(requiredAmount); } } ``` ## Support - [GitHub Issues](https://github.com/shogun-network/shogun-sdk/issues) - [Main SDK Documentation](../../README.md) - [Discord Community](https://discord.gg/gundotfun) ## License ISC