> ## Documentation Index
> Fetch the complete documentation index at: https://docs.o1.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Get a quote, build a transaction, and execute your first swap on Base in under five minutes.

This guide walks you through a complete swap from a TypeScript client. We'll trade **1,000 USDC for WETH** on Base, then receive native ETH back instead of WETH.

<Note>
  Production base URL is currently `https://quiet-bloodhound-531.convex.site`. If you're integrating against a vanity domain (e.g. `https://api.o1.exchange/dex`), substitute that throughout.
</Note>

## Prerequisites

<CardGroup cols={3}>
  <Card title="API key" icon="key">
    See [Authentication](/api/dex-aggregator/authentication) to request one.
  </Card>

  <Card title="Base RPC URL" icon="server">
    Any Base mainnet RPC works. We use `viem` defaults below.
  </Card>

  <Card title="Wallet with USDC" icon="wallet">
    Plus a small ETH balance to pay gas (and approve the router if this is your first swap).
  </Card>
</CardGroup>

## 1. Set up your client

```ts theme={null} theme={null}
import { createPublicClient, createWalletClient, http, erc20Abi } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";

const API_URL = "https://quiet-bloodhound-531.convex.site";
const API_KEY = process.env.O1_API_KEY!;

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({ chain: base, transport: http() });
const walletClient = createWalletClient({ chain: base, account, transport: http() });

const USDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
const WETH = "0x4200000000000000000000000000000000000006";
```

## 2. Get a quote

<Tabs>
  <Tab title="Request">
    **Endpoint:** `POST {API_URL}/quote`

    ```ts theme={null} theme={null}
    const quote = await fetch(`${API_URL}/quote`, {
      method: "POST",
      headers: {
        "x-api-key": API_KEY,
        "content-type": "application/json",
      },
      body: JSON.stringify({
        chainId: 8453,
        tokenIn: USDC,
        tokenOut: WETH,
        amountIn: "1000000000",   // 1,000 USDC (6 decimals)
        slippageBps: 100,         // 1% slippage tolerance
      }),
    }).then((r) => r.json());
    ```
  </Tab>

  <Tab title="Response">
    ```json theme={null} theme={null}
    {
      "quoteId": "a1b2c3d4...",
      "expiresAt": 1712180000000,
      "routePlan": {
        "chainId": 8453,
        "tokenIn": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
        "tokenOut": "0x4200000000000000000000000000000000000006",
        "amountIn": "1000000000",
        "expectedAmountOut": "484446072048780734",
        "minAmountOut": "479601611328292926",
        "slippageBps": 100,
        "blockNumber": 44266656,
        "gasEstimate": { "gasUnits": 300000 },
        "routes": [
          {
            "amountIn": "1000000000",
            "legs": [
              {
                "dex": "UNIV3",
                "tokenIn": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
                "tokenOut": "0x4200000000000000000000000000000000000006",
                "amountIn": "1000000000",
                "minOut": "0",
                "poolId": "0xd0b5...f224",
                "data": { "kind": "v3_direct", "pool": "0xd0b5...f224" }
              }
            ]
          }
        ]
      }
    }
    ```
  </Tab>
</Tabs>

The fields you'll display to the user:

* `routePlan.expectedAmountOut` — the price you quote
* `routePlan.minAmountOut` — the worst-case fill after slippage
* `routePlan.routes[].legs[].dex` — which venues are involved
* `expiresAt` — quote validity window (about 10 seconds; see [Quote freshness](/api/dex-aggregator/guides/quote-freshness))

## 3. Approve the router (first swap only)

<Note>
  Skip this step entirely if `tokenIn` is native ETH, or if you're using an EIP-2612 permit (see [`POST /submit`](/api/dex-aggregator/endpoints/submit) → `permit`).
</Note>

```ts theme={null} theme={null}
const ROUTER = "0x8Dc736C6BA12e1Df1c73DB6BED8d7573F0aD90B3";

const allowance = await publicClient.readContract({
  address: USDC,
  abi: erc20Abi,
  functionName: "allowance",
  args: [account.address, ROUTER],
});

if (allowance < BigInt(quote.routePlan.amountIn)) {
  await walletClient.writeContract({
    address: USDC,
    abi: erc20Abi,
    functionName: "approve",
    args: [ROUTER, BigInt(quote.routePlan.amountIn)],
  });
}
```

## 4. Build the transaction

<Tabs>
  <Tab title="Request">
    **Endpoint:** `POST {API_URL}/submit`

    ```ts theme={null} theme={null}
    const submit = await fetch(`${API_URL}/submit`, {
      method: "POST",
      headers: {
        "x-api-key": API_KEY,
        "content-type": "application/json",
      },
      body: JSON.stringify({
        quoteId: quote.quoteId,
        user: account.address,
        unwrapNativeOut: true,   // receive native ETH instead of WETH
      }),
    }).then((r) => r.json());
    ```
  </Tab>

  <Tab title="Response">
    ```json theme={null} theme={null}
    {
      "quoteId": "a1b2c3d4...",
      "chainId": 8453,
      "to": "0x8Dc736C6BA12e1Df1c73DB6BED8d7573F0aD90B3",
      "data": "0x...",
      "value": "0"
    }
    ```
  </Tab>
</Tabs>

## 5. Sign and send

```ts theme={null} theme={null}
const txHash = await walletClient.sendTransaction({
  to: submit.to as `0x${string}`,
  data: submit.data as `0x${string}`,
  value: BigInt(submit.value),
});

console.log(`Submitted: https://basescan.org/tx/${txHash}`);
```

That's it. Wait for the receipt with `publicClient.waitForTransactionReceipt({ hash: txHash })` and the swap is complete.

## Full example, end to end

<Accordion title="Complete TypeScript script">
  ```ts theme={null} theme={null}
  import { createPublicClient, createWalletClient, http, erc20Abi } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { base } from "viem/chains";

  const API_URL = "https://quiet-bloodhound-531.convex.site";
  const API_KEY = process.env.O1_API_KEY!;
  const ROUTER = "0x8Dc736C6BA12e1Df1c73DB6BED8d7573F0aD90B3";

  const USDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
  const WETH = "0x4200000000000000000000000000000000000006";

  async function main() {
    const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
    const publicClient = createPublicClient({ chain: base, transport: http() });
    const walletClient = createWalletClient({ chain: base, account, transport: http() });

    // 1. Quote
    const quote = await fetch(`${API_URL}/quote`, {
      method: "POST",
      headers: { "x-api-key": API_KEY, "content-type": "application/json" },
      body: JSON.stringify({
        chainId: 8453,
        tokenIn: USDC,
        tokenOut: WETH,
        amountIn: "1000000000",
        slippageBps: 100,
      }),
    }).then((r) => r.json());

    console.log(`Expected out: ${quote.routePlan.expectedAmountOut} WETH (wei)`);

    // 2. Approve if needed
    const allowance = await publicClient.readContract({
      address: USDC,
      abi: erc20Abi,
      functionName: "allowance",
      args: [account.address, ROUTER],
    });
    if (allowance < BigInt(quote.routePlan.amountIn)) {
      const approveHash = await walletClient.writeContract({
        address: USDC,
        abi: erc20Abi,
        functionName: "approve",
        args: [ROUTER, BigInt(quote.routePlan.amountIn)],
      });
      await publicClient.waitForTransactionReceipt({ hash: approveHash });
    }

    // 3. Submit
    const submit = await fetch(`${API_URL}/submit`, {
      method: "POST",
      headers: { "x-api-key": API_KEY, "content-type": "application/json" },
      body: JSON.stringify({
        quoteId: quote.quoteId,
        user: account.address,
        unwrapNativeOut: true,
      }),
    }).then((r) => r.json());

    // 4. Send
    const txHash = await walletClient.sendTransaction({
      to: submit.to as `0x${string}`,
      data: submit.data as `0x${string}`,
      value: BigInt(submit.value),
    });

    const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
    console.log(`Confirmed in block ${receipt.blockNumber}`);
  }

  main().catch(console.error);
  ```
</Accordion>

## What's next

<CardGroup cols={2}>
  <Card title="Two-step vs one-step" icon="code-branch" href="/api/dex-aggregator/guides/integration-patterns">
    When to use `/quote` + `/submit` vs `/execute`.
  </Card>

  <Card title="ERC-20 approvals" icon="lock-open" href="/api/dex-aggregator/guides/erc20-approvals">
    Approve patterns, including one-tx permit-enabled swaps.
  </Card>

  <Card title="Native ETH" icon="ethereum" href="/api/dex-aggregator/guides/native-eth">
    Sell ETH directly without wrapping, or receive ETH instead of WETH.
  </Card>

  <Card title="Quote freshness" icon="clock" href="/api/dex-aggregator/guides/quote-freshness">
    How to keep the displayed price live in your UI.
  </Card>
</CardGroup>
