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()