Skip to content

PrazwalR/SwapKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 

Repository files navigation

🔀 SwapKit

The Intent-Based Unified Liquidity Layer for Web3

One SDK. Every DEX. Zero MEV.

Stop integrating Uniswap, 1inch, and Paraswap separately. Write 4 lines of code and let SwapKit find the best route, simulate MEV risk, and execute safely.

npm version License: MIT Tests GitHub Repo

Getting StartedHow It WorksAPI ReferenceCLIArchitecture


📖 Table of Contents


😰 The Problem

Building a token swap feature in a Web3 app is an engineering nightmare. In 2024–2026, liquidity is fragmented across completely different execution architectures:

The Three Worlds of DeFi Execution

┌─────────────────────────────────────────────────────────────────────────┐
│                    THE FRAGMENTED DeFi LANDSCAPE                        │
├──────────────────┬──────────────────────┬───────────────────────────────┤
│   Uniswap V4     │   1inch Fusion+      │   Paraswap                   │
│   (On-Chain)     │   (Off-Chain Intent) │   (On-Chain Aggregator)      │
├──────────────────┼──────────────────────┼───────────────────────────────┤
│ • Singleton      │ • No on-chain TX     │ • Traditional API            │
│   PoolManager    │ • EIP-712 signatures │ • Routes across 30+ DEXs     │
│ • Custom Hooks   │ • HTLC secrets for   │ • Returns standard           │
│ • UniversalRouter│   cross-chain        │   calldata                   │
│   binary encoding│ • Resolver network   │ • No MEV protection          │
│ • V4_SWAP command│   executes for you   │                              │
│   + Actions      │ • Gasless for maker  │                              │
├──────────────────┼──────────────────────┼───────────────────────────────┤
│ SDK: @uniswap/   │ SDK: @1inch/         │ SDK: REST API                │
│ v4-periphery     │ cross-chain-sdk      │ api.paraswap.io              │
│                  │                      │                              │
│ Complexity: 🔴   │ Complexity: 🔴       │ Complexity: 🟡              │
└──────────────────┴──────────────────────┴───────────────────────────────┘

If you want the best price for your users, you must integrate ALL THREE. That means:

  1. Learning 3 completely different APIs and execution models
  2. Handling on-chain transactions AND off-chain signatures
  3. Managing cross-chain HTLC secrets (1inch Fusion+)
  4. Encoding binary UniversalRouter payloads (Uniswap V4)
  5. Building your own MEV protection (or your users get sandwiched)
  6. Dealing with 3 different error handling patterns

This takes 3-5 weeks of engineering per project. Every team rebuilds it from scratch.

The MEV Problem

Even after integrating all the DEXs, there's another monster: MEV (Maximum Extractable Value).

When a user submits a swap transaction on Ethereum, it sits in the public mempool — a waiting room visible to everyone. Predatory bots watch this mempool 24/7. When they spot a large swap, they execute a Sandwich Attack:

Without MEV Protection:

    User submits: "Swap 10 ETH → USDC"
         │
         ▼
    ┌─────────────────────────┐
    │   PUBLIC MEMPOOL        │  ◄── Bot sees your transaction!
    │   (visible to everyone) │
    └─────────────────────────┘
         │
         ▼
    Bot FRONT-RUNS: Buys token first (pushes price UP)
         │
         ▼
    Your swap executes at WORSE price ($2,000 instead of $2,050)
         │
         ▼
    Bot BACK-RUNS: Sells token (takes profit)
         │
         ▼
    You lost ~$50 to the sandwich bot 💸

💡 Our Solution

SwapKit eliminates all of this complexity. You describe what you want (an Intent), and SwapKit figures out the how.

Before SwapKit (The Old Way)

// ❌ WITHOUT SwapKit — 200+ lines of protocol-specific code

// Step 1: Set up Uniswap V4
const poolKey = { currency0, currency1, fee: 500, tickSpacing: 10, hooks: "0x..." };
const actions = encodePacked([SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL]);
const params = encodeAbiParameters([...], [{ poolKey, zeroForOne, amountIn, ... }]);
const commands = "0x10"; // V4_SWAP command
const calldata = encodeFunctionData({ abi: UniversalRouterABI, ... });

// Step 2: Set up 1inch Fusion+ (completely different model!)
const fusionSdk = new SDK({ url: "https://api.1inch.dev/fusion-plus", authKey: KEY });
const quote = await fusionSdk.getQuote({ srcChainId, dstChainId, ... });
const order = fusionSdk.createOrder(quote, { walletAddress, hashLock, secretHashes, ... });
const signature = await walletClient.signTypedData(order.typedData);
await fusionSdk.submitOrder(srcChainId, order, quoteId, secretHashes);

// Step 3: Set up Paraswap (yet another API!)
const paraswapQuote = await fetch("https://api.paraswap.io/prices?...");
const txParams = await fetch("https://api.paraswap.io/transactions/1?...");

// Step 4: Compare all three, handle errors, manage MEV...
// ... another 100 lines of comparison, error handling, and retry logic

After SwapKit (The New Way)

// ✅ WITH SwapKit — 4 lines. That's it.

import { createSwapKit } from "@swap-kit/core";

const sdk = createSwapKit({ oneInchApiKey: process.env.ONEINCH_KEY });

const quotes = await sdk.quote({
  fromToken: "0xEeee...EEeE",  // ETH
  toToken: "0xA0b8...eB48",    // USDC
  fromAmount: 1000000000000000000n, // 1 ETH
  fromChainId: 1,
});

// quotes[0] is already the best route with MEV analysis applied
console.log(quotes[0].protocol);    // "paraswap" or "1inch-fusion" or "uniswap-v4"
console.log(quotes[0].amountOut);   // Best price across all DEXs
console.log(quotes[0].mevExposure); // MEV risk estimate

🚀 Getting Started

Step 1: Install the SDK

npm install @swap-kit/core
# or
pnpm add @swap-kit/core
# or
yarn add @swap-kit/core

Step 2: Get Your API Keys

SwapKit needs one required key (Alchemy for blockchain access) and has one optional key (1inch for intent-based swaps):

Key Required? Where to Get It Free?
Alchemy RPC ✅ Yes alchemy.com Yes (free tier)
1inch API ⬡ Optional portal.1inch.dev Yes (free tier)
Paraswap ❌ No key needed Open API Free
DefiLlama ❌ No key needed Open API Free
Flashbots ❌ No key needed Public RPC Free

Without the 1inch key, SwapKit still works perfectly — it routes across Uniswap V4 and Paraswap. The 1inch key simply adds Fusion+ as an additional routing option for potentially better prices.

Step 3: Initialize the SDK

import { createSwapKit } from "@swap-kit/core";

// Minimal setup (Paraswap + Uniswap V4 only — no API key needed!)
const sdk = createSwapKit({
  oneInchApiKey: "", // Leave empty to skip 1inch
});

// Full setup (all three protocols)
const sdk = createSwapKit({
  oneInchApiKey: process.env.ONEINCH_API_KEY!,
  rustEngineUrl: "http://localhost:3030", // Optional: for MEV protection
  mevFailOpen: true, // If Rust engine is down, still execute (default: true)
});

Step 4: Get a Quote

// Define what you want to swap
const quotes = await sdk.quote({
  fromToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH
  toToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",   // USDC
  fromAmount: 1000000000000000000n, // 1 ETH (18 decimals)
  fromChainId: 1,                   // Ethereum Mainnet
  maxSlippageBps: 50,               // 0.5% max slippage
});

// The SDK returns quotes sorted by best net output
console.log(`Best route: ${quotes[0].protocol}`);
console.log(`You receive: ${quotes[0].amountOut} USDC`);
console.log(`MEV risk: ${quotes[0].mevExposure}`);

Step 5: Execute the Swap

import { createWalletClient, createPublicClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";

// Create a wallet (in production, this comes from MetaMask/WalletConnect)
const walletClient = createWalletClient({
  account: privateKeyToAccount("0xYOUR_PRIVATE_KEY"),
  chain: mainnet,
  transport: http(process.env.RPC_ETHEREUM),
});

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(process.env.RPC_ETHEREUM)
});

// Execute!
const intent = {
  fromToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH
  toToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",   // USDC
  fromAmount: 1000000000000000000n, // 1 ETH
  fromChainId: 1,
};

const result = await sdk.swap(
  intent,
  walletClient,
  publicClient
);

console.log(`✅ Swap executed! TX: ${result.txHash}`);
console.log(`Received: ${result.actualAmountOut} USDC`);
console.log(`Gas paid: ${result.gasPaidWei} wei`);

🔍 How It Works

Here is exactly what happens when you call sdk.quote() and sdk.swap():

Phase 1: Parallel Scanning

When you request a quote, SwapKit fires off simultaneous requests to all three protocols:

sdk.quote({ fromToken: ETH, toToken: USDC, amount: 1 ETH })
    │
    ├──→ Uniswap V4 Adapter
    │       └─ Calls QuoterV2 contract via eth_call (on-chain simulation)
    │       └─ Returns: 2,018.47 USDC
    │
    ├──→ 1inch Adapter
    │       └─ Calls 1inch REST API (/swap/v6.0/1/quote)
    │       └─ Returns: 2,017.10 USDC
    │
    └──→ Paraswap Adapter
            └─ Calls Paraswap REST API (/prices)
            └─ Returns: 2,022.15 USDC  ◄── Winner!

All three run concurrently using Promise.allSettled, so the total time is only as slow as the slowest API (~200ms).

Phase 2: MEV Simulation (Rust Engine)

Before returning the quote, SwapKit sends the winning route to the Rust engine for safety analysis:

Best Quote (Paraswap, 2,022.15 USDC)
    │
    ▼
Rust Engine POST /simulate
    │
    ├─ Values the trade in the chain's NATIVE token (ETH/MATIC/…) wei
    ├─ Compares extractable value vs the attacker's 2-tx gas cost (same unit)
    ├─ Scans recent blocks for known sandwich-bot activity
    ├─ Calculates optimal slippage tolerance
    │
    ▼
Returns: { sandwichRisk: "low", recommendedSlippage: 30bps }

Why native-token valuation matters: profitability is only meaningful when the value a bot can extract and the gas it must pay are in the same unit. The engine denominates both in native-token wei, so a large USDC-output trade is no longer mis-scored as "no risk" just because USDC has 6 decimals. Token→token swaps with no native leg are honestly reported as unknown rather than guessed.

If the Rust engine detects high MEV risk, the SDK can automatically:

  • Lower the slippage tolerance to make sandwich attacks unprofitable
  • Route the transaction through Flashbots Protect (a private submission channel that hides your transaction from bots)

What if I don't run the Rust engine? The SDK works perfectly without it. It simply skips the MEV simulation step and uses the default slippage you set (e.g., 0.5%). Think of the Rust engine as an optional safety upgrade.

Phase 3: Unified Execution

SwapKit abstracts the completely different execution models behind a single .swap() call:

sdk.swap(intent, walletClient, publicClient)
    │
    ├─ If winner is Uniswap V4:
    │     1. Encode UniversalRouter calldata (V4_SWAP + SETTLE_ALL + TAKE_ALL)
    │     2. Build Ethereum transaction
    │     3. Send via walletClient.sendTransaction()
    │     4. Wait for on-chain confirmation
    │
    ├─ If winner is 1inch Fusion+:
    │     1. Create EIP-712 typed data order
    │     2. Request signature from wallet (NOT a transaction!)
    │     3. Submit signed intent to 1inch Relayer API
    │     4. Resolvers execute the trade (gasless for the user!)
    │
    └─ If winner is Paraswap:
          1. Fetch transaction params from Paraswap API
          2. Build Ethereum transaction
          3. Send via walletClient.sendTransaction()
          4. Wait for on-chain confirmation

You don't need to know ANY of this. Just call sdk.swap() and it handles everything.


📚 API Reference

createSwapKit(config)

Creates a new SwapKit instance.

const sdk = createSwapKit({
  oneInchApiKey: string,         // Your 1inch API key (pass "" to skip)
  rustEngineUrl?: string,        // URL of the Rust MEV engine (optional)
  mevFailOpen?: boolean,         // If true, skip MEV check when engine is down (default: true)
  customChains?: ChainConfig[],  // Dynamically inject unsupported or new chains (e.g. Avalanche)
});

sdk.quote(intent)

Fetches quotes from all supported protocols.

const quotes: QuoteResult[] = await sdk.quote({
  fromToken: Address,        // Source token contract address
  toToken: Address,          // Destination token contract address
  fromAmount: bigint,        // Amount in wei (smallest unit)
  fromChainId: number,       // Source chain (1 = Ethereum, 8453 = Base, 42161 = Arbitrum)
  toChainId?: number,        // Destination chain (defaults to fromChainId)
  maxSlippageBps?: number,   // Max slippage in basis points (50 = 0.5%)
  recipient?: Address,       // Receiving address (defaults to sender)
  deadline?: number,         // Unix timestamp deadline
  skipMEVCheck?: boolean,    // Skip Rust engine simulation
});

Returns: QuoteResult[] sorted by best net output (after gas + MEV estimates).

sdk.swap(intent, walletClient, publicClient)

Quotes and executes the best swap in one call.

const result: SwapResult = await sdk.swap(intent, walletClient, publicClient);

// result.txHash        — Transaction hash (or order hash for 1inch)
// result.protocol      — Which protocol won ("uniswap-v4" | "1inch-fusion" | "paraswap")
// result.actualAmountOut — Tokens received
// result.gasPaidWei    — Gas cost in wei

sdk.bestQuote(intent)

Convenience method that returns only the single best quote.

const best: QuoteResult = await sdk.bestQuote(intent);

💻 CLI

SwapKit ships with a beautiful command-line interface for quick testing.

Installation

npm install -g @swap-kit/cli
# or run directly
npx @swap-kit/cli quote --from 0xEeee...EEeE --to 0xA0b8...eB48 --amount 1

Commands

# Get the best quote across all protocols
swap-kit quote \
  --from 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \
  --to 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
  --amount 1 \
  --chain 1

# Output:
# ✔ Route optimized successfully!
#
# 🏆 Best Quote Found:
# Protocol:      paraswap
# Amount Out:    2,022.15
# MEV Exposure:  Protected 🛡️

🏗 Architecture

graph TB
    subgraph "Developer's App"
        A[Your Code] -->|"sdk.quote(intent)"| B["@swap-kit/core"]
    end

    subgraph "@swap-kit/core (TypeScript)"
        B --> C[Intent Parser]
        C --> D[Quote Engine (Promise.allSettled)]
        D -->|Concurrent| E[Uniswap V4 Adapter]
        D -->|Concurrent| F[1inch Fusion Adapter]
        D -->|Concurrent| G[Paraswap Adapter]
        E --> H[Sort by netAmountOut DESC]
        F --> H
        G --> H
        H --> I[MEV Guard]
    end

    subgraph "swap-kit-engine (Rust)"
        I -->|"POST /simulate"| J[MEV Simulator]
        J --> K[Sandwich Risk Analyzer]
        J --> L[Slippage Optimizer]
    end

    subgraph "Execution Layer"
        I --> M[Execution Engine]
        M -->|On-Chain TX| N["Uniswap V4 UniversalRouter"]
        M -->|EIP-712 Signature| O["1inch Relayer API"]
        M -->|On-Chain TX| P["Paraswap Augustus Router"]
    end

    subgraph "MEV Protection"
        M -->|If high risk| Q["Flashbots Protect RPC"]
        M -->|If low risk| R["Standard Alchemy RPC"]
    end

    style B fill:#6366f1,stroke:#4f46e5,color:#fff
    style J fill:#ef4444,stroke:#dc2626,color:#fff
    style H fill:#22c55e,stroke:#16a34a,color:#fff
Loading

Supported Chains

Chain Chain ID Uniswap V4 1inch Paraswap Native RPC
Ethereum 1 eth.drpc.org
Base 8453 mainnet.base.org
Arbitrum 42161 arb1.arbitrum.io/rpc
Optimism 10 mainnet.optimism.io
Polygon 137 polygon-rpc.com
BNB Chain 56 bsc-dataseed.binance.org
Sepolia (Testnet) 11155111 rpc.sepolia.org

🔑 API Keys Guide

For Developers Using Our SDK

When you install @swap-kit/core, you provide your own API keys. SwapKit never bundles any keys — you bring your own.

// Your keys, your quotas, your control
const sdk = createSwapKit({
  oneInchApiKey: process.env.MY_1INCH_KEY!,
});

Where to Get Each Key

1. Alchemy (Required — for blockchain RPC access)

  1. Go to alchemy.com and sign up (free)
  2. Create a new app → Select "Ethereum Mainnet"
  3. Copy the API key from the dashboard
  4. Use it in your RPC URLs: https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY

2. 1inch (Optional — enables Fusion+ intent swaps)

  1. Go to portal.1inch.dev and sign up
  2. Navigate to API Keys → Create a new key
  3. Enable the "Swap API" and "Fusion API" permissions
  4. Pass it to SwapKit: oneInchApiKey: "YOUR_KEY"

3. Paraswap — No key needed! 🎉

The Paraswap API is completely open. SwapKit uses it out of the box.


🛠 Developer Documentation

The codebase operates heavily via @swap-kit/core interacting with real APIs (Paraswap, 1inch, Uniswap QuoterV2) and delegating risk analysis to the swap-kit-engine (Rust).

Environment Variables

Variable Description
ALCHEMY_API_KEY Alchemy RPC key for all chains
ONEINCH_API_KEY 1inch Fusion+ API key
RPC_ETHEREUM Custom Ethereum RPC URL
RPC_BASE Custom Base RPC URL
RPC_ARBITRUM Custom Arbitrum RPC URL
RPC_OPTIMISM Custom Optimism RPC URL
RPC_POLYGON Custom Polygon RPC URL
RPC_BSC Custom BNB Chain RPC URL
RPC_SEPOLIA Custom Sepolia testnet RPC URL
CORS_ORIGIN Restrict CORS on Rust engine
BIND_ADDR Rust engine bind address (default: 127.0.0.1:3030)

RPC Priority Order

SwapKit resolves the highest priority RPC url logic automatically:

  1. Programmatic rpcUrl parameter (highest)
  2. process.env.RPC_ETHEREUM / RPC_BASE / RPC_ARBITRUM etc.
  3. Alchemy URL (if ALCHEMY_API_KEY set)
  4. Public fallback RPCs (lowest)

Rust Engine API Endpoints

The Rust engine has 3 endpoints. (Quoting lives entirely in the TypeScript SDK, which calls the real Paraswap/1inch APIs and the on-chain Uniswap V4 Quoter — the engine does not expose a /quote route.)

  1. GET /health — Returns "ok". Used for monitoring.

  2. POST /simulate — MEV sandwich attack risk assessment:

    • Classifies risk as none, low, medium, high, or unknown
    • Decides profitability in the chain's native token (wei), so the risk is correct regardless of the output token's decimals; token→token swaps with no native leg are reported as unknown rather than guessed
    • Uses integer-based math (no floating-point precision loss)
    • Recommends optimal slippage
    • Request body: { from_token, to_token, from_amount, chain_id, protocol, amount_out, slippage_bps }
  3. POST /mine — CREATE2 vanity address mining for Uniswap V4 hooks: In Uniswap V4, the starting characters of a Hook's contract address dictate what permissions the Hook has (e.g., an address starting with 0x40... vs 0x00...). If you want "BeforeSwap" permissions, you must deploy to a specific prefix. This endpoint brute-forces that deployment salt for you.

    How it works:

    • Step 1: The Request: A developer sends a payload (deployer, init_code_hash, prefix).
    • Step 2: Concurrent Mining: The engine spins up a highly optimized multi-threaded task using rayon, utilizing all available CPU cores.
    • Step 3: Keccak256 Brute-forcing: It generates random salt values and hashes them to find the target Ethereum address. (e.g., Attempt 1: 0x91... Fail ❌ → Attempt 84,302: 0x40... Success! ✅)
    • Step 4: Safe Concurrency: To prevent server DoS during this intense CPU work, the engine uses a Semaphore (max 2 simultaneous jobs) and a 30-second timeout. Once the matching salt is found, it immediately halts all threads and returns the winning salt.

    How to use it: Run the engine locally (it runs on port 3030 by default), then send a POST request with your hook's deployment parameters:

    curl -X POST http://localhost:3030/mine \
      -H "Content-Type: application/json" \
      -d '{
        "deployer": "0xYourWalletAddress",
        "init_code_hash": "0xYourCompiledBytecodeHash",
        "prefix": "40"
      }'

    Response:

    {
      "salt": "0x000000000000000000000000000000000000000000000000000000000001494e",
      "address": "0x40a9...138d",
      "attempts": 84302,
      "found": true
    }

TypeScript SDK Architecture

The SDK has these modules:

  • adapters/: Protocol adapters making REAL API calls
    • paraswap.tsfetch("https://apiv5.paraswap.io/prices?...")
    • one-inch.tsfetch("https://api.1inch.dev/swap/v6.0/...")
    • uniswap-v4.tsclient.simulateContract() on QuoterV2
  • quote/engine.ts: Uses Promise.allSettled (one failure doesn't kill all)
  • mev/guard.ts: POST to Rust engine /simulate
  • mev/slippage.ts: Pure math slippage optimization
  • execution/engine.ts: Handles approval + execution for all 3 protocols
  • intent/parser.ts: Resolves token symbols (ETH, USDC, etc.) to addresses per chain
  • utils/token.ts: On-chain decimal reads with known-token fallbacks (USDC=6, USDT=6, WBTC=8)
  • utils/chain.ts: RPC priority: env override > Alchemy > public fallback

📝 Changelog (v0.2.0)

A second deep audit fixed three correctness bugs that made headline features silently misbehave, plus a round of cleanups. All fixes were verified against a mainnet fork (anvil) with real transactions and an exhaustive edge-case matrix.

  • 🔴 Uniswap V4 quotes now work. The Quoter ABI was wrong (extra sqrtPriceLimitX96 input, array outputs), so every quote reverted on-chain and Uniswap was silently dropped from routing. Replaced with the real IV4Quoter ABI ((uint256 amountOut, uint256 gasEstimate)), verified live on mainnet. Fee tiers are now quoted concurrently.
  • 🔴 MEV risk is no longer mis-scored. The simulator compared output-token units (e.g. 6-decimal USDC) against gas-in-wei, so large stablecoin-output trades always read as "no risk". Profitability is now decided in native-token wei; token→token swaps with no native leg return unknown instead of a false "none".
  • 🔴 Uniswap V4 ERC-20 swaps now execute. Added the missing second Permit2 approval (Permit2.approve(token, UniversalRouter)); without it the router could not pull the token and every ERC-20-input v4 swap reverted.
  • 🟠 Fixed the red pnpm typecheck (closure-narrowing never in the gasless tests).
  • 🟠 CLI now reads the 1INCH_API_KEY env var spelling shipped in .env.
  • 🟡 Type-safe slippageBps on route data (removed as any); removed dead ABIs, dead Rust quote types, and the unused ACTIONS/CHAINS constants; fixed compiler warnings.
  • ℹ️ Test suite expanded to 220+ checks: 34 Rust, 123 TS regression suites, 28 logic edge cases, 38 live integration tests, plus fork-based end-to-end execution.

📝 Changelog (v0.1.9)

We recently underwent an intense audit leading to 15 key fixes in v0.1.9.

  • 🔴 Fixed RPC priority inversion (env vars now properly override Alchemy)
  • 🔴 Fixed netAmountOut cross-unit subtraction bug across all 3 adapters
  • 🔴 Fixed CLI execute command referencing a non-existent kit.execute.
  • 🟠 Fixed QuoterV2 ABI mismatch for Uniswap V4
  • 🟠 Fixed zeroForOne always true in V4 adapter
  • 🟠 Fixed 1inch same-chain ERC-20 approval being skipped
  • 🟠 Fixed settle/take currencies for V4 swap direction
  • 🟠 Fixed Rayon thread pool starvation DoS in Rust mining
  • 🟠 Replaced f64 precision loss with integer-based risk classification
  • 🟡 Added graceful shutdown to Rust engine
  • 🟡 Capped MEV estimate at amount_out on overflow
  • 🟡 Added zero-amount rejection on all endpoints
  • 🟡 Added getTokenDecimals with known fallbacks for USDC/USDT/WBTC
  • 🟡 MEV guard now clamps netAmountOut to 0n (prevents underflow)
  • ℹ️ Test suite expanded from 39 → 111+ tests

🛡️ Security

We've invested heavily in Denial of Service (DoS) protections.

  • Payload Bound: 64KB body size limit across the Rust Axum server.
  • Concurrency Constraints: Mining API utilizes a semaphore max bounded to 2 concurrent workers to prevent thread starvation in Rayon.
  • Execution Lifecycle: 30-second timeout on computationally heavy mining, combined with atomic boolean cancellation loops.
  • Network Boundaries: Rust server binds to 127.0.0.1 locally, preventing unwarranted global network exposure.
  • Integer Math Only: Total removal of f64 usage to prevent precision loss across amounts and slippage heuristics.

❓ FAQ

Do I need ALL the API keys?

No! SwapKit works with zero API keys — it will route through Paraswap (which is free and open). Adding an Alchemy key enables Uniswap V4 on-chain quotes, and adding a 1inch key enables Fusion+ intent swaps.

I'm getting a TypeScript/Module error in Node.js. How do I fix it?

If you are writing a quick testing script using ts-node, you might encounter CommonJS vs ESM import errors. Solution: Use tsx instead of ts-node. It handles modern TypeScript seamlessly without configuration:

npx tsx index.ts

What happens if a protocol is down?

SwapKit uses a "fail-open" design. If 1inch's API is down, the SDK silently skips it and returns quotes from the remaining protocols. Your users never see an error.

Is the Rust engine required?

No. Without the Rust engine, SwapKit uses static slippage values (e.g., 0.5%). With the engine, it dynamically adjusts slippage based on real-time mempool analysis.

What is "Flashbots Protect"?

When MEV risk is high, SwapKit routes your transaction through Flashbots Protect — a private submission channel that sends your transaction directly to block builders, completely bypassing the public mempool. Bots literally cannot see your transaction to attack it.

Flashbots Protect is enabled by default. You can configure it when creating the SDK:

const sdk = createSwapKit({
  oneInchApiKey: process.env.ONEINCH_API_KEY!,
  rustEngineUrl: "http://localhost:3030",

  // Flashbots configuration (all optional — sensible defaults are provided)
  flashbotsEnabled: true,                              // Default: true
  flashbotsProtectRpc: "https://rpc.flashbots.net",    // Default: Flashbots mainnet
  onFlashbotsReroute: (quote) => {                     // Optional callback
    console.log(`🛡️ Rerouted ${quote.protocol} swap via Flashbots Protect`);
    // Send alert to your monitoring system, log to analytics, etc.
  },
});

How it works internally:

  1. The SDK fetches the best quote and sends it to the Rust MEV engine for sandwich risk analysis.
  2. If the engine returns sandwichRisk: "high", the ExecutionEngine dynamically clones the user's wallet client with a Flashbots Protect RPC transport.
  3. The transaction is signed identically but submitted to Flashbots' private relay instead of the public mempool.
  4. Bots never see the transaction. The user's swap executes safely.

📊 Test Results

Every component is validated by deterministic suites, live integration tests, and end-to-end execution against a mainnet fork (anvil):

╔══════════════════════════════════════════════════════════════╗
║       SwapKit — Full Integration & Edge Case Test Suite     ║
╚══════════════════════════════════════════════════════════════╝

  🦀 Rust unit + security/DoS tests ...... 34/34 ✅
  📦 TS regression suites ................ 123/123 ✅
       (approvals · gasless · flashbots · recipient · MEV fail-open · slippage)
  🧪 TS logic edge-case suite ............ 28/28 ✅
  🌐 Live integration (Alchemy + 1inch) .. 38/38 ✅
  ⛓️ Fork E2E execution + HTTP edge matrix . verified ✅
       (Uniswap V4 ETH→USDC & USDC→WETH, 1inch ETH→USDC, /simulate · /mine · DoS)

  TOTAL: 220+ checks PASSED — Ready for publication! 🎉

Reproduce the fork tests:

anvil --fork-url $RPC_ETHEREUM --port 8545 --silent &
cd swap-kit/packages/core
RPC_ETHEREUM=http://127.0.0.1:8545 npx tsx scripts/fork-verify.ts
RPC_ETHEREUM=http://127.0.0.1:8545 npx tsx scripts/fork-edge.ts

📄 License

MIT — Use SwapKit freely in commercial and open-source projects.

About

DeFi Intent SDK — "swap-kit" A unified TypeScript + Rust SDK that abstracts 1inch Fusion+, Uniswap v4 hooks, and Paraswap into a single swap(intent) interface. Developers pass an intent (from, to, chain, constraints) and the SDK picks the best execution path — MEV-protected, cross-chain, gas-optimised. Ships as an npm package + Rust crate.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors