# Integration Guide

> **Base URL**: `https://pusher-cdp.x.ubtk.dev` \
> **Swagger UI**: <https://pusher-cdp.x.ubtk.dev/api-docs> \
> **TypeScript SDK**: [@crosscurve/sdk](https://www.npmjs.com/package/@crosscurve/sdk) \
> **General Documentation**: <https://docs.crosscurve.fi>

***

## Table of Contents

1. [Integration Overview](#integration-overview)
2. [TypeScript SDK](#typescript-sdk)
3. [Integration Architecture](#integration-architecture)
4. [Supported Networks](#supported-networks)
5. [Reference Data](#reference-data)
6. [Executing Cross-Chain Swaps](#executing-cross-chain-swaps)
7. [Token Approval](#token-approval)
8. [Transaction Tracking](#transaction-tracking)
9. [Error Handling](#error-handling)
10. [API Reference](#api-reference)
11. [Code Examples](#code-examples)
12. [Rate Limits and Best Practices](#rate-limits-and-best-practices)
13. [Limitations](#limitations)
14. [Contract Addresses](#contract-addresses)
15. [Fee Structure](#fee-structure)
16. [FAQ](#faq)
17. [Glossary](#glossary)
18. [Changelog](#changelog)

***

## Integration Overview

### Minimum Flow

```
1. GET /networks          → Get list of networks and contract addresses
2. GET /tokenlist         → Get list of tokens (filter: can_swap)
3. POST /routing/scan     → Find swap route (includes signature)
4. POST /tx/create        → Build transaction
5. [Approve token]        → Allow contract to use tokens (if not already done)
6. [Send TX]              → Sign and send via ethers/web3
7. GET /search?search=    → Get requestId by hash (wait ~3 sec)
8. GET /transaction/{id}  → Track status until completed
```

### Example: Swap CRV (Arbitrum) → CRV (Ethereum)

> Example for understanding the flow. Verify current addresses via `/tokenlist`.

```javascript
import { ethers } from 'ethers';

const API_BASE = 'https://api.crosscurve.fi';

// Initialize provider and signer (example for browser with MetaMask)
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();

// 1. Find route
const routeResponse = await fetch(`${API_BASE}/routing/scan`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    params: {
      chainIdIn: 42161,       // Arbitrum
      chainIdOut: 1,          // Ethereum
      tokenIn: '0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978',   // CRV on Arbitrum
      tokenOut: '0xD533a949740bb3306d119CC777fa900bA034cd52',  // CRV on Ethereum
      amountIn: '1000000000000000000000'  // 1000 CRV (18 decimals)
    },
    slippage: 1  // 1%
  })
});

const routes = await routeResponse.json();
if (!routes.length) {
  throw new Error('No routes available for this pair');
}

const selectedRoute = routes[0]; // first route from list
console.log(`Expected output: ${selectedRoute.amountOut}`);
console.log(`Price impact: ${selectedRoute.priceImpact}%`);
console.log(`Fee: ${selectedRoute.totalFee.percent}%`);
console.log(`Signature included: ${!!selectedRoute.signature}`);

// 2. Build transaction
// buildCalldata: true — returns ready calldata for sendTransaction
// signature is already included in selectedRoute from /routing/scan
const txResponse = await fetch(`${API_BASE}/tx/create`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    from: '0xYourWalletAddress',      // With correct checksum!
    recipient: '0xRecipientAddress',  // Can match from
    routing: selectedRoute,           // Includes signature
    buildCalldata: true               // Get ready calldata
  })
});

const txData = await txResponse.json();
// txData: { to, value, data } — ready for sendTransaction

// 3. Approve tokens (check allowance to avoid unnecessary TX)
const tokenContract = new ethers.Contract(
  '0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978', // CRV token on Arbitrum
  ['function approve(address spender, uint256 amount) returns (bool)',
   'function allowance(address owner, address spender) view returns (uint256)'],
  signer
);
const ownerAddress = await signer.getAddress();
const allowance = await tokenContract.allowance(ownerAddress, txData.to);
if (allowance.lt('1000000000000000000000')) { // if allowance < amountIn
  await tokenContract.approve(txData.to, ethers.constants.MaxUint256);
  console.log('Token approved');
}

// 4. Send transaction to blockchain
const tx = await signer.sendTransaction({
  to: txData.to,
  data: txData.data,
  value: txData.value || '0'
});
console.log(`TX sent: ${tx.hash}`);

const receipt = await tx.wait();
console.log(`TX confirmed in block: ${receipt.blockNumber}`);

// 5. Get requestId
// Option A: via API (wait for indexing ~3 sec)
await new Promise(r => setTimeout(r, 3000));
const searchResponse = await fetch(`${API_BASE}/search?search=${tx.hash}`);
const searchResult = await searchResponse.json();
let requestId = searchResult.result[0]?.requestId;

// Option B: from transaction events (faster, no waiting)
if (!requestId) {
  const COMPLEX_OP_TOPIC = '0x830adbcf80ee865e0f0883ad52e813fdbf061b0216b724694a2b4e06708d243c';
  const log = receipt.logs.find(l => l.topics[0] === COMPLEX_OP_TOPIC);
  if (log) {
    // Decode event to get nextRequestId
    const iface = new ethers.utils.Interface([
      'event ComplexOpProcessed(uint64 indexed currentChainId, bytes32 indexed currentRequestId, uint64 nextChainId, bytes32 nextRequestId, uint8 result, uint8 lastOp)'
    ]);
    const decoded = iface.parseLog(log);
    requestId = decoded.args.nextRequestId;
  }
}

console.log(`RequestId: ${requestId}`);

// 6. Track status (polling every 5 sec)
let status;
for (let i = 0; i < 60; i++) { // max 5 minutes
  const statusResponse = await fetch(`${API_BASE}/transaction/${requestId}`);
  status = await statusResponse.json();
  console.log(`Status: ${status.status}`);

  if (status.status === 'completed') {
    console.log('Swap completed!');
    break;
  }
  if (status.destination?.status === 'retry') {
    console.log('Retry required via alternative bridge, see /tx/create/retry');
    break;
  }
  if (status.inconsistency) {
    console.log('Refund required, see /inconsistency');
    break;
  }
  if (status.destination?.emergency) {
    console.log('Recovery required, see /tx/create/emergency');
    break;
  }

  await new Promise(r => setTimeout(r, 5000)); // wait 5 sec
}
```

> **Important:** Addresses must be in checksum format (EIP-55). Use `ethers.utils.getAddress(address)` for conversion.

### Test Examples

| Source Network   | Target Network   | Token In | Token Out |
| ---------------- | ---------------- | -------- | --------- |
| Arbitrum (42161) | Ethereum (1)     | CRV      | CRV       |
| Optimism (10)    | Arbitrum (42161) | USDC     | USDC      |
| Arbitrum (42161) | Polygon (137)    | USDT     | USDT      |

> Get token addresses from `/tokenlist`.

### If No Route Found

If `/routing/scan` returns an empty array `[]`:

* Check that tokens have the `can_swap` tag
* Try changing the amount (too small/large)
* Try a different token pair
* Route requires liquidity in supported pools

### cURL Examples for Quick Testing

Test the API without writing code:

```bash
# 1. Get list of networks
curl -s "https://api.crosscurve.fi/networks?type=0" | jq 'keys'

# 2. Get tokens
curl -s "https://api.crosscurve.fi/tokenlist" | jq '.ethereum.tokens[:3]'

# 3. Find route (Arbitrum CRV → Ethereum CRV)
curl -X POST "https://api.crosscurve.fi/routing/scan" \
  -H "Content-Type: application/json" \
  -d '{
    "params": {
      "chainIdIn": 42161,
      "chainIdOut": 1,
      "tokenIn": "0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978",
      "tokenOut": "0xD533a949740bb3306d119CC777fa900bA034cd52",
      "amountIn": "1000000000000000000000"
    },
    "slippage": 1
  }' | jq '.[0] | {amountIn, amountOut, priceImpact}'

# 4. Check transaction status
curl -s "https://api.crosscurve.fi/transaction/<REQUEST_ID>" | jq '{status, inconsistency}'

# 5. Get token price
curl -s "https://api.crosscurve.fi/prices/0xD533a949740bb3306d119CC777fa900bA034cd52/1"
```

***

## TypeScript SDK

Alternative to direct API calls — TypeScript SDK with type safety and built-in auto-recovery.

### Installation

```bash
npm install @crosscurve/sdk
# + one of the providers
npm install viem    # or ethers / web3
```

### Quick Example

```typescript
import { CrossCurveSDK, ViemAdapter } from '@crosscurve/sdk'
import { createWalletClient, createPublicClient, http } from 'viem'
import { arbitrum } from 'viem/chains'

// Initialize
const sdk = new CrossCurveSDK()
await sdk.init()

// Setup signer (Viem)
const walletClient = createWalletClient({ account, chain: arbitrum, transport: http() })
const publicClient = createPublicClient({ chain: arbitrum, transport: http() })
const signer = new ViemAdapter(walletClient, publicClient, account)

// Get quote
const quote = await sdk.getQuote({
  fromChain: 42161,       // Arbitrum
  toChain: 1,             // Ethereum
  fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',  // USDC on Arbitrum
  toToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',    // USDC on Ethereum
  amount: '1000000000',   // 1000 USDC (6 decimals)
  slippage: 0.5,          // 0.5%
  sender: account.address
})

// Execute swap
const result = await sdk.executeQuote(quote, {
  signer,
  autoRecover: true  // automatic recovery on failures
})

// Track
await sdk.trackTransaction(result.requestId)
```

### Key Methods

| Method               | Description            |
| -------------------- | ---------------------- |
| `init()`             | Load chains and tokens |
| `getQuote()`         | Get quote              |
| `executeQuote()`     | Execute swap           |
| `trackTransaction()` | Track status           |
| `recover()`          | Manual recovery        |

### Supported Providers

* **ViemAdapter** (recommended)
* **EthersV6Adapter**
* **EthersV5Adapter**
* **Web3Adapter**

> Full documentation: [GitHub](https://github.com/eywa-protocol/crosscurve-sdk) | [npm](https://www.npmjs.com/package/@crosscurve/sdk)

***

## Integration Architecture

### Cross-Chain Swap Stages

| Stage          | Description                                | API / Action                      |
| -------------- | ------------------------------------------ | --------------------------------- |
| 1. Discovery   | Get reference data                         | `GET /networks`, `GET /tokenlist` |
| 2. Routing     | Find available routes (includes signature) | `POST /routing/scan`              |
| 3. Transaction | Build calldata                             | `POST /tx/create`                 |
| 4. Approval    | Allow token spending                       | `token.approve()` (ERC-20)        |
| 5. Execution   | Send transaction                           | `signer.sendTransaction()`        |
| 6. Lookup      | Get requestId                              | `GET /search?search={txHash}`     |
| 7. Tracking    | Track status                               | `GET /transaction/{requestId}`    |

***

## Supported Networks

Get current list via `GET /networks`. Examples:

| Network  | Chain ID | API Name   |
| -------- | -------- | ---------- |
| Ethereum | 1        | `ethereum` |
| Arbitrum | 42161    | `arbitrum` |
| Optimism | 10       | `optimism` |
| Polygon  | 137      | `polygon`  |
| BSC      | 56       | `bsc`      |

> 20+ EVM networks supported. See `/networks` for current list.

***

## Reference Data

### GET /networks

Get list of supported blockchain networks.

**Query Parameters:**

| Parameter | Type   | Description                          |
| --------- | ------ | ------------------------------------ |
| `type`    | number | Filter: `0` - mainnet, `1` - testnet |

**Example Request:**

```bash
curl "https://api.crosscurve.fi/networks?type=0"
```

**Example Response:**

```json
{
  "ethereum": {
    "name": "ethereum",
    "chainId": 1,
    "symbol": "ETH",
    "portal": "0x...",
    "synthesis": "0x...",
    "router": "0x...",
    "tokens": [...]
  }
}
```

> **Important:** Keys are network names (`ethereum`), not chainId.

***

### GET /tokenlist

Get list of tokens.

**Example Request:**

```bash
curl "https://api.crosscurve.fi/tokenlist"
```

**Example Response:**

```json
{
  "ethereum": {
    "tags": ["erc20", "native", "stable", "can_swap", ...],
    "tokens": [
      {
        "chainId": 1,
        "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "symbol": "USDC",
        "decimals": 6,
        "tags": ["erc20", "stable", "can_swap"],
        "permit": false
      }
    ]
  }
}
```

> **Important:** Keys are network names. For swaps, check that token has `can_swap` tag.

**Main Token Tags:**

| Tag              | Description                        |
| ---------------- | ---------------------------------- |
| `can_swap`       | Available for cross-chain swap     |
| `stable`         | Stablecoin                         |
| `native`         | Native network token (ETH, BNB...) |
| `wrapped_native` | Wrapped version (WETH, WBNB...)    |
| `curve_lp`       | Curve LP token                     |

***

### GET /prices/{token}

Get token price in USD.

**Path Parameters:**

| Parameter | Type   | Description              |
| --------- | ------ | ------------------------ |
| `token`   | string | Token address (checksum) |

> **Important:** Use token address, not symbol. Symbols (USDC, CRV) are not supported. Recommended to use `/prices/{token}/{chainId}` for unambiguity.

**Example:**

```bash
curl "https://api.crosscurve.fi/prices/0xD533a949740bb3306d119CC777fa900bA034cd52"
# Response: "0.360043"
```

***

### GET /prices/{token}/{chainId}

Get token price in specific network.

**Example:**

```bash
curl "https://api.crosscurve.fi/prices/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/1"
```

**Example Response:** `0.999827` (number, price in USD)

***

### POST /prices

Batch get prices for multiple tokens.

**Request Body:**

```json
{
  "tokens": [
    {"address": "0xD533a949740bb3306d119CC777fa900bA034cd52", "chainId": 1},
    {"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "chainId": 42161}
  ]
}
```

**Response:**

```json
[
  {"address": "0xD533a949740bb3306d119CC777fa900bA034cd52", "price": 0.35226},
  {"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "price": 0.999835}
]
```

***

## Executing Cross-Chain Swaps

### POST /routing/scan

Find available routes for swap.

**Headers:**

| Header         | Required | Description                                                                                                                                                                  |
| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type` | Yes      | `application/json`                                                                                                                                                           |
| `api-key`      | No       | Partner API key (Free or Standard). When provided, response includes `feeShare`, `feeShareRecipient`, `feeShareToken` fields. Standard keys also allow setting `feeShareBps` |

**Request Body:**

```json
{
  "params": {
    "chainIdIn": 1,
    "chainIdOut": 42161,
    "tokenIn": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "tokenOut": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
    "amountIn": "1000000000"
  },
  "slippage": 0.5,
  "feeShareBps": 50,
  "feeFromAmount": false,
  "feeToken": "0x0000000000000000000000000000000000000000",
  "from": "0xYourWalletAddress",
  "providers": ["AXELAR", "LAYER_ZERO"],
  "feeShareBps": 0
}
```

**Parameters:**

| Field               | Type      | Required | Description                                                                                  |
| ------------------- | --------- | -------- | -------------------------------------------------------------------------------------------- |
| `params.chainIdIn`  | number    | Yes      | Source chain ID                                                                              |
| `params.chainIdOut` | number    | Yes      | Target chain ID                                                                              |
| `params.tokenIn`    | string    | Yes      | Source token address                                                                         |
| `params.tokenOut`   | string    | Yes      | Target token address                                                                         |
| `params.amountIn`   | string    | Yes      | Amount in smallest units                                                                     |
| `slippage`          | number    | Yes      | Allowed slippage (%)                                                                         |
| `feeFromAmount`     | boolean   | No       | Deduct fee from input amount                                                                 |
| `feeToken`          | string    | No       | Token address to pay fees in                                                                 |
| `from`              | string    | No       | User wallet address (for gas estimation)                                                     |
| `feeShareBps`       | number    | No       | Integrator commission in basis points (e.g., `50` = 0.5%). Only works with Standard API keys |
| `providers`         | string\[] | No       | Filter routing providers (e.g., `["AXELAR", "LAYER_ZERO"]`)                                  |

**Response:**

```json
[
  {
    "query": {
      "params": { ... },
      "slippage": 0.5
    },
    "route": [...],
    "amountIn": "1000000000",
    "amountOut": "998500000",              // Minimum with slippage (guaranteed)
    "amountOutWithoutSlippage": "1003500000", // Ideal amount without slippage
    "tokenInPrice": 1.0,
    "tokenOutPrice": 0.9998,
    "priceImpact": 0.15,
    "totalFee": {
      "type": "aggregation",
      "amount": "1500000",
      "percent": "0.15"
    },
    "sourceFee": {
      "token": "0x0000000000000000000000000000000000000000",
      "amount": "500000000000000",
      "usd": 1.2
    },
    "deliveryFee": {
      "token": "0x0000000000000000000000000000000000000000",
      "amount": "1500000000000000",
      "usd": 3.5
    },
    "feeShare": "50000000000000",
    "feeShareRecipient": "0x1234567890abcdef1234567890abcdef12345678",
    "feeShareToken": "0x0000000000000000000000000000000000000000",
    "txs": [
      {
        "chainId": 1,
        "gasFeeNative": "0.005",
        "gasFeeUsd": 12.5,
        "gasConsumption": 250000,
        "consumptions": [
          { "gasConsumption": 250000, "bridge": null, "type": "start" },
          { "gasConsumption": 150000, "bridge": "AXELAR", "type": "data" },
          { "gasConsumption": 80000, "bridge": "LAYER_ZERO", "type": "hash" }
        ]
      }
    ],
    "expectedFinalitySeconds": "180",
    "signature": "0x..."
  }
]
```

**Additional Response Fields:**

| Field               | Description                              |
| ------------------- | ---------------------------------------- |
| `sourceFee`         | Fee on source chain (token, amount, usd) |
| `deliveryFee`       | Delivery fee (token, amount, usd)        |
| `feeShare`          | Partner fee share (only with `api-key`)  |
| `feeShareRecipient` | Partner fee recipient address            |
| `feeShareToken`     | Partner fee token                        |
| `consumptions`      | Gas consumption breakdown by bridge      |
| `signature`         | Route signature (for CDP API)            |

> **Response structure:** Array of objects where each object is a separate route. Select one route and pass it entirely to `/tx/create`. The `route` field inside the object is an array of steps for that route.

> Empty array `[]` — no route found. See [If No Route Found](#if-no-route-found).

***

### POST /estimate

> **DEPRECATED:** This endpoint is no longer supported. The signature is now included directly in the `/routing/scan` response. Use the route from `/routing/scan` directly in `/tx/create`.

Get operation estimate with signature for execution.

**Request Body:** **Entire route object** from `/routing/scan` (pass whole object, not individual fields)

**Response:**

```json
{
  "priceInDollars": "1000.00",
  "stablePrice": "1000000000",
  "executionPrice": "998500000",
  "workerFee": "1500000",
  "deadline": "1703001600",
  "signature": "0x..."
}
```

***

### POST /tx/create

Build transaction data for blockchain submission.

**Request Body:**

```json
{
  "from": "0xYourAddress",
  "recipient": "0xRecipientAddress",
  "routing": { /* object from /routing/scan, includes signature */ },
  "permit": {
    "v": 28,
    "r": "0x...",
    "s": "0x..."
  },
  "buildCalldata": true
}
```

**Parameters:**

| Field           | Type    | Required | Description                                                             |
| --------------- | ------- | -------- | ----------------------------------------------------------------------- |
| `from`          | string  | Yes      | Sender address                                                          |
| `recipient`     | string  | Yes      | Recipient address                                                       |
| `routing`       | object  | Yes      | **Entire object** of route from `/routing/scan` (includes signature)    |
| `estimate`      | object  | No       | **DEPRECATED** — not required, signature is already included in routing |
| `permit`        | object  | No       | EIP-2612 permit signature                                               |
| `buildCalldata` | boolean | No       | Build calldata                                                          |

> **Note:** The `estimate` field is no longer required — signature is already included in the `routing` object from `/routing/scan`.

**Response (with `buildCalldata: true`):** — recommended

```json
{
  "to": "0xContractAddress",
  "value": "0",
  "data": "0x..."
}
```

Ready for `sendTransaction({ to, data, value })`.

**Response (without `buildCalldata`):**

```json
{
  "to": "0xContractAddress",
  "value": "1500000000000000",
  "args": [
    ["LM", "SW"],
    ["0x...", "0x..."],
    {
      "invoice": {
        "executionPrice": "1500000000000000",
        "deadline": "1699999999",
        "v": 27,
        "r": "0x...",
        "s": "0x..."
      },
      "feeShare": "0",
      "feeShareRecipient": "0x0000000000000000000000000000000000000000",
      "feeToken": "0x0000000000000000000000000000000000000000"
    },
    "0x..."
  ],
  "abi": "function start(string[] calldata operations, bytes[] calldata params, tuple(...) receipt, bytes bridgeOptions)"
}
```

Requires call via `ethers.Contract`.

***

## Token Approval

Before executing a swap, you must allow the CrossCurve contract to use your tokens.

> **Where to get spenderAddress?** Use `txData.to` from `/tx/create` response — this is the contract address that will spend your tokens.

### Standard Approve (ERC-20)

```javascript
import { ethers } from 'ethers';

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

async function approveToken(
  tokenAddress: string,
  spenderAddress: string,  // txData.to from /tx/create
  amount: string,
  signer: ethers.Signer
) {
  const token = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
  const ownerAddress = await signer.getAddress();

  // Check current allowance
  const currentAllowance = await token.allowance(ownerAddress, spenderAddress);

  if (currentAllowance.gte(amount)) {
    console.log('Allowance already sufficient');
    return;
  }

  // Approve maximum amount (or specific)
  const tx = await token.approve(spenderAddress, ethers.constants.MaxUint256);
  await tx.wait();

  console.log('Token approved');
}
```

### Permit (EIP-2612) - Approve via Signature

If token supports EIP-2612 (`permit: true` field in tokenlist), you can use a signature instead of approve transaction.

```javascript
async function signPermit(
  tokenAddress: string,
  spenderAddress: string,
  amount: string,
  deadline: number,
  signer: ethers.Signer
) {
  const token = new ethers.Contract(tokenAddress, [
    'function name() view returns (string)',
    'function nonces(address owner) view returns (uint256)',
    'function DOMAIN_SEPARATOR() view returns (bytes32)'
  ], signer);

  const owner = await signer.getAddress();
  const nonce = await token.nonces(owner);
  const name = await token.name();
  const chainId = await signer.getChainId();

  const domain = {
    name: name,
    version: '1',
    chainId: chainId,
    verifyingContract: tokenAddress
  };

  const types = {
    Permit: [
      { name: 'owner', type: 'address' },
      { name: 'spender', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
      { name: 'deadline', type: 'uint256' }
    ]
  };

  const value = {
    owner: owner,
    spender: spenderAddress,
    value: amount,
    nonce: nonce.toNumber(),
    deadline: deadline
  };

  const signature = await signer._signTypedData(domain, types, value);
  const { v, r, s } = ethers.utils.splitSignature(signature);

  return { v, r, s };
}

// Using permit in /tx/create
// deadline can be set manually (e.g., 30 minutes from now)
const deadline = Math.floor(Date.now() / 1000) + 1800;
const permit = await signPermit(tokenIn, txData.to, amountIn, deadline, signer);

const txResponse = await fetch(`${API_BASE}/tx/create`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    from: walletAddress,
    recipient: walletAddress,
    routing: route,  // routing already includes signature
    permit: permit   // <-- Pass permit signature
  })
});
```

***

## Transaction Tracking

### GET /transaction/{requestId}

Get cross-chain transaction status.

**Parameters:**

| Parameter   | Type   | Description         |
| ----------- | ------ | ------------------- |
| `requestId` | string | Unique operation ID |

**Response:**

```json
{
  "status": "completed",
  "inconsistency": false,
  "source": { "status": "completed", "chainId": 1, "transactionHash": "0x..." },
  "oracle": { "status": "completed", "requestId": "0x..." },
  "destination": { "status": "completed", "chainId": 42161, "emergency": false }
}
```

**Possible Statuses:**

| Status        | Description                            |
| ------------- | -------------------------------------- |
| `in progress` | Operation in progress                  |
| `completed`   | Successfully completed                 |
| `failed`      | Execution error                        |
| `reverted`    | Reverted (requires inconsistency)      |
| `retry`       | Ready for retry via alternative bridge |
| `canceled`    | Canceled                               |

**Status Handling Algorithm:**

```
Check status:
├── "completed" → Done ✅
├── "in progress" → Continue polling
├── "retry" → POST /tx/create/retry (retry via alternative bridge)
└── "failed" / "reverted" → Check flags:
    ├── destination.emergency === true → POST /tx/create/emergency
    └── inconsistency === true → POST /inconsistency
```

**Extended Response with bridgeState:**

```json
{
  "status": "in progress",
  "inconsistency": false,
  "source": {
    "chainId": 1,
    "transactionHash": "0x...",
    "from": "0x...",
    "events": [...],
    "status": "completed"
  },
  "oracle": {
    "relayChainId": 1,
    "requestId": "0x...",
    "status": "in progress",
    "height": null,
    "epoch": null,
    "time": null
  },
  "destination": {
    "chainId": 42161,
    "transactionHash": null,
    "events": [],
    "emergency": false,
    "status": "in progress",
    "bridgeState": {
      "AXELAR": { "txHash": null },
      "LAYER_ZERO": { "txHash": null }
    }
  }
}
```

***

### GET /search

Search transactions by hash or requestId.

**Parameters:**

| Parameter | Type   | Required | Description                   |
| --------- | ------ | -------- | ----------------------------- |
| `search`  | string | Yes      | Transaction hash or requestId |
| `limit`   | number | No       | Results limit                 |
| `offset`  | number | No       | Offset                        |

**Example:**

```bash
curl "https://api.crosscurve.fi/search?search=0xTransactionHash"
```

***

### GET /history

User transaction history.

**Parameters:**

| Parameter | Type   | Required | Description    |
| --------- | ------ | -------- | -------------- |
| `address` | string | Yes      | Wallet address |

**Example:**

```bash
curl "https://api.crosscurve.fi/history?address=0xYourAddress"
```

***

## Error Handling

### Scenarios

| Flag / Status                 | Action                                                   |
| ----------------------------- | -------------------------------------------------------- |
| `status: "completed"`         | Operation completed ✅                                    |
| `destination.status: "retry"` | Call `/tx/create/retry` for retry via alternative bridge |
| `destination.emergency: true` | Call `/tx/create/emergency` for recovery                 |
| `inconsistency: true`         | Call `/inconsistency` for refund                         |

### GET /inconsistency/{requestId}

Get parameters for refund on inconsistency.

**Example:**

```bash
curl "https://api.crosscurve.fi/inconsistency/0xRequestId"
```

***

### POST /inconsistency

Create refund transaction.

> **Flow:**
>
> 1. Call `GET /inconsistency/{requestId}` — get parameters (tokenIn, tokenOut, chainIdIn, chainIdOut, amountIn)
> 2. Sign refund data with user wallet (EIP-712 or personal\_sign)
> 3. Pass signature and original route to this endpoint

**Request Body:**

```json
{
  "requestId": "0x...",
  "signature": "0x...",
  "routing": { /* original route from /routing/scan */ },
  "permit": {
    "v": 28,
    "r": "0x...",
    "s": "0x...",
    "deadline": 1703001600
  }
}
```

**Parameters:**

| Field       | Type   | Required | Description                         |
| ----------- | ------ | -------- | ----------------------------------- |
| `requestId` | string | Yes      | Operation ID with inconsistency     |
| `signature` | string | Yes      | User signature (65 bytes hex)       |
| `routing`   | object | Yes      | Original route from `/routing/scan` |
| `permit`    | object | No       | EIP-2612 permit (if token supports) |

***

### POST /tx/create/emergency

Emergency recovery of locked funds.

> **When to use:** When `destination.emergency: true` in transaction status — funds are locked on destination chain and require manual recovery.

**Request Body:**

```json
{
  "requestId": "0x...",
  "signature": "0x..."
}
```

**Parameters:**

| Field       | Type   | Required | Description                   |
| ----------- | ------ | -------- | ----------------------------- |
| `requestId` | string | Yes      | Locked operation ID           |
| `signature` | string | Yes      | User signature (65 bytes hex) |

**Response:** Returns transaction data for fund recovery.

***

### POST /tx/create/retry

Retry delivery via alternative bridge.

> **When to use:** When `destination.status: "retry"` — delivery via one bridge failed, but alternative bridges are available.

**Request Body:**

```json
{
  "requestId": "0x...",
  "signature": "0x..."
}
```

**Parameters:**

| Field       | Type   | Required | Description                   |
| ----------- | ------ | -------- | ----------------------------- |
| `requestId` | string | Yes      | Operation ID for retry        |
| `signature` | string | Yes      | User signature (65 bytes hex) |

**Response:**

```json
{
  "to": "0x...",
  "abi": "function retry(tuple(...) params, uint256 nonce, address protocol, address bridge, bytes currentOptions, bool isHash)",
  "args": [
    {
      "requestId": "0x...",
      "data": "0x...",
      "chainIdTo": 42161,
      "to": "0x..."
    },
    "12345",
    "0x...",
    "0x...",
    "0x...",
    false
  ],
  "value": "1500000000000000"
}
```

**Usage Example:**

```javascript
async function executeRetry(requestId, signer) {
  // Create signature (same as for emergency)
  const messageHash = ethers.utils.solidityKeccak256(['string'], [requestId]);
  const messageHashBinary = ethers.utils.arrayify(messageHash);
  const signature = await signer.signMessage(messageHashBinary);

  const response = await fetch(`${API_BASE}/tx/create/retry`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ requestId, signature })
  });

  const tx = await response.json();

  // Send transaction
  const iface = new ethers.utils.Interface([tx.abi]);
  const data = iface.encodeFunctionData('retry', tx.args);

  const txResponse = await signer.sendTransaction({
    to: tx.to,
    data,
    value: tx.value
  });

  console.log('Retry transaction sent:', txResponse.hash);
  return txResponse.wait();
}
```

***

### Creating Signature for Emergency/Inconsistency/Retry

For `/tx/create/emergency` and `/inconsistency` endpoints, a signature confirming wallet ownership is required.

```javascript
import { ethers } from 'ethers';

async function createRecoverySignature(requestId, signer) {
  // 1. Hash requestId as string
  const messageHash = ethers.utils.solidityKeccak256(['string'], [requestId]);

  // 2. Convert to bytes
  const messageHashBinary = ethers.utils.arrayify(messageHash);

  // 3. Sign (adds Ethereum prefix)
  const signature = await signer.signMessage(messageHashBinary);

  return signature; // 132 characters (0x + 65 bytes)
}

// Usage
const signature = await createRecoverySignature(requestId, signer);

// For emergency:
await fetch(`${API_BASE}/tx/create/emergency`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ requestId, signature })
});

// For inconsistency:
await fetch(`${API_BASE}/inconsistency`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ requestId, signature, routing: selectedRoute })
});
```

***

### Common API Errors

| Error                                      | Cause                        | Solution                       |
| ------------------------------------------ | ---------------------------- | ------------------------------ |
| `Can't find receiveRequest`                | requestId not found          | Verify requestId               |
| `Can't find paymentTx`                     | Transaction not found        | Verify requestId               |
| `Routing signature not valid`              | Route expired or changed     | Repeat `/routing/scan`         |
| `Already completed`                        | Transaction completed        | No action required             |
| `Not an emergency`                         | Emergency conditions not met | Wait or check status           |
| `Uncorrect user address`                   | Signature not from owner     | Sign with wallet from `from`   |
| `invalid signature string`                 | Invalid signature format     | Verify 65-byte hex signature   |
| `Retry is not supported for single bridge` | Only one bridge available    | Use emergency instead of retry |
| `No remaining bridges available`           | All bridges already used     | Use emergency                  |

***

## API Reference

### Endpoint List

#### Reference Data

| Method | Endpoint                    | Description       |
| ------ | --------------------------- | ----------------- |
| GET    | `/networks`                 | List of networks  |
| GET    | `/tokenlist`                | List of tokens    |
| GET    | `/validators`               | Validator status  |
| GET    | `/ready`                    | Service readiness |
| GET    | `/prices/{token}`           | Token price       |
| GET    | `/prices/{token}/{chainId}` | Price in network  |
| POST   | `/prices`                   | Batch prices      |

#### Routing and Swap

| Method | Endpoint        | Description                         |
| ------ | --------------- | ----------------------------------- |
| POST   | `/routing/scan` | Find routes (includes signature)    |
| POST   | `/estimate`     | **DEPRECATED** — Operation estimate |
| POST   | `/tx/create`    | Create transaction                  |

#### Monitoring

| Method | Endpoint                   | Description         |
| ------ | -------------------------- | ------------------- |
| GET    | `/transaction/{requestId}` | Transaction status  |
| GET    | `/search`                  | Search transactions |
| GET    | `/history`                 | User history        |
| GET    | `/transactions`            | Transaction list    |

#### Error Handling

| Method | Endpoint                     | Description                  |
| ------ | ---------------------------- | ---------------------------- |
| GET    | `/inconsistency/{requestId}` | Refund parameters            |
| POST   | `/inconsistency`             | Refund transaction           |
| POST   | `/tx/create/emergency`       | Emergency recovery           |
| POST   | `/tx/create/retry`           | Retry via alternative bridge |

#### Supply and Statistics

> Values are dynamic, examples current at time of testing.

| Method | Endpoint              | Description            | Response Format    |
| ------ | --------------------- | ---------------------- | ------------------ |
| GET    | `/supply/eywa/ts`     | Total Supply EYWA      | number             |
| GET    | `/supply/eywa/ms`     | Max Supply EYWA        | number             |
| GET    | `/supply/eywa/cmc`    | Data for CoinMarketCap | number             |
| GET    | `/supply/eywa/cg`     | Data for CoinGecko     | `{"result":"..."}` |
| GET    | `/points/multipliers` | Points multipliers     | object             |

#### NFT Operations

| Method | Endpoint         | Description             | Required Parameters                     |
| ------ | ---------------- | ----------------------- | --------------------------------------- |
| GET    | `/nft/rarity`    | NFT rarity              | `tokens` (query, string) ⚠️             |
| POST   | `/nft/estimate`  | NFT operation estimate  | `wallet`, `tokenIds` (array of numbers) |
| POST   | `/nft/tx`        | Create NFT transaction  | `wallet`, `tokenIds`, `estimate`        |
| POST   | `/nft/emergency` | NFT emergency operation | `requestId`, `signature`                |

> ⚠️ **Known bug:** `GET /nft/rarity` does not parse `tokens` query parameter. Endpoint temporarily unavailable.

**NFT Operations Flow:**

```
1. POST /nft/estimate { wallet, tokenIds: [1,2,3] } → estimate
2. POST /nft/tx { wallet, tokenIds, estimate } → tx data
```

***

## Code Examples

> Main flow in [Integration Overview](#integration-overview) section.

### TypeScript: Types and Polling

```typescript
interface SwapParams {
  chainIdIn: number;
  chainIdOut: number;
  tokenIn: string;
  tokenOut: string;
  amountIn: string;
  slippage: number;
  from: string;
  recipient: string;
}

interface TransactionStatus {
  status: 'in progress' | 'completed' | 'failed' | 'reverted' | 'retry' | 'canceled';
  inconsistency: boolean;
  source: { status: string; chainId: number; transactionHash: string };
  destination?: {
    status: string;
    emergency: boolean;
    bridgeState?: Record<string, { txHash: string | null }>;
  };
}

// Polling with timeout
async function trackTransaction(requestId: string, maxAttempts = 60): Promise<TransactionStatus> {
  const API_BASE = 'https://api.crosscurve.fi';

  for (let i = 0; i < maxAttempts; i++) {
    const res = await fetch(`${API_BASE}/transaction/${requestId}`);
    const data: TransactionStatus = await res.json();

    if (data.status === 'completed') return data;
    if (data.inconsistency) return data; // requires /inconsistency
    if (data.destination?.emergency) return data; // requires /tx/create/emergency

    await new Promise(r => setTimeout(r, 5000));
  }
  throw new Error('Timeout: transaction not completed');
}
```

### JavaScript: Getting Networks and Tokens

```javascript
async function getNetworksAndTokens() {
  const networks = await fetch('https://api.crosscurve.fi/networks?type=0').then(r => r.json());
  const tokensByNetwork = await fetch('https://api.crosscurve.fi/tokenlist').then(r => r.json());
  return { networks, tokensByNetwork };
}

function getSwappableTokens(tokensByNetwork, networkName) {
  const networkData = tokensByNetwork[networkName];
  if (!networkData) return [];

  return networkData.tokens.filter(token =>
    token.tags.includes('can_swap')
  );
}
```

### Python: Finding Route

```python
import requests

API_BASE = 'https://api.crosscurve.fi'

def find_route(chain_in: int, chain_out: int,
               token_in: str, token_out: str,
               amount: str, slippage: float = 0.5):

    response = requests.post(f'{API_BASE}/routing/scan', json={
        'params': {
            'chainIdIn': chain_in,
            'chainIdOut': chain_out,
            'tokenIn': token_in,
            'tokenOut': token_out,
            'amountIn': amount
        },
        'slippage': slippage
    })

    routes = response.json()

    if not routes:
        raise Exception('No routes available')

    selected = routes[0]  # first route from list
    print(f"Amount In: {selected['amountIn']}")
    print(f"Amount Out: {selected['amountOut']}")
    print(f"Price Impact: {selected['priceImpact']}%")
    print(f"Total Fee: {selected['totalFee']['percent']}%")

    return selected

# Example usage
route = find_route(
    chain_in=42161,      # Arbitrum
    chain_out=1,         # Ethereum
    token_in='0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978',   # CRV on Arbitrum
    token_out='0xD533a949740bb3306d119CC777fa900bA034cd52',  # CRV on Ethereum
    amount='1000000000000000000000'  # 1000 CRV (18 decimals)
)
```

***

## Route Operation Codes

| Code | Description                  |
| ---- | ---------------------------- |
| `A`  | Add liquidity                |
| `S`  | Swap                         |
| `R`  | Remove liquidity             |
| `LM` | Lock/Mint                    |
| `BU` | Burn/Unlock                  |
| `BM` | Burn/Mint                    |
| `Uw` | Unwrap (unwrap native token) |
| `W`  | Wrap (wrap native token)     |
| `M`  | Emergency Mint               |
| `U`  | Emergency Unlock             |

***

## Rate Limits and Best Practices

### API Keys

Two types of API keys are available:

| Key Type | Requests/Minute | Commission               | `feeShareBps` |
| -------- | --------------- | ------------------------ | ------------- |
| Free     | 20              | 0%                       | Not available |
| Standard | 60              | From `feeShareBps` param | 1–10000 bps   |

Pass the key in the `api-key` header:

```javascript
headers: {
  'Content-Type': 'application/json',
  'api-key': 'your-api-key-here'
}
```

> API is accessible without a key, but a key is required for higher rate limits and commission earning.

**Test keys for development:**

* Free: `test-sdk-test-sdk-test-sdk-free`
* Standard: `test-sdk-test-sdk-test-sdk-standard`

To get a production API key, contact BD: [@Eywa\_BDLead](https://t.me/Eywa_BDLead) / <a.gluhovskij@eywa.fi>

### Rate Limits

| Endpoint                  | Recommendation        |
| ------------------------- | --------------------- |
| `/networks`, `/tokenlist` | Cache locally         |
| `/routing/scan`           | On user request       |
| `/tx/create`              | After route selection |
| `/transaction/{id}`       | Polling with interval |
| `/prices`                 | Cache when needed     |

### Best Practices

1. **Cache `/networks` and `/tokenlist`** — data changes rarely

   ```javascript
   let cache = { networks: null, tokens: null, time: 0 };
   const TTL = 3600000; // 1 hour

   async function getNetworks() {
     if (cache.networks && Date.now() - cache.time < TTL) return cache.networks;
     cache.networks = await fetch(`${API_BASE}/networks?type=0`).then(r => r.json());
     cache.time = Date.now();
     return cache.networks;
   }
   ```
2. **Checksum addresses** — EIP-55 format recommended for compatibility

   ```javascript
   const address = ethers.utils.getAddress(userInput); // converts to checksum
   ```
3. **Check `can_swap`** before calling `/routing/scan`

   ```javascript
   if (!token.tags.includes('can_swap')) {
     throw new Error('Token not available for cross-chain swap');
   }
   ```
4. **Check route freshness** — routes have limited validity period

   ```javascript
   // If significant time has passed since getting the route,
   // it's recommended to request a new one via /routing/scan
   ```
5. **Polling** — 5-10 sec interval, handle `inconsistency` and `emergency`
6. **Save requestId** — store after sending transaction for tracking and recovery
7. **Slippage** — use appropriate values (0.5-1% for stables, 1-3% for volatile)
8. **Use correct wallet** for recovery signatures — must match `from` of original transaction
9. **Account for token decimals** — not all tokens have 18 decimals

   ```javascript
   // ❌ Wrong: hardcoded 18 decimals
   const amount = parseFloat(value) / 1e18;

   // ✅ Correct: use decimals from tokenlist
   function formatAmount(rawAmount, decimals) {
     return parseFloat(rawAmount) / Math.pow(10, decimals);
   }

   // Decimals examples:
   // USDC, USDT: 6
   // WBTC: 8
   // CRV, ETH, most tokens: 18
   ```

***

## Limitations

| Parameter      | Description                                       |
| -------------- | ------------------------------------------------- |
| Minimum amount | Depends on token pair and liquidity               |
| Maximum amount | Limited by pool liquidity                         |
| Slippage       | Specified in percent when calling `/routing/scan` |
| Execution time | Depends on networks and oracle network load       |

***

## Contract Addresses

Contract addresses available via `/networks`:

| Contract  | Description                            | Field in /networks |
| --------- | -------------------------------------- | ------------------ |
| Portal    | Entry point for cross-chain operations | `portal`           |
| Synthesis | Synthetic token contract               | `synthesis`        |
| Router    | Swap router                            | `router`           |

```javascript
// Getting contract addresses
const networks = await fetch('https://api.crosscurve.fi/networks?type=0').then(r => r.json());
const ethPortal = networks.ethereum.portal;
const ethRouter = networks.ethereum.router;
const ethSynthesis = networks.ethereum.synthesis;
```

***

## Fee Structure

Fee information is returned in `/routing/scan` response:

| Fee Type      | Description     | Response Field              |
| ------------- | --------------- | --------------------------- |
| `dex`         | DEX/pool fee    | In each route step          |
| `bridge`      | Bridge fee      | In bridgeIn/bridgeOut steps |
| `aggregation` | Aggregation fee | Total CrossCurve fee        |
| `total`       | Total fee       | `totalFee` in route         |

```javascript
const route = routes[0];
console.log(`Total fee: ${route.totalFee.percent}%`);
console.log(`Fee in USD: $${route.totalFee.amount}`);

// Details by step
route.route.forEach(step => {
  step.fees?.forEach(fee => {
    console.log(`${fee.type}: ${fee.percent}%`);
  });
});
```

***

## FAQ

### General Questions

**Q: Is an API key required?** A: The API is accessible without a key, but with limited rate (20 req/min). Two key types are available: Free (20 req/min, no commission) and Standard (60 req/min, commission via `feeShareBps`). See [API Keys](#api-keys).

**Q: Are there rate limits?** A: Free keys — 20 requests/minute, Standard keys — 60 requests/minute. Cache reference data and avoid frequent requests.

**Q: Which networks are supported?** A: Current list available via `GET /networks`. EVM-compatible networks are supported.

**Q: Why does `/routing/scan` return an empty array?** A: No route found. See [If No Route Found](#if-no-route-found).

### Technical Questions

**Q: How to get requestId after sending transaction?** A: RequestId is emitted in Portal contract events. Use `GET /search?search={txHash}` to find it.

**Q: What to do with `inconsistency: true`?** A: Refund required:

1. Call `GET /inconsistency/{requestId}` — get parameters for signature
2. Sign data with user wallet
3. Call `POST /inconsistency` with `requestId`, `signature` and original `routing`

**Q: What to do with `emergency: true`?** A: Fund recovery required. Call `POST /tx/create/emergency` with `requestId` and `signature` (user signature).

**Q: How does permit (EIP-2612) work?** A: If token supports permit (`permit: true` in tokenlist), you can sign permission offchain instead of separate approve transaction. Pass signature `{v, r, s}` to `/tx/create`.

***

## Glossary

| Term              | Description                                             |
| ----------------- | ------------------------------------------------------- |
| **Portal**        | Entry point contract for cross-chain operations         |
| **Synthesis**     | Contract for minting/burning synthetic tokens           |
| **Router**        | Swap routing contract                                   |
| **RequestId**     | Unique identifier for cross-chain operation             |
| **Inconsistency** | State requiring fund refund                             |
| **Emergency**     | State requiring fund recovery                           |
| **Retry**         | Retry delivery via alternative bridge                   |
| **can\_swap**     | Token tag indicating cross-chain swap capability        |
| **Synth**         | Synthetic token representing asset from another network |
| **bridgeIn**      | Token lock operation on source chain                    |
| **bridgeOut**     | Token unlock operation on destination chain             |
| **Oracle**        | CrossCurve validator network                            |
| **Slippage**      | Allowed deviation from expected price                   |
| **sourceFee**     | Fee on source chain                                     |
| **deliveryFee**   | Fee for delivery to target chain                        |
| **feeShare**      | Partner fee share (when using api-key)                  |
| **bridgeState**   | Delivery state for each bridge (AXELAR, LAYER\_ZERO)    |

***

## Changelog

> API change history. Follow updates in Swagger UI.

| Date    | Change                                                                                                                                                                                              |
| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2025-01 | **BREAKING:** /estimate endpoint deprecated — signature now included in /routing/scan. Updated flow: routing/scan → tx/create → send TX                                                             |
| 2025-01 | Added: CDP API URL, api-key for partners, new routing params (feeFromAmount, feeToken, providers), sourceFee/deliveryFee/feeShare in response, /tx/create/retry endpoint, retry status, bridgeState |
| 2025-12 | Documentation created: Quick Start, API Reference, code examples                                                                                                                                    |
| —       | For current API changes, see Swagger UI                                                                                                                                                             |

***

## Support

* **Swagger UI**: <https://api.crosscurve.fi/api-docs/>
* **Documentation**: <https://docs.crosscurve.fi>
* **Twitter/X**: [@crosscurvefi](https://x.com/crosscurvefi)

### Partnership and API Keys

To receive a partner API key (fee sharing), contact BD:

* **Email**: <a.gluhovskij@eywa.fi>
* **Telegram**: [@Eywa\_BDLead](https://t.me/Eywa_BDLead)


---

# Agent Instructions: 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:

```
GET https://docs.crosscurve.fi/developer-documentation/guide-for-developers/integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
