Skip to main content
Automated copytrading script: track a wallet via WebSocket, and when it trades, replicate the trade with your own wallet.

Architecture

wss://ws.naos.trade → SWAP_BATCH_INSERT from target wallet
                    → GET /api/v2/quote (same token pair)
                    → sign locally
                    → POST /api/v2/execute

Full script

import { ethers, parseUnits } from 'ethers'
import WebSocket from 'ws'

const API = 'https://api.naos.trade'
const KEY = process.env.NAOS_API_KEY!
const wallet = new ethers.Wallet(process.env.TRADING_EVM_PKEY!)

const NATIVE = '0x0000000000000000000000000000000000000000'
const TARGET_WALLET = '0x78630390bee32abfa8717ce9b9b596cad980ad78' // wallet to copy
const CHAIN = 'BASE'
const BUY_AMOUNT = parseUnits('0.005', 18).toString()               // 0.005 ETH per copy trade

// --- API helper ---

async function api<T>(path: string, init?: RequestInit): Promise<T> {
  const res = await fetch(`${API}${path}`, {
    ...init,
    headers: { 'Authorization': `Bearer ${KEY}`, 'Content-Type': 'application/json', ...init?.headers }
  })
  return res.json() as Promise<T>
}

// --- Copy a trade ---

async function copyTrade(swap: any): Promise<void> {
  const isBuy = swap.is_buy
  const token = swap.token_address

  console.log(`[COPY] ${isBuy ? 'BUY' : 'SELL'} ${swap.token_symbol || token} on chain ${swap.chain}`)

  // Determine tokenIn/tokenOut based on direction
  const tokenIn = isBuy ? NATIVE : token
  const tokenOut = isBuy ? token : NATIVE
  const amount = isBuy ? BUY_AMOUNT : '100%' // buy fixed amount, sell everything

  // 1. Quote — use slippage=10000 for fast mode (skips gas estimation + stats, lowest latency)
  const quote = await api<any>(`/api/v2/quote?` + new URLSearchParams({
    tokenIn, tokenOut, amount,
    slippage: '10000',
    trader: wallet.address,
    chain: CHAIN
  }))

  if (!quote.success || !quote.transaction) {
    console.log(`[SKIP] Quote failed: ${quote.error}`)
    return
  }

  // 2. Sign
  const signedTrade = await wallet.signTransaction(
    ethers.Transaction.from(quote.transaction.trade)
  )

  let signedApprove: string | undefined
  if (quote.transaction.approve) {
    signedApprove = await wallet.signTransaction(
      ethers.Transaction.from(quote.transaction.approve)
    )
  }

  let signedExtra: string | undefined
  if (quote.transaction.extra) {
    signedExtra = await wallet.signTransaction(
      ethers.Transaction.from(quote.transaction.extra)
    )
  }

  // 3. Execute
  const result = await api<any>('/api/v2/execute', {
    method: 'POST',
    body: JSON.stringify({
      trade: signedTrade,
      chain: CHAIN,
      mev: true,
      simulation: true,
      quoteId: quote.transaction.quoteId,
      approve: signedApprove,
      extra: signedExtra
    })
  })

  if (result.success) {
    console.log(`[OK] ${result.txRes?.explorerUrl}`)
  } else {
    console.log(`[FAIL] ${result.parsedError}: ${result.error}`)
  }
}

// --- WebSocket listener ---

function connect(): void {
  const ws = new WebSocket('wss://ws.naos.trade')

  ws.on('open', () => {
    console.log(`[WS] Connected — tracking ${TARGET_WALLET}`)
    ws.send(JSON.stringify({
      type: 'subscribe',
      wallets: [TARGET_WALLET.toLowerCase()],
      heartbeat: true
    }))
  })

  ws.on('message', async (data: Buffer) => {
    const msg = JSON.parse(data.toString())

    if (msg.type === 'SWAP_BATCH_INSERT') {
      for (const swap of msg.data) {
        // Only copy trades on our target chain
        if (swap.chain !== 8453) continue // 8453 = Base

        console.log(`[SWAP] ${swap.is_buy ? 'BUY' : 'SELL'} ${swap.token_symbol}${swap.volume_native} ETH`)

        try {
          await copyTrade(swap)
        } catch (err: any) {
          console.error(`[ERR] ${err.message}`)
        }
      }
    }
  })

  ws.on('close', () => {
    console.log('[WS] Disconnected — reconnecting in 5s')
    setTimeout(connect, 5000)
  })

  ws.on('error', (err) => console.error('[WS]', err.message))
}

connect()

How it works

  1. WebSocket subscription — subscribes to wallets: [TARGET_WALLET] to receive all SWAP_BATCH_INSERT events from the target
  2. Trade detection — when a SWAP_BATCH_INSERT arrives, iterates over each swap and checks is_buy to determine direction
  3. Copy logic — buys with a fixed ETH amount, sells 100% of holdings (using amount: '100%')
  4. Sign & execute — signs all transactions (trade + approve + extra) and executes with MEV protection

Configuration

VariableDescription
TARGET_WALLETWallet address to copy trades from
CHAIN / chain ID checkFilter to a specific chain (8453 = Base)
BUY_AMOUNTFixed amount of native token per buy
slippage: '10000'Fast mode — lowest latency, no stats in response
mev: trueEnable MEV protection for copy trades

Enhancements

  • Fast mode: the script uses slippage=10000 which activates fast mode — skips gas estimation, price impact, and USD valuation for the lowest possible quote latency. See Best Practices for details.
  • Multi-chain: subscribe to multiple wallets, check swap.chain to route to the correct chain
  • Filters: skip trades below a volume threshold (swap.volume_native < 0.01)
  • Token allowlist: only copy trades for tokens you’ve vetted
  • Rate limiting: debounce rapid swaps from the target wallet
  • Notifications: send trade results to Discord/Telegram