@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
Markdown
# @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