All skills
uniswap avatar

/swap-integration

@bff480c
by Uniswap Labsuniswap/uniswap-ai233 stars
39

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.

Use this Skill: https://skilld.dev/gh/uniswap/uniswap-ai/swap-integration

This session only. Nothing lands on disk.

referencesadvanced-patterns.md

≈2.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Advanced Trading API Patterns

Supplementary patterns for the swap-integration skill covering smart account integration, L2 WETH handling, and rate limiting.

Smart Account Integration (ERC-4337)

Execute Trading API swaps through ERC-4337 smart accounts using delegation and bundlers. This pattern is useful for automated services (DCA bots, portfolio rebalancers) that execute swaps on behalf of users via delegated smart accounts.

Architecture

Trading API (get calldata) -> Create Execution -> Delegation Redemption -> Bundler (UserOperation)

Full Pattern

import {
  createPublicClient,
  createWalletClient,
  http,
  type Address,
  type Hex,
  encodeFunctionData,
} from 'viem';
import { base } from 'viem/chains';

// REQUIRED: define decision_origin yourself — 'human_mediated' (a human reviews/approves
// each action before it executes) or 'autonomous' (no per-action human approval).
// See "Agent Attribution" in SKILL.md. There is no default — you must choose.
declare const DECISION_ORIGIN: 'human_mediated' | 'autonomous';

const AGENT_INFO = JSON.stringify({
  integration_name: 'swap-integration',
  decision_origin: DECISION_ORIGIN,
  version: '1.6.0',
});

// Types for smart account integration
interface SwapCalldata {
  to: Address;
  data: Hex;
  value: string;
}

interface Execution {
  target: Address;
  callData: Hex;
  value: bigint;
}

interface SignedDelegation {
  delegator: Address;
  delegate: Address;
  authority: Hex;
  caveats: readonly unknown[];
  salt: bigint;
  signature: Hex;
}

// 1. Get swap calldata from Trading API (standard 3-step flow)
async function getSwapCalldata(
  quoteResponse: Record<string, unknown>,
  apiKey: string
): Promise<SwapCalldata> {
  const { permitData, permitTransaction, ...cleanQuote } = quoteResponse;

  const swapRes = await fetch('https://trade-api.gateway.uniswap.org/v1/swap', {
    method: 'POST',
    headers: {
      'x-api-key': apiKey,
      'Content-Type': 'application/json',
      'x-agent-info': AGENT_INFO,
    },
    body: JSON.stringify({
      ...cleanQuote,
      ...(permitData && { permitData }),
    }),
  });

  const swapData = await swapRes.json();
  if (!swapRes.ok) throw new Error(swapData.detail || 'Swap request failed');

  return swapData.swap;
}

// 2. Create execution for delegation redemption
function createExecution(swap: SwapCalldata): Execution {
  return {
    target: swap.to, // Universal Router address
    callData: swap.data,
    value: BigInt(swap.value),
  };
}

// 3. Submit via bundler as a UserOperation
async function executeViaSmartAccount(
  bundlerClient: {
    sendUserOperation: (params: {
      account: unknown;
      calls: readonly { to: Address; data: Hex; value?: bigint }[];
    }) => Promise<Hex>;
  },
  delegateSmartAccount: unknown,
  delegationManagerAddress: Address,
  signedDelegation: SignedDelegation,
  execution: Execution
): Promise<Hex> {
  // Encode the delegation redemption call
  const redeemData = encodeFunctionData({
    abi: [
      {
        name: 'redeemDelegations',
        type: 'function',
        inputs: [
          { name: 'delegations', type: 'tuple[][]', components: [] },
          { name: 'modes', type: 'uint8[]' },
          { name: 'executions', type: 'tuple[][]', components: [] },
        ],
        outputs: [],
      },
    ] as const,
    functionName: 'redeemDelegations',
    args: [
      [[signedDelegation]], // delegations (array of delegation chains)
      [0], // modes (0 = SingleDefault)
      [[execution]], // executions
    ],
  });

  return bundlerClient.sendUserOperation({
    account: delegateSmartAccount,
    calls: [
      {
        to: delegationManagerAddress,
        data: redeemData,
        value: execution.value,
      },
    ],
  });
}

// Complete flow
async function swapViaSmartAccount(
  quoteResponse: Record<string, unknown>,
  apiKey: string,
  bundlerClient: Parameters<typeof executeViaSmartAccount>[0],
  delegateSmartAccount: Parameters<typeof executeViaSmartAccount>[1],
  delegationManagerAddress: Address,
  signedDelegation: SignedDelegation
): Promise<Hex> {
  // Get swap calldata from Trading API
  const swapCalldata = await getSwapCalldata(quoteResponse, apiKey);

  // Create execution for delegation
  const execution = createExecution(swapCalldata);

  // Submit via bundler
  return executeViaSmartAccount(
    bundlerClient,
    delegateSmartAccount,
    delegationManagerAddress,
    signedDelegation,
    execution
  );
}

Key Considerations

  • Approval target: For smart accounts, approve tokens directly to the Universal Router (legacy mode) rather than using Permit2. This avoids the need for EIP-712 signature flows that are complex with smart accounts.
  • Gas estimation: Bundlers estimate gas differently. Add a 20-30% buffer to the callGasLimit for swap operations.
  • Nonce management: If executing multiple swaps in sequence, handle UserOperation nonces carefully to avoid conflicts.
  • Error handling: Bundler errors differ from standard transaction errors. Check both the UserOperation receipt and the inner execution status.

WETH Handling on L2s

On L2 chains (Base, Optimism, Arbitrum), swaps that output ETH often deliver WETH instead of native ETH. This is because the Universal Router's UNWRAP_WETH command may not be included in all routes, especially when the Trading API optimizes for gas efficiency.

When This Happens

  • Swapping any token to ETH on L2 chains
  • Using smart accounts where the swap recipient is the smart account itself
  • Cross-chain swaps landing on L2s

WETH Addresses by Chain

Chain Chain ID WETH Address
Ethereum 1 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Base 8453 0x4200000000000000000000000000000000000006
Optimism 10 0x4200000000000000000000000000000000000006
Arbitrum 42161 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1

Detection and Unwrap Pattern

import { createPublicClient, createWalletClient, http, parseAbi, type Address } from 'viem';
import { base } from 'viem/chains';

const WETH_ABI = parseAbi([
  'function balanceOf(address) view returns (uint256)',
  'function withdraw(uint256)',
]);

// Chain-specific WETH addresses
const WETH_ADDRESSES: Record<number, Address> = {
  1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
  10: '0x4200000000000000000000000000000000000006',
  8453: '0x4200000000000000000000000000000000000006',
  42161: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
};

async function unwrapWethIfNeeded(
  publicClient: ReturnType<typeof createPublicClient>,
  walletClient: ReturnType<typeof createWalletClient>,
  accountAddress: Address,
  chainId: number
): Promise<void> {
  const wethAddress = WETH_ADDRESSES[chainId];
  if (!wethAddress) return;

  // Check WETH balance after swap
  const wethBalance = await publicClient.readContract({
    address: wethAddress,
    abi: WETH_ABI,
    functionName: 'balanceOf',
    args: [accountAddress],
  });

  if (wethBalance > 0n) {
    // Unwrap WETH to native ETH
    const hash = await walletClient.writeContract({
      address: wethAddress,
      abi: WETH_ABI,
      functionName: 'withdraw',
      args: [wethBalance],
    });

    await publicClient.waitForTransactionReceipt({ hash });
  }
}

Integration with Trading API Flow

// After executing a swap that outputs ETH on an L2:
const receipt = await publicClient.waitForTransactionReceipt({ hash: swapHash });

if (receipt.status === 'success') {
  // Check and unwrap any WETH received instead of native ETH
  await unwrapWethIfNeeded(publicClient, walletClient, account.address, chainId);
}

Rate Limiting Best Practices

The Trading API enforces rate limits to ensure fair usage. Hitting rate limits during batch operations is common and should be handled gracefully.

Known Limits

Endpoint Rate Limit
/check_approval ~10 requests/second
/quote ~10 requests/second
/swap ~10 requests/second

Exponential Backoff Implementation

interface RetryOptions {
  maxRetries: number;
  baseDelayMs: number;
  maxDelayMs: number;
}

const DEFAULT_RETRY_OPTIONS: RetryOptions = {
  maxRetries: 5,
  baseDelayMs: 200,
  maxDelayMs: 10000,
};

async function fetchWithRetry(
  url: string,
  init: RequestInit,
  options: RetryOptions = DEFAULT_RETRY_OPTIONS
): Promise<Response> {
  let lastError: Error | undefined;

  for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
    const response = await fetch(url, init);

    if (response.status !== 429 && response.status < 500) {
      return response;
    }

    if (attempt === options.maxRetries) {
      lastError = new Error(
        `Request failed after ${options.maxRetries} retries: ${response.status}`
      );
      break;
    }

    // Exponential backoff with jitter
    const delay = Math.min(
      options.baseDelayMs * Math.pow(2, attempt) + Math.random() * 100,
      options.maxDelayMs
    );

    await new Promise((resolve) => setTimeout(resolve, delay));
  }

  throw lastError;
}

Batch Operations

When executing multiple swaps or quotes in sequence, add deliberate delays:

// REQUIRED: define decision_origin yourself — 'human_mediated' (a human reviews/approves
// each action before it executes) or 'autonomous' (no per-action human approval).
// See "Agent Attribution" in SKILL.md. There is no default — you must choose.
declare const DECISION_ORIGIN: 'human_mediated' | 'autonomous';

const AGENT_INFO = JSON.stringify({
  integration_name: 'swap-integration',
  decision_origin: DECISION_ORIGIN,
  version: '1.6.0',
});

async function batchQuotes(
  params: QuoteParams[],
  apiKey: string,
  delayMs = 150
): Promise<QuoteResponse[]> {
  const results: QuoteResponse[] = [];

  for (const param of params) {
    const response = await fetchWithRetry('https://trade-api.gateway.uniswap.org/v1/quote', {
      method: 'POST',
      headers: {
        'x-api-key': apiKey,
        'Content-Type': 'application/json',
        'x-agent-info': AGENT_INFO,
      },
      body: JSON.stringify(param),
    });

    const data = await response.json();
    if (!response.ok) throw new Error(data.detail || 'Quote failed');
    results.push(data);

    // Deliberate delay between requests to stay under rate limits
    if (params.indexOf(param) < params.length - 1) {
      await new Promise((resolve) => setTimeout(resolve, delayMs));
    }
  }

  return results;
}

Best Practices Summary

  • Add 100-200ms delays between sequential API calls
  • Implement exponential backoff with jitter for 429 responses
  • Cache approval check results (approvals rarely change between calls)
  • Batch quote requests where possible rather than requesting individually
  • Monitor for 429 responses and adjust delay dynamically

Source: SKILL.md on GitHub

3 warnings10d5 checks · Risk SAFE
  • Gen Agent Trust Hub10d

    This skill provides integration patterns for the Uniswap Trading API and Universal Router. It includes robust input validation guidelines and mandates human-in-the-loop confirmation for all blockchain transactions, adhering to security best practices.

  • Socket10d

    No alerts

  • Snyk10d

    Risk: MEDIUM · 1 issue

  • Runlayer6mo

    2/2 files flagged

  • ZeroLeaks5mo

    2 findings · Score: 76/100

Signed by skilld at bff480c. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated last week
What it can do
Reads files Edits files Runs commands Network
Modelopus
model
opus
metadata
{
  "author": "uniswap",
  "version": "1.6.0"
}
All 12 allowed tools
ReadWriteEditGlobGrepBash(npm:*)Bash(npx:*)Bash(yarn:*)Bash(curl:*)WebFetchAskUserQuestionTask(subagent_type:swap-integration-expert)

README badge

README badge for uniswap/uniswap-ai/swap-integration