@shogun-sdk/money-legos
v1.3.48-prod.3
Published
Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.
Readme
@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
npm install @shogun-sdk/money-legospnpm
pnpm add @shogun-sdk/money-legosyarn
yarn add @shogun-sdk/money-legos2. Initialize and Use
Set up the SDK and start making cross-chain operations:
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 - RPC URLs, environment variables
- Usage Examples - NFT purchases, cross-chain swaps, balance checking
- Transaction Signing - EVM and Solana transaction execution
- 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:
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)
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-urlNext.js (.env.local)
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-urlVite (.env)
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 onOption 3: Vite Plugin Configuration
For Vite applications, use the env-compatible plugin:
// 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
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
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
// 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
// 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
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
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
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
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
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
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
new LegoClient(config: LegoClientConfig)Parameters:
config.apiKey(string): Your API key from Shogun dashboardconfig.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 purchaseprops.token(Token): Token to use for paymentprops.userAddress(string): Buyer's wallet address
Returns:
interface LegoResult {
status: boolean;
error?: string;
data?: {
steps: TransactionStep[];
details: PurchaseDetails;
fees: FeeInformation;
};
isLoading: boolean;
refetch: () => Promise<LegoResult>;
}OneShotClient
Constructor
new OneShotClient(apiKey: string, baseUrl: string)Methods
fetchQuote(params: QuoteParams): Promise<QuoteResponse>
Get a quote for cross-chain token swaps.
Parameters:
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:
interface QuoteResponse {
status: boolean;
error?: string;
data?: QuoteTypes;
}ShogunBalancesApiClient
Constructor
new ShogunBalancesApiClient(apiKey: string)Methods
getEvmWalletBalance(address: string): Promise<EvmBalance[]>
Get token balances for an EVM wallet.
Returns:
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
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
const result = await legoClient.MagicEden.SwapForNFT({...});
if (!result.status) {
console.error('Operation failed:', result.error);
return;
}
// Proceed with result.data2. Use TypeScript for Type Safety
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
// ❌ 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
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
// 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
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
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
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
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
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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
// 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
License
ISC
