> For the complete documentation index, see [llms.txt](https://docs.crosscurve.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.crosscurve.fi/developer-documentation/integration-guide-old/direct-integration-with-rest-api/eoa-swap-example.md).

# EOA Swap example

### EOA Swap flow

A standard wallet swap (MetaMask, WalletConnect, or any EOA signer) in six steps:

1. Fetch supported networks and tokens
2. Find a route
3. Approve token spending (ERC-20 only)
4. Send swap transaction
5. Extract requestId from receipt
6. Poll for completion

#### Step 1: Get Networks and Tokens

```
GET /networks
```

Returns an object keyed by network name. Each entry contains the chain configuration and supported tokens.

```json
{
  "sepolia": {
    "name": "sepolia",
    "chainId": 11155111,
    "router": "0x...",
    "tokens": [
      {
        "chainId": 11155111,
        "address": "0x...",
        "name": "USD Coin",
        "symbol": "USDC",
        "decimals": 6,
        "tags": ["erc20", "stable"],
        "permit": false
      }
    ]
  }
}
```

Use `chainId` and token `address` values from this response when building a scan request.

#### Step 2: Find a Route

```
POST /routing/scan/stream
```

Request body:

```json
{
  "from": "0xYOUR_WALLET_ADDRESS",
  "recipient": "0xRECIPIENT_ADDRESS",
  "params": {
    "tokenIn": "0xTOKEN_IN",
    "amountIn": "1000000",
    "chainIdIn": 11155111,
    "tokenOut": "0xTOKEN_OUT",
    "chainIdOut": 64165
  },
  "slippage": 1
}
```

| Field               | Type      | Required | Description                                                |
| ------------------- | --------- | -------- | ---------------------------------------------------------- |
| `from`              | string    | Yes      | Sender address                                             |
| `recipient`         | string    | No       | Recipient address (defaults to sender)                     |
| `params.tokenIn`    | string    | Yes      | Input token address                                        |
| `params.amountIn`   | string    | Yes      | Amount in smallest token units                             |
| `params.chainIdIn`  | number    | Yes      | Source chain ID                                            |
| `params.tokenOut`   | string    | Yes      | Output token address                                       |
| `params.chainIdOut` | number    | Yes      | Destination chain ID                                       |
| `slippage`          | number    | Yes      | Slippage tolerance in percent                              |
| `feeShareBps`       | number    | No       | Partner fee in basis points (100 = 1%)                     |
| `providers`         | string\[] | No       | Filter by provider: `"cross-curve"`, `"rubic"`, `"bungee"` |

The response is NDJSON — each line is a separate JSON object:

**Success line:**

```json
{ "route": [...], "simulation": { /* route object — see Routing Response */ } }
```

**Error line (route could not be evaluated):**

```json
{ "route": [...], "error": "error message" }
```

Collect the `simulation` objects from success lines. Routes arrive in arbitrary order, so sort by `amountOut` descending to find the best route and pass it to the next step.

> You can also use `POST /routing/scan` which returns all routes as a single JSON array. The request body is the same. Each array item has the same shape as the `simulation` object above.

#### Step 3: Build the Transaction

```
POST /tx/create
```

Request body:

```json
{
  "from": "0xYOUR_WALLET_ADDRESS",
  "recipient": "0xRECIPIENT_ADDRESS",
  "routing": { },
  "buildCalldata": true
}
```

| Field           | Type    | Required | Description                                                                              |
| --------------- | ------- | -------- | ---------------------------------------------------------------------------------------- |
| `from`          | string  | Yes      | Sender address                                                                           |
| `recipient`     | string  | Yes      | Recipient address                                                                        |
| `routing`       | object  | Yes      | The `simulation` object from `/routing/scan/stream` (or array item from `/routing/scan`) |
| `buildCalldata` | boolean | No       | When `true`, returns encoded calldata ready to send                                      |

Pass the entire route object as `routing`. Do not modify or cherry-pick fields from it.

**Response when `buildCalldata` is `true`:**

```json
{
  "to": "0xROUTER_ADDRESS",
  "data": "0xENCODED_CALLDATA...",
  "value": "0"
}
```

**Response when `buildCalldata` is `false` or omitted:**

```json
{
  "to": "0xROUTER_ADDRESS",
  "abi": "[...]",
  "args": [...],
  "value": "0"
}
```

#### Step 3: Approve Token Spending (ERC-20 only)

If the input token is an ERC-20 (not the native token), approve the router contract to spend your tokens before sending the swap transaction. The router address is the `to` field from the `/tx/create` response.

```js
// Check current allowance
const allowance = await tokenContract.allowance(walletAddress, tx.to);

if (allowance < BigInt(route.amountIn)) {
  await tokenContract.approve(tx.to, route.amountIn);
}
```

Skip this step if the input token is the chain's native token (ETH, MATIC, etc.) — native tokens do not require approval.

> For ERC-4337 and EIP-7702 wallets, the approval is already included in the `calls[]` array returned by `/tx/create`. This step applies only to EOA transactions.

#### Step 4: Send Swap Transaction

Send the transaction on-chain using your wallet or signer.

#### Step 5: Extract requestId

After the transaction is confirmed, extract the `requestId` from the `ComplexOpProcessed` event in the receipt:

```js
const COMPLEX_OP_TOPIC = "0x830adbcf80ee865e0f0883ad52e813fdbf061b0216b724694a2b4e06708d243c";
const log = receipt.logs.find((l) => l.topics[0] === COMPLEX_OP_TOPIC);
const requestId = log.topics[2]; // currentRequestId (bytes32)
```

#### Step 6: Poll for Completion

Poll `GET /transaction/{requestId}` until the status reaches a final state.

See Transaction Tracking for response format and polling recommendations.

#### Full Example (JavaScript + ethers.js)

```js
import { ethers } from "ethers";

const API = "https://api.crosscurve.fi";
const API_KEY = "YOUR_API_KEY";

const provider = new ethers.JsonRpcProvider("https://YOUR_RPC_URL");
const signer = new ethers.Wallet("0xYOUR_PRIVATE_KEY", provider);
const WALLET_ADDRESS = signer.address;

const TOKEN_IN = "0xTOKEN_IN";
const TOKEN_OUT = "0xTOKEN_OUT";
const CHAIN_ID_IN = 11155111;
const CHAIN_ID_OUT = 64165;
const AMOUNT_IN = "1000000"; // in smallest token units

const ERC20_ABI = [
  "function approve(address spender, uint256 amount) returns (bool)",
  "function allowance(address owner, address spender) view returns (uint256)",
];

// Helper: parse NDJSON stream into an array of route objects
async function scanRoutes(body) {
  const response = await fetch(`${API}/routing/scan/stream`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Accept": "application/x-ndjson",
      "api-key": API_KEY,
    },
    body: JSON.stringify(body),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  const routes = [];
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    let idx;
    while ((idx = buffer.indexOf("\n")) >= 0) {
      const line = buffer.slice(0, idx).trim();
      buffer = buffer.slice(idx + 1);
      if (!line) continue;
      const parsed = JSON.parse(line);
      if (parsed.simulation) routes.push(parsed.simulation);
    }
  }
  if (buffer.trim()) {
    const parsed = JSON.parse(buffer);
    if (parsed.simulation) routes.push(parsed.simulation);
  }

  return routes;
}

// Step 1: Find a route
const routes = await scanRoutes({
  from: WALLET_ADDRESS,
  recipient: WALLET_ADDRESS,
  params: {
    tokenIn: TOKEN_IN,
    amountIn: AMOUNT_IN,
    chainIdIn: CHAIN_ID_IN,
    tokenOut: TOKEN_OUT,
    chainIdOut: CHAIN_ID_OUT,
  },
  slippage: 1,
});
routes.sort((a, b) => {
  if (BigInt(a.amountOut) > BigInt(b.amountOut)) return -1;
  if (BigInt(a.amountOut) < BigInt(b.amountOut)) return 1;
  return 0;
});
const route = routes[0];

// Step 2: Build the transaction
const txRes = await fetch(`${API}/tx/create`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    from: WALLET_ADDRESS,
    recipient: WALLET_ADDRESS,
    routing: route,
    buildCalldata: true,
  }),
});
const tx = await txRes.json();

// Step 3: Approve router if input is ERC-20 (skip for native token)
const tokenContract = new ethers.Contract(TOKEN_IN, ERC20_ABI, signer);
const allowance = await tokenContract.allowance(WALLET_ADDRESS, tx.to);
if (allowance < BigInt(route.amountIn)) {
  const approveTx = await tokenContract.approve(tx.to, route.amountIn);
  await approveTx.wait();
}

// Step 4: Send swap on-chain
const swapTx = await signer.sendTransaction({
  to: tx.to,
  data: tx.data,
  value: tx.value,
});
const receipt = await swapTx.wait();

// Step 5: Extract requestId from receipt
const COMPLEX_OP_TOPIC = "0x830adbcf80ee865e0f0883ad52e813fdbf061b0216b724694a2b4e06708d243c";
const log = receipt.logs.find((l) => l.topics[0] === COMPLEX_OP_TOPIC);
const requestId = log.topics[2]; // currentRequestId (bytes32)

// Step 6: Poll for completion
async function pollStatus(requestId) {
  while (true) {
    const res = await fetch(`${API}/transaction/${requestId}`);
    const status = await res.json();

    console.log("Status:", status.status);

    if (["completed", "failed", "reverted", "canceled"].includes(status.status)) {
      return status;
    }

    await new Promise((r) => setTimeout(r, 12000));
  }
}

const result = await pollStatus(requestId);
console.log("Final status:", result.status);
```

#### Full Example (TypeScript + viem)

```ts
import { createWalletClient, createPublicClient, http, parseAbi, parseEventLogs } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";

const API = "https://api.crosscurve.fi";
const API_KEY = "YOUR_API_KEY";

const TOKEN_IN = "0xTOKEN_IN";
const TOKEN_OUT = "0xTOKEN_OUT";
const CHAIN_ID_IN = 11155111;
const CHAIN_ID_OUT = 64165;
const AMOUNT_IN = "1000000";

const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY");
const publicClient = createPublicClient({ chain: sepolia, transport: http() });
const walletClient = createWalletClient({
  account,
  chain: sepolia,
  transport: http(),
});

// Helper: parse NDJSON stream into an array of route objects
async function scanRoutes(body: Record<string, unknown>) {
  const response = await fetch(`${API}/routing/scan/stream`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Accept": "application/x-ndjson",
      "api-key": API_KEY,
    },
    body: JSON.stringify(body),
  });

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();
  const routes: unknown[] = [];
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    let idx: number;
    while ((idx = buffer.indexOf("\n")) >= 0) {
      const line = buffer.slice(0, idx).trim();
      buffer = buffer.slice(idx + 1);
      if (!line) continue;
      const parsed = JSON.parse(line);
      if (parsed.simulation) routes.push(parsed.simulation);
    }
  }
  if (buffer.trim()) {
    const parsed = JSON.parse(buffer);
    if (parsed.simulation) routes.push(parsed.simulation);
  }

  return routes;
}

// Step 1: Find a route
const routes = await scanRoutes({
  from: account.address,
  recipient: account.address,
  params: {
    tokenIn: TOKEN_IN,
    amountIn: AMOUNT_IN,
    chainIdIn: CHAIN_ID_IN,
    tokenOut: TOKEN_OUT,
    chainIdOut: CHAIN_ID_OUT,
  },
  slippage: 1,
});
routes.sort((a: any, b: any) => {
  if (BigInt(a.amountOut) > BigInt(b.amountOut)) return -1;
  if (BigInt(a.amountOut) < BigInt(b.amountOut)) return 1;
  return 0;
});
const route = routes[0];

// Step 2: Build the transaction
const txRes = await fetch(`${API}/tx/create`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    from: account.address,
    recipient: account.address,
    routing: route,
    buildCalldata: true,
  }),
});
const tx: { to: string; data: string; value: string } = await txRes.json();

// Step 3: Approve router if input is ERC-20 (skip for native token)
const erc20Abi = parseAbi([
  "function approve(address,uint256) returns (bool)",
  "function allowance(address,address) view returns (uint256)",
]);
const allowance = await publicClient.readContract({
  address: TOKEN_IN as `0x${string}`,
  abi: erc20Abi,
  functionName: "allowance",
  args: [account.address, tx.to as `0x${string}`],
});
if (allowance < BigInt(route.amountIn)) {
  await walletClient.writeContract({
    address: TOKEN_IN as `0x${string}`,
    abi: erc20Abi,
    functionName: "approve",
    args: [tx.to as `0x${string}`, BigInt(route.amountIn)],
  });
}

// Step 4: Send swap on-chain
const hash = await walletClient.sendTransaction({
  to: tx.to as `0x${string}`,
  data: tx.data as `0x${string}`,
  value: BigInt(tx.value),
});

console.log("Transaction sent:", hash);

// Step 5: Extract requestId from receipt
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const complexOpAbi = parseAbi([
  "event ComplexOpProcessed(uint64 indexed currentChainId, bytes32 indexed currentRequestId, uint64 indexed nextChainId, bytes32 nextRequestId, uint8 result, uint8 lastOp)",
]);
const events = parseEventLogs({ abi: complexOpAbi, logs: receipt.logs, eventName: "ComplexOpProcessed" });
const requestId = events[0].args.currentRequestId;

// Step 6: Poll for completion
async function pollStatus(requestId: string) {
  while (true) {
    const res = await fetch(`${API}/transaction/${requestId}`);
    const status = await res.json();

    console.log("Status:", status.status);

    if (["completed", "failed", "reverted", "canceled"].includes(status.status)) {
      return status;
    }

    await new Promise((r) => setTimeout(r, 12000));
  }
}
```

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.crosscurve.fi/developer-documentation/integration-guide-old/direct-integration-with-rest-api/eoa-swap-example.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
