TypeScript Examples - Solana

Complete TypeScript examples for trading on Solana (Jupiter integration)

Prerequisites

npm install @solana/web3.js bs58

Environment Variables

export NAOS_API_KEY="your-api-key"
export NAOS_API_DOMAIN="https://api.naos.trade"
export TRADING_SOL_PKEY="your-solana-private-key-base58"

Setup

import { Keypair, VersionedTransaction } from '@solana/web3.js'
import bs58 from 'bs58'

const API_BASE_URL = process.env.NAOS_API_DOMAIN || 'http://localhost:3001'
const API_KEY = process.env.NAOS_API_KEY || 'your-api-key-here'
const TRADING_SOL_PKEY = process.env.TRADING_SOL_PKEY

interface SwapStep {
  input: string
  output: string
  router: string
  quoter: string
  fee: number
  tick: number
  hook: string
  poolType: string
  address: string
}

interface QuoteResponse {
  success: boolean
  path?: Array<{ step: SwapStep }>
  stats?: {
    valueAmountInUsd: number
    amountOut: string
    amountOutMin: string
    priceImpact?: number
    txCostUsd?: number
  }
  transaction?: {
    trade: string
    quoteId: string
  }
  error?: string
}

async function makeRequest<T>(endpoint: string, options?: RequestInit): Promise<T> {
  const response = await fetch(`${API_BASE_URL}${endpoint}`, {
    method: options?.method || 'GET',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      ...(options?.headers as Record<string, string> || {})
    },
    body: options?.body
  })

  if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`)
  return await response.json() as T
}

Get Quote

async function getQuote(params: {
  token: string
  amount: number
  side: 'BUY' | 'SELL'
  slippage: number
  trader: string
  chain: string
  simulation?: boolean  // Optional: run transaction simulation
}): Promise<QuoteResponse> {
  const queryParams = new URLSearchParams({
    token: params.token,
    amount: params.amount.toString(),
    side: params.side,
    slippage: params.slippage.toString(),
    trader: params.trader,
    chain: params.chain
  })

  if (params.simulation) queryParams.append('simulation', 'true')

  const response = await makeRequest<QuoteResponse>(`/api/v1/quote?${queryParams.toString()}`)

  if (response.success && response.stats) {
    console.log('Quote received:')
    console.log(`  Value: $${response.stats.valueAmountInUsd.toFixed(2)}`)
    console.log(`  Amount Out: ${response.stats.amountOut}`)
    console.log(`  Price Impact: ${response.stats.priceImpact?.toFixed(2) || 'N/A'}%`)
    console.log(`  TX Cost: $${response.stats.txCostUsd?.toFixed(4) || 'N/A'}`)
    console.log(`  Quote ID: ${response.transaction?.quoteId}`)
  }

  return response
}

Execute Transaction

async function executeTransaction(params: {
  txData: string
  network: string
  mev: boolean
  skipSimulation: boolean
  quoteId: string
}): Promise<any> {
  const response = await makeRequest<any>('/api/v1/execute', {
    method: 'POST',
    body: JSON.stringify(params)
  })

  if (response.success) {
    console.log('Transaction executed:')
    console.log(`  Status: ${response.status}`)
    console.log(`  TX Hash: ${response.txRes?.txHash}`)
    console.log(`  Spent: ${response.txRes?.spent}`)
    console.log(`  Received: ${response.txRes?.received}`)
  }

  return response
}

Simple Buy Example

async function simpleBuySolana(): Promise<void> {
  if (!TRADING_SOL_PKEY) {
    throw new Error('TRADING_SOL_PKEY environment variable not set')
  }

  const keypair = Keypair.fromSecretKey(bs58.decode(TRADING_SOL_PKEY))
  const traderAddress = keypair.publicKey.toBase58()

  const params = {
    token: '9fLGtf1rRkYd6Dy93hvYh2r8T8Wy2EPa6WHC5ibgpump',
    trader: traderAddress,
    amount: 1,
    slippage: 1000
  }

  console.log('Getting quote...')
  const quote = await getQuote({
    token: params.token,
    amount: params.amount,
    side: 'BUY',
    slippage: params.slippage,
    trader: params.trader,
    chain: 'SOL'
  })

  if (!quote.success || !quote.transaction?.trade) {
    throw new Error(quote.error || 'Quote failed')
  }

  console.log('Signing transaction with Solana keypair...')
  const txBuffer = Buffer.from(quote.transaction.trade, 'base64')
  const transaction = VersionedTransaction.deserialize(txBuffer)
  transaction.sign([keypair])
  const signedTx = Buffer.from(transaction.serialize()).toString('base64')

  console.log('Executing transaction...')
  const result = await executeTransaction({
    txData: signedTx,
    network: 'SOL',
    mev: false,
    skipSimulation: false,
    quoteId: quote.transaction.quoteId
  })

  console.log('Trade completed!')
  console.log(`TX Hash: ${result.txRes?.txHash}`)
  console.log(`Explorer: https://solscan.io/tx/${result.txRes?.txHash}`)
}

Full Trading Flow (Buy → Sell)

Complete example showing buy and then sell 50% of holdings:

async function solanaFullTradingFlow(): Promise<void> {
  if (!TRADING_SOL_PKEY) {
    throw new Error('TRADING_SOL_PKEY environment variable not set')
  }

  const keypair = Keypair.fromSecretKey(bs58.decode(TRADING_SOL_PKEY))
  const traderAddress = keypair.publicKey.toBase58()

  const params = {
    token: 'FDcjznQLP6KLCgrEPF35PNpFMebmGLDhPji8TCmVdkK8',
    amount: 0.1,
    slippage: 1000
  }

  console.log('='.repeat(80))
  console.log('BUY STEP')
  console.log('='.repeat(80))
  console.log(`Token: ${params.token}`)
  console.log(`Chain: SOL`)
  console.log(`Trader: ${traderAddress}`)
  console.log(`Amount: ${params.amount} SOL`)
  console.log(`Slippage: ${params.slippage / 100}%`)

  console.log('\nGetting token info...')
  const tokenInfo = await makeRequest<any>(`/api/v1/token-info?tokenAddress=${params.token}&chain=SOL`)
  const tokenDecimals = tokenInfo.tokenInfo?.token?.decimals || 6
  console.log(`Token decimals: ${tokenDecimals}`)

  const buyQuote = await getQuote({
    token: params.token,
    amount: params.amount,
    side: 'BUY',
    slippage: params.slippage,
    trader: traderAddress,
    chain: 'SOL'
  })

  if (!buyQuote.success || !buyQuote.transaction?.trade) {
    throw new Error(buyQuote.error || 'Failed to get buy quote')
  }

  console.log('\nSigning buy transaction with Solana keypair...')
  const buyTxBuffer = Buffer.from(buyQuote.transaction.trade, 'base64')
  const buyTransaction = VersionedTransaction.deserialize(buyTxBuffer)
  buyTransaction.sign([keypair])
  const signedBuyTx = Buffer.from(buyTransaction.serialize()).toString('base64')

  console.log('\nExecuting buy transaction...')
  const buyResult = await executeTransaction({
    txData: signedBuyTx,
    network: 'SOL',
    mev: false,
    skipSimulation: false,
    quoteId: buyQuote.transaction.quoteId
  })

  if (!buyResult.success) throw new Error(buyResult.error || 'Buy failed')

  const tokensReceived = buyResult.txRes?.received || '0'
  console.log('\nBUY COMPLETED!')
  console.log(`TX Hash: ${buyResult.txRes?.txHash}`)
  console.log(`Tokens Received: ${tokensReceived}`)
  console.log(`SOL Spent: ${buyResult.txRes?.spent}`)
  console.log(`Explorer: https://solscan.io/tx/${buyResult.txRes?.txHash}`)

  console.log('\nWaiting 20 seconds before selling...')
  await new Promise(resolve => setTimeout(resolve, 20000))

  console.log('\n' + '='.repeat(80))
  console.log('SELL STEP (50% of holdings)')
  console.log('='.repeat(80))

  const tokensReceivedBigInt = BigInt(tokensReceived)
  if (tokensReceivedBigInt === 0n) {
    throw new Error('No tokens received from buy transaction')
  }

  const balanceFormatted = Number(tokensReceivedBigInt) / Math.pow(10, tokenDecimals)
  console.log(`Token Balance: ${balanceFormatted.toFixed(tokenDecimals)} tokens`)

  const sellAmount50PercentRaw = balanceFormatted * 0.5
  const sellAmount50Percent = Math.floor(sellAmount50PercentRaw * Math.pow(10, tokenDecimals)) / Math.pow(10, tokenDecimals)
  console.log(`Selling 50%: ${sellAmount50Percent.toFixed(tokenDecimals)} tokens`)

  const sellQuote = await getQuote({
    token: params.token,
    amount: sellAmount50Percent,
    side: 'SELL',
    slippage: params.slippage,
    trader: traderAddress,
    chain: 'SOL'
  })

  if (!sellQuote.success || !sellQuote.transaction?.trade) {
    throw new Error(sellQuote.error || 'Failed to get sell quote')
  }

  console.log('\nSigning sell transaction with Solana keypair...')
  const sellTxBuffer = Buffer.from(sellQuote.transaction.trade, 'base64')
  const sellTransaction = VersionedTransaction.deserialize(sellTxBuffer)
  sellTransaction.sign([keypair])
  const signedSellTx = Buffer.from(sellTransaction.serialize()).toString('base64')

  console.log('\nExecuting sell transaction...')
  const sellResult = await executeTransaction({
    txData: signedSellTx,
    network: 'SOL',
    mev: false,
    skipSimulation: false,
    quoteId: sellQuote.transaction.quoteId
  })

  if (!sellResult.success) throw new Error(sellResult.error || 'Sell failed')

  console.log('\nSELL COMPLETED!')
  console.log(`TX Hash: ${sellResult.txRes?.txHash}`)
  console.log(`SOL Received: ${sellResult.txRes?.received}`)
  console.log(`Tokens Spent: ${sellResult.txRes?.spent}`)
  console.log(`Explorer: https://solscan.io/tx/${sellResult.txRes?.txHash}`)

  console.log('\n' + '='.repeat(80))
  console.log('SOLANA TRADING FLOW COMPLETED SUCCESSFULLY!')
  console.log('='.repeat(80))
  console.log(`Buy TX: https://solscan.io/tx/${buyResult.txRes?.txHash}`)
  console.log(`Sell TX: https://solscan.io/tx/${sellResult.txRes?.txHash}`)
}

Token Information

Get Solana token details:

async function getTokenInfo(tokenAddress: string): Promise<any> {
  const response = await makeRequest<any>(
    `/api/v1/token-info?tokenAddress=${tokenAddress}&chain=SOL`
  )
  
  if (response.success) {
    console.log('Token Info:')
    console.log(`  Name: ${response.name || 'N/A'}`)
    console.log(`  Symbol: ${response.symbol || 'N/A'}`)
    console.log(`  Decimals: ${response.decimals}`)
    console.log(`  Price: $${response.priceUsd?.toFixed(6) || 'N/A'}`)
    console.log(`  Liquidity: $${response.liquidityUsd?.toLocaleString() || 'N/A'}`)
    console.log(`  Pool: ${response.poolAddress || 'N/A'}`)
  }
  
  return response
}

Balance Checking

Check SOL and SPL token balances:

async function getBalance(walletAddress: string): Promise<any> {
  const response = await makeRequest<any>(
    `/api/v1/balance?walletAddress=${walletAddress}&network=SOL`
  )
  
  if (response.success) {
    const formatted = Number(response.balance) / Math.pow(10, response.decimals || 9)
    console.log(`SOL Balance: ${formatted.toFixed(6)} SOL`)
  }
  
  return response
}

async function getTokenBalance(
  walletAddress: string,
  tokenAddress: string
): Promise<any> {
  const response = await makeRequest<any>(
    `/api/v1/token-balance?walletAddress=${walletAddress}&tokenAddress=${tokenAddress}&network=SOL`
  )
  
  if (response.success) {
    const formatted = Number(response.balance) / Math.pow(10, response.decimals || 6)
    console.log(`Token Balance: ${formatted.toFixed(6)}`)
  }
  
  return response
}

Keypair Management

Working with Solana keypairs:

import { Keypair } from '@solana/web3.js'
import bs58 from 'bs58'

function loadKeypair(privateKey: string): Keypair {
  const decoded = bs58.decode(privateKey)
  return Keypair.fromSecretKey(decoded)
}

function generateNewKeypair(): Keypair {
  const keypair = Keypair.generate()
  const privateKey = bs58.encode(keypair.secretKey)
  console.log('Public Key:', keypair.publicKey.toBase58())
  console.log('Private Key (base58):', privateKey)
  return keypair
}

const keypair = loadKeypair(TRADING_SOL_PKEY!)
console.log('Loaded wallet:', keypair.publicKey.toBase58())

Transaction Signing Details

Understanding Solana transaction signing:

async function signSolanaTransaction(
  trade: string,
  keypair: Keypair
): Promise<string> {
  const txBuffer = Buffer.from(trade, 'base64')
  
  const transaction = VersionedTransaction.deserialize(txBuffer)
  
  transaction.sign([keypair])
  
  const signedTxBuffer = Buffer.from(transaction.serialize())
  const signedTx = signedTxBuffer.toString('base64')
  
  return signedTx
}

Error Handling

async function safeSolanaTrade() {
  try {
    await simpleBuySolana()
  } catch (error: any) {
    if (error.message.includes('Pool not found')) {
      console.error('Token not found or has no liquidity on Jupiter')
    } else if (error.message.includes('Insufficient balance')) {
      console.error('Not enough SOL in wallet')
    } else if (error.message.includes('Slippage')) {
      console.error('Price moved too much, increase slippage tolerance')
    } else if (error.message.includes('blockhash')) {
      console.error('Transaction expired, retry with new quote')
    } else if (error.message.includes('HTTP 401')) {
      console.error('Invalid API key')
    } else if (error.message.includes('HTTP 429')) {
      console.error('Rate limit exceeded')
    } else {
      console.error('Unexpected error:', error.message)
    }
  }
}

Running the Examples

Create a file solana-trading-test.ts:

import * as dotenv from 'dotenv'
dotenv.config()

async function main() {
  console.log('Testing Solana Trading Flow...')
  await solanaFullTradingFlow()
}

main().catch(console.error)

Run it:

export NAOS_API_KEY="your-api-key"
export NAOS_API_DOMAIN="https://api.naos.trade"
export TRADING_SOL_PKEY="your-solana-private-key-base58"

npx ts-node solana-trading-test.ts

Best Practices

Transaction Timing

  1. Wait 20+ seconds between buy and sell for blockchain confirmation

  2. Monitor transaction status on Solscan before next trade

Precision Handling

  1. Use floor division for sell amounts to avoid rounding errors

  2. Check token decimals: Most tokens use 6-9 decimals

  3. Convert properly: Divide by Math.pow(10, decimals) for display

Slippage Settings

  1. 5-10% (500-1000 bps) for most Solana tokens

  2. 10-20% (1000-2000 bps) for highly volatile or low liquidity tokens

  3. Higher for pump.fun tokens: These can be extremely volatile

Security

  1. Store private keys securely: Use environment variables

  2. Base58 format: Solana private keys are base58 encoded

  3. Test with small amounts: Start with 0.1 SOL or less

  4. Never commit keys: Add .env to .gitignore

Error Recovery

  1. Retry on blockhash errors: Get a fresh quote

  2. Check SOL balance: Ensure enough for transaction fees (~0.01 SOL)

  3. Verify token existence: Use token-info endpoint first

  4. Handle Jupiter errors: Some tokens may not be available

Important Differences from EVM

Feature
Solana
EVM

Serialization

Base64

Hex string

Private Key Format

Base58

0x-prefixed hex

Transaction Type

VersionedTransaction

Legacy/EIP-1559

Signing Library

@solana/web3.js

ethers.js

Approval Transactions

Not needed

Often required

Decimals

Usually 6-9

Usually 18

Explorer

solscan.io

etherscan.io, basescan.org

Solana-Specific Notes

Transaction Format

const txBuffer = Buffer.from(quote.transaction.trade, 'base64')
const transaction = VersionedTransaction.deserialize(txBuffer)
transaction.sign([keypair])
const signed = Buffer.from(transaction.serialize()).toString('base64')

No Approval Needed

Unlike EVM:

  • Solana doesn't require separate approval transactions

  • Token accounts are created automatically if needed

  • All logic is in a single transaction

Jupiter Integration

  • Powered by Jupiter aggregator for best prices

  • Supports most SPL tokens and pump.fun tokens

  • Automatic route optimization

Advanced: Multiple Signers

If you need multiple signers:

const keypair1 = Keypair.fromSecretKey(bs58.decode(KEY1))
const keypair2 = Keypair.fromSecretKey(bs58.decode(KEY2))

transaction.sign([keypair1, keypair2])

Common Token Addresses

Popular Solana tokens for testing:

const TOKENS = {
  USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
  USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
  SOL: 'So11111111111111111111111111111111111111112',
  BONK: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263',
  WIF: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm'
}

For complete playground with all scenarios, see: src/trading-suite/playground.ts

Last updated