SPB Git

Repository Public

smoke-swap-kyberswap.mts 4.9 KB 56 lines typescript

1// Live READ-ONLY check of the KyberSwap adapter: real quotes (GET /routes only — nothing is encoded, signed or sent) for small2// realistic amounts on each served network, with latency, compared with CoinGecko spot prices.3// Run: npx tsx scripts/smoke-swap-kyberswap.mts     (about one request per second; the public limit is 3 rps)4import { NETWORKS, formatUnits } from '@spw/core';5import { checkQuote, httpClient, resolveAsset, type AssetRef, type SwapQuoteRequest } from '../packages/swap/src/index.ts';6import { createKyberswapProvider } from '../packages/swap/src/providers/kyberswap.ts';78const BAND = 0.02; // quoted rate must be within 2 % of the CoinGecko cross rate9const CG: Record<string, string> = { ETH: 'ethereum', USDC: 'usd-coin', USDT: 'tether', WBTC: 'wrapped-bitcoin', LINK: 'chainlink', POL: 'polygon-ecosystem-token', BNB: 'binancecoin', FDUSD: 'first-digital-usd', AVAX: 'avalanche-2', HYPE: 'hyperliquid' };10const tok = (network: string, symbol: string): AssetRef => ({ network, asset: NETWORKS[network]!.tokens.find((t) => t.symbol === symbol)!.id });11const native = (network: string): AssetRef => ({ network, asset: 'native' });12const WALLET = '0x1111111111111111111111111111111111111111'; // placeholder; never sent to the provider13const cases: [AssetRef, AssetRef, bigint][] = [14  [native('ethereum'), tok('ethereum', 'USDC'), 10n ** 16n],          // 0.01 ETH15  [tok('ethereum', 'USDC'), tok('ethereum', 'WBTC'), 50_000_000n],    // 50 USDC16  [tok('base', 'USDC'), native('base'), 25_000_000n],                 // 25 USDC17  [tok('arbitrum', 'USDC'), tok('arbitrum', 'LINK'), 30_000_000n],    // 30 USDC18  [native('polygon'), tok('polygon', 'USDC'), 200n * 10n ** 18n],     // 200 POL19  [native('optimism'), tok('optimism', 'USDC'), 10n ** 16n],          // 0.01 ETH20  [native('bsc'), tok('bsc', 'FDUSD'), 4n * 10n ** 16n],              // 0.04 BNB21  [native('avalanche'), tok('avalanche', 'USDC'), 2n * 10n ** 18n],   // 2 AVAX22  [native('hyperevm'), tok('hyperevm', 'USDC'), 10n ** 18n],          // 1 HYPE23];24const prices = (await (await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${Object.values(CG).join(',')}&vs_currencies=usd`)).json()) as Record<string, { usd: number }>;25const usd = (sym: string) => { const v = prices[CG[sym]!]?.usd; if (!(v! > 0)) throw new Error(`no CoinGecko price for ${sym}`); return v!; };2627const p = createKyberswapProvider(process.env), http = httpClient(p.id);28let failed = 0; const lat: number[] = [];29console.log(`provider ${p.id}  networks ${p.capabilities().networks.join(',')}  ${new Date().toISOString()}`);30for (const [from, to, amountIn] of cases) {31  const a = resolveAsset(from).def, b = resolveAsset(to).def, label = `${from.network}: ${formatUnits(amountIn, a.decimals)} ${a.symbol} -> ${b.symbol}`;32  const req: SwapQuoteRequest = { from, to, amountIn, sender: WALLET, recipient: WALLET, slippageBps: 50 };33  const t0 = Date.now();34  try {35    const quotes = await p.quote(req, { signal: AbortSignal.timeout(8000), http, now: Date.now });36    const ms = Date.now() - t0; lat.push(ms);37    if (quotes.length !== 1) throw new Error(`expected one quote, got ${quotes.length}`);38    const q = quotes[0]!, invalid = checkQuote(q, req, Date.now());39    if (invalid) throw new Error(`checkQuote: ${invalid}`);40    const inUnits = Number(amountIn) / 10 ** a.decimals; // display only41    const rate = (Number(q.amountOut) / 10 ** b.decimals) / inUnits, ref = usd(a.symbol) / usd(b.symbol), dev = rate / ref - 1;42    const gas = q.fees[0]!, nat = NETWORKS[from.network]!.native;43    const ok = Math.abs(dev) <= BAND; if (!ok) failed++;44    console.log(`${ok ? 'OK  ' : 'FAIL'} ${label.padEnd(36)} out ${formatUnits(q.amountOut, b.decimals)} ${b.symbol} (min ${formatUnits(q.minAmountOut, b.decimals)})  ~${(inUnits * usd(a.symbol)).toFixed(2)} USD in`45      + `\n     rate ${rate.toPrecision(7)} vs CoinGecko ${ref.toPrecision(7)} (${(dev * 100).toFixed(3)} %)  impact ${q.priceImpactBps} bps  gas ${formatUnits(gas.amount, 18)} ${nat.symbol} (~${((Number(gas.amount) / 1e18) * usd(nat.symbol)).toFixed(4)} USD)  via ${q.steps[0]!.via.join('+')}  ${ms} ms`);46  } catch (e) { failed++; console.log(`ERR  ${label.padEnd(36)} ${(e as { code?: string }).code ?? ''} ${(e as Error).message}  ${Date.now() - t0} ms`); }47  await new Promise((r) => setTimeout(r, 1000));48}49// "no route" must be an empty array, not an error50const none = await p.quote({ from: native('ethereum'), to: tok('ethereum', 'USDC'), amountIn: 1n, sender: WALLET, recipient: WALLET, slippageBps: 50 }, { signal: AbortSignal.timeout(8000), http, now: Date.now });51console.log(`${none.length === 0 ? 'OK  ' : 'FAIL'} ${'ethereum: 1 wei ETH -> USDC'.padEnd(36)} no route -> ${JSON.stringify(none)}`); if (none.length) failed++;52lat.sort((x, y) => x - y);53console.log(`latency ms: min ${lat[0]} median ${lat[Math.floor(lat.length / 2)]} max ${lat[lat.length - 1]}  (${lat.length} quotes)`);54console.log(failed ? `${failed} FAILED` : 'all checks passed');55process.exit(failed ? 1 : 0);56
Files main
Menu

Theme