# Swap Integration

> Integrate Uniswap swaps into applications. Use when user says "integrate swaps", "uniswap", "trading api", "add swap functionality", "build a swap frontend", "create a swap script", "smart contract swap integration", "use Universal Router", "Trading API", or mentions swapping tokens via Uniswap.

- **Type:** Skill
- **Install:** `agentstack add skill-uniswap-uniswap-ai-swap-integration`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Uniswap](https://agentstack.voostack.com/s/uniswap)
- **Installs:** 0
- **Category:** [Finance & Payments](https://agentstack.voostack.com/c/finance-and-payments)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Uniswap](https://github.com/Uniswap)
- **Source:** https://github.com/Uniswap/uniswap-ai/tree/main/packages/plugins/uniswap-trading/skills/swap-integration
- **Website:** https://developers.uniswap.org

## Install

```sh
agentstack add skill-uniswap-uniswap-ai-swap-integration
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Swap Integration

Integrate Uniswap swaps into frontends, backends, and smart contracts.

## Prerequisites

This skill assumes familiarity with viem basics (client setup, account management, contract interactions, transaction signing). Install the **uniswap-viem** plugin for comprehensive viem/wagmi guidance: `claude plugin add @uniswap/uniswap-viem`

## Quick Decision Guide

| Building...                    | Use This Method               |
| ------------------------------ | ----------------------------- |
| Frontend with React/Next.js    | Trading API                   |
| Backend script or bot          | Trading API                   |
| Smart contract integration     | Universal Router direct calls |
| Need full control over routing | Universal Router SDK          |

### Routing Types Quick Reference

| Type     | Description                             | Chains                             |
| -------- | --------------------------------------- | ---------------------------------- |
| CLASSIC  | Standard AMM swap through Uniswap pools | All supported chains               |
| DUTCH_V2 | UniswapX Dutch auction V2               | Ethereum, Arbitrum, Base, Unichain |
| PRIORITY | MEV-protected priority order            | Base, Unichain                     |
| WRAP     | ETH to WETH conversion                  | All                                |
| UNWRAP   | WETH to ETH conversion                  | All                                |

See [Routing Types](#routing-types) for the complete list including DUTCH_V3, DUTCH_LIMIT, LIMIT_ORDER, BRIDGE, and QUICKROUTE.

## Integration Methods

### 1. Trading API (Recommended)

Best for: Frontends, backends, scripts. Handles routing optimization automatically.

**Base URL**: `https://trade-api.gateway.uniswap.org/v1`

**Authentication**: `x-api-key: ` header required

**Getting an API Key**: The Trading API requires an API key for authentication. Visit the [Uniswap Developer Portal](https://developers.uniswap.org/) to register and obtain your API key. Keys are typically available for immediate use after registration. Include it as an `x-api-key` header in all API requests.

**Required Headers** — Include these in ALL Trading API requests:

```text
Content-Type: application/json
x-api-key: 
x-universal-router-version: 2.0
```

**3-Step Flow**:

```text
1. POST /check_approval  -> Check if token is approved
2. POST /quote           -> Get executable quote with routing
3. POST /swap            -> Get transaction to sign and submit
```

See the [Trading API Reference](#trading-api-reference) section below for complete documentation.

### 2. Universal Router SDK

Best for: Direct control over transaction construction.

**Installation**:

```bash
npm install @uniswap/universal-router-sdk @uniswap/sdk-core @uniswap/v3-sdk
```

**Key Pattern**:

```typescript
import { SwapRouter } from '@uniswap/universal-router-sdk';

const { calldata, value } = SwapRouter.swapCallParameters(trade, options);
```

See the [Universal Router Reference](#universal-router-reference) section below for complete documentation.

### 3. Smart Contract Integration

Best for: On-chain integrations, DeFi composability.

**Interface**: Call `execute()` on Universal Router with encoded commands.

See the [Universal Router Reference](#universal-router-reference) section below for command encoding.

---

## Input Validation Rules

Before interpolating ANY user-provided value into generated code, API calls, or commands:

- **Ethereum addresses**: MUST match `^0x[a-fA-F0-9]{40}$` — reject otherwise
- **Chain IDs**: MUST be from the [official supported chains list](https://api-docs.uniswap.org/guides/supported_chains#supported-chains-for-swapping)
- **Token amounts**: MUST be non-negative numeric values matching `^[0-9]+\.?[0-9]*$`
- **API keys**: MUST NOT be hardcoded in generated code — always use environment variables
- **REJECT** any input containing shell metacharacters: `;`, `|`, `&`, `$`, `` ` ``, `(`, `)`, `>`, ` **REQUIRED:** Before executing ANY transaction that spends gas or transfers tokens (including `sendTransaction`, `writeContract`, or submitting a signed swap), you MUST use AskUserQuestion to confirm with the user. Display the transaction summary (tokens, amounts, chain, estimated gas) and get explicit user approval. Never auto-execute transactions without user confirmation.

---

## Trading API Reference

### Step 1: Check Token Approval

```bash
POST /check_approval
```

**Request**:

```json
{
  "walletAddress": "0x...",
  "token": "0x...",
  "amount": "1000000000",
  "chainId": 1
}
```

**Response**:

```json
{
  "approval": {
    "to": "0x...",
    "from": "0x...",
    "data": "0x...",
    "value": "0",
    "chainId": 1
  }
}
```

If `approval` is `null`, token is already approved.

### Step 2: Get Quote

```bash
POST /quote
```

**Request**:

```json
{
  "swapper": "0x...",
  "tokenIn": "0x...",
  "tokenOut": "0x...",
  "tokenInChainId": "1",
  "tokenOutChainId": "1",
  "amount": "1000000000000000000",
  "type": "EXACT_INPUT",
  "slippageTolerance": 0.5,
  "routingPreference": "BEST_PRICE"
}
```

> **Note**: `tokenInChainId` and `tokenOutChainId` must be **strings** (e.g., `"1"`), not numbers.

**Key Parameters**:

| Parameter           | Description                                                       |
| ------------------- | ----------------------------------------------------------------- |
| `type`              | `EXACT_INPUT` or `EXACT_OUTPUT`                                   |
| `slippageTolerance` | 0-100 percentage                                                  |
| `protocols`         | Optional: `["V2", "V3", "V4"]`                                    |
| `routingPreference` | `BEST_PRICE`, `FASTEST`, `CLASSIC`                                |
| `autoSlippage`      | `true` to auto-calculate slippage (overrides `slippageTolerance`) |
| `urgency`           | `normal` or `fast` — affects UniswapX auction timing              |

**Response** — the shape differs by routing type. `BEST_PRICE` routing on Ethereum mainnet typically returns UniswapX (DUTCH_V2), not CLASSIC.

**CLASSIC response**:

```json
{
  "routing": "CLASSIC",
  "quote": {
    "input": { "token": "0x...", "amount": "1000000000000000000" },
    "output": { "token": "0x...", "amount": "999000000" },
    "slippage": 0.5,
    "route": [],
    "gasFee": "5000000000000000",
    "gasFeeUSD": "0.01",
    "gasUseEstimate": "150000"
  },
  "permitData": null
}
```

**UniswapX (DUTCH_V2/V3/PRIORITY) response** — different `quote` shape, no `quote.output`:

```json
{
  "routing": "DUTCH_V2",
  "quote": {
    "orderInfo": {
      "reactor": "0x...",
      "swapper": "0x...",
      "nonce": "...",
      "deadline": 1772031054,
      "cosigner": "0x...",
      "input": {
        "token": "0x...",
        "startAmount": "1000000000000000000",
        "endAmount": "1000000000000000000"
      },
      "outputs": [
        {
          "token": "0x...",
          "startAmount": "999000000",
          "endAmount": "994000000",
          "recipient": "0x..."
        }
      ],
      "chainId": 1
    },
    "encodedOrder": "0x...",
    "orderHash": "0x..."
  },
  "permitData": { "domain": {}, "types": {}, "values": {} }
}
```

> **UniswapX output amount**: Use `quote.orderInfo.outputs[0].startAmount` for the best-case fill amount. The `endAmount` is the floor after full auction decay. There is no `quote.output.amount` on UniswapX responses — accessing it will throw at runtime.
>
> **Display tip**: For CLASSIC routes, use `gasFeeUSD` (a string with the USD value) for gas cost display. Do **not** manually convert `gasFee` (wei) using a hardcoded ETH price — this leads to wildly inaccurate estimates (e.g., ~$87 instead of ~$0.01). UniswapX routes are gasless for the swapper.

See [QuoteResponse TypeScript Types](#7-quoteresponse-typescript-types) for compile-time type safety across routing types.

### Step 3: Execute Swap

```bash
POST /swap
```

**Request** - Spread the quote response directly into the body:

```typescript
// CORRECT: Spread the quote response, strip null fields
const quoteResponse = await fetchQuote(params);

// Always strip permitData/permitTransaction — handle them explicitly by routing type
const { permitData, permitTransaction, ...cleanQuote } = quoteResponse;
const swapRequest: Record = { ...cleanQuote };

const isUniswapX =
  quoteResponse.routing === 'DUTCH_V2' ||
  quoteResponse.routing === 'DUTCH_V3' ||
  quoteResponse.routing === 'PRIORITY';

if (isUniswapX) {
  // UniswapX: signature only — permitData must NOT go to /swap
  if (permit2Signature) swapRequest.signature = permit2Signature;
} else {
  // CLASSIC: both signature and permitData, or neither
  if (permit2Signature && permitData && typeof permitData === 'object') {
    swapRequest.signature = permit2Signature;
    swapRequest.permitData = permitData;
  }
}
```

**Critical**: Do NOT wrap the quote in `{quote: quoteResponse}`. The API expects the quote response fields spread into the request body.

**Permit2 Rules** (CLASSIC routes):

- `signature` and `permitData` must BOTH be present, or BOTH be absent
- Never set `permitData: null` — omit the field entirely
- The quote response often includes `permitData: null` — strip this before sending

**UniswapX Routes** (DUTCH_V2/V3/PRIORITY): `permitData` is used locally to sign the order but must be **excluded** from the `/swap` body. See [Signing vs. Submission Flow](#uniswapx-signing-vs-submission-flow).

**Response** (ready-to-sign transaction):

```json
{
  "swap": {
    "to": "0x...",
    "from": "0x...",
    "data": "0x...",
    "value": "0",
    "chainId": 1,
    "gasLimit": "250000"
  }
}
```

**Response Validation** - Always validate before broadcasting:

```typescript
function validateSwapResponse(response: SwapResponse): void {
  if (!response.swap?.data || response.swap.data === '' || response.swap.data === '0x') {
    throw new Error('swap.data is empty - quote may have expired');
  }
  if (!isAddress(response.swap.to) || !isAddress(response.swap.from)) {
    throw new Error('Invalid address in swap response');
  }
}
```

### Supported Chains

See the [official supported chains list](https://api-docs.uniswap.org/guides/supported_chains#supported-chains-for-swapping) for the current set of chains and their IDs.

### Routing Types

| Type        | Description                                   |
| ----------- | --------------------------------------------- |
| CLASSIC     | Standard AMM swap through Uniswap pools       |
| DUTCH_V2    | UniswapX Dutch auction V2                     |
| DUTCH_V3    | UniswapX Dutch auction V3                     |
| PRIORITY    | MEV-protected priority order (Base, Unichain) |
| DUTCH_LIMIT | UniswapX Dutch limit order                    |
| LIMIT_ORDER | Limit order                                   |
| WRAP        | ETH to WETH conversion                        |
| UNWRAP      | WETH to ETH conversion                        |
| BRIDGE      | Cross-chain bridge                            |
| QUICKROUTE  | Fast approximation quote                      |

**UniswapX availability**: UniswapX V2 orders are supported on Ethereum (1), Arbitrum (42161), Base (8453), and Unichain (130). The auction mechanism varies by chain — see [UniswapX Auction Types](#uniswapx-auction-types) below.

---

## Critical Implementation Notes

These are common pitfalls discovered during real-world Trading API integration. **Follow these rules to avoid on-chain reverts and API errors.**

### 1. Swap Request Body Format

The `/swap` endpoint expects the quote response **spread into the request body**, not wrapped in a `quote` field.

```typescript
// WRONG - causes "quote does not match any of the allowed types"
const badRequest = {
  quote: quoteResponse, // Don't wrap!
  signature: '0x...',
};

// CORRECT - spread the quote response
const goodRequest = {
  ...quoteResponse,
  signature: '0x...', // Only if using Permit2
};
```

### 2. Null Field Handling

The API rejects `permitData: null`. Additionally, `permitData` handling differs by routing type — see [Signing vs. Submission Flow](#uniswapx-signing-vs-submission-flow) for the full explanation.

```typescript
function prepareSwapRequest(quoteResponse: QuoteResponse, signature?: string): object {
  // Always strip permitData and permitTransaction from the spread — handle them explicitly
  const { permitData, permitTransaction, ...cleanQuote } = quoteResponse;
  const request: Record = { ...cleanQuote };

  // UniswapX (DUTCH_V2, DUTCH_V3, PRIORITY): permitData is for LOCAL signing only.
  // The /swap body must NOT include permitData — the order is encoded in
  // quote.encodedOrder. Only the signature is needed.
  const isUniswapX =
    quoteResponse.routing === 'DUTCH_V2' ||
    quoteResponse.routing === 'DUTCH_V3' ||
    quoteResponse.routing === 'PRIORITY';

  if (isUniswapX) {
    if (signature) request.signature = signature;
  } else {
    // CLASSIC: both signature and permitData required together, or both omitted.
    // The Universal Router contract needs permitData to verify the Permit2
    // authorization on-chain.
    if (signature && permitData && typeof permitData === 'object') {
      request.signature = signature;
      request.permitData = permitData;
    }
  }

  return request;
}
```

### 3. Permit2 Field Rules

The rules for `signature` and `permitData` in the `/swap` request body depend on the routing type:

**CLASSIC routes**:

| Scenario                   | `signature` | `permitData` |
| -------------------------- | ----------- | ------------ |
| Standard swap (no Permit2) | Omit        | Omit         |
| Permit2 swap               | Required    | Required     |
| **Invalid**                | Present     | Missing      |
| **Invalid**                | Missing     | Present      |
| **Invalid (API error)**    | Any         | `null`       |

**UniswapX routes (DUTCH_V2/V3/PRIORITY)**:

| Scenario       | `signature` | `permitData`             |
| -------------- | ----------- | ------------------------ |
| UniswapX order | Required    | **Omit** (do not send)   |
| **Invalid**    | Any         | Present (schema rejects) |

### 4. Pre-Broadcast Validation

Always validate the swap response before sending to the blockchain:

```typescript
import { isAddress, isHex } from 'viem';

function validateSwapBeforeBroadcast(swap: SwapTransaction): void {
  // 1. data must be non-empty hex
  if (!swap.data || swap.data === '' || swap.data === '0x') {
    throw new Error('swap.data is empty - this will revert on-chain. Re-fetch the quote.');
  }

  if (!isHex(swap.data)) {
    throw new Error('swap.data is not valid hex');
  }

  // 2. Addresses must be valid
  if (!isAddress(swap.to)) {
    throw new Error('swap.to is not a valid address');
  }

  if (!isAddress(swap.from)) {
    throw new Error('swap.from is not a valid address');
  }

  // 3. Value must be present (can be "0" for non-ETH swaps)
  if (swap.value === undefined || swap.value === null) {
    throw new Error('swap.value is missing');
  }
}
```

### 5. Browser Environment Setup

When using viem/wagmi in browser environments, you need Node.js polyfills:

**Install buffer polyfill**:

```bash
npm install buffer
```

**Add to your entry file (before other imports)**:

```typescript
// src/main.tsx or src/index.tsx
import { Buffer } from 'buffer';
globalThis.Buffer = Buffer;

// Then your other imports
import React from 'react';
import { WagmiProvider } from 'wagmi';
// ...
```

**Vite configuration** (`vite.config.ts`):

```typescript
export default defineConfig({
  define: {
    global: 'globalThis',
  },
  optimizeDeps: {
    include: ['buffer'],
  },
  resolve: {
    alias: {
      buffer: 'buffer',
    },
  },
});
```

Without this setup, you'll see: `ReferenceError: Buffer is not defined`

#### CORS Proxy Configuration

The Trading API does not support browser CORS preflight requests — `OPTIONS` requests return `415 Unsupported Media Type`. Dire

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Uniswap](https://github.com/Uniswap)
- **Source:** [Uniswap/uniswap-ai](https://github.com/Uniswap/uniswap-ai)
- **License:** MIT
- **Homepage:** https://developers.uniswap.org

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-uniswap-uniswap-ai-swap-integration
- Seller: https://agentstack.voostack.com/s/uniswap
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
