# 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)
