# Hydrex Router API — Agent Reference

This document is a concise reference for LLM agents and AI coding assistants integrating with the Hydrex Router API.

---

## Base URL

```
https://router.api.hydrex.fi
```

- **Swagger UI:** https://router.api.hydrex.fi/api
- **OpenAPI spec (JSON):** https://router.api.hydrex.fi/api-json
- **Primary chain:** Base mainnet (`chainId: 8453`)
- **Auth:** No API key required for public endpoints

---

## Core Concepts

- **Amounts** — always pass amounts as strings in base units (wei). Never floats.
  - 1 USDC (6 decimals) = `"1000000"`
  - 1 WETH (18 decimals) = `"1000000000000000000"`
- **Slippage** — expressed in basis points (BIPS). `50` = 0.5%, `100` = 1%.
- **Router contract address** — always read `transaction.to` from the quote response. Never hardcode it.
- **Aggregator sources:** `ZEROX`, `OPENOCEAN`, `OKX`, `KYBERSWAP`

---

## Endpoints

### GET /quote ★ (primary)

Returns the best route across all aggregators plus Hydrex router calldata ready for on-chain submission.

**Required parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `fromTokenAddress` | address | Input token |
| `toTokenAddress` | address | Output token |
| `amount` | string (wei) | Input amount |
| `chainId` | number | `8453` for Base |
| `taker` | address | Wallet executing the swap |

**Optional parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `slippage` | number | Basis points. Default: `50` |
| `source` | string | Comma-separated source filter (`ZEROX`, `OPENOCEAN`, `OKX`, `KYBERSWAP`). Omit for all. |
| `referral` | address | Referral wallet for on-chain fee |
| `referralFeeBps` | number | Referral fee in basis points |
| `spread` | number | Spread fee in basis points (aggregator-level) |
| `spreadDirection` | string | `"input"` or `"output"` |
| `admin` | address | Wallet receiving spread fee |
| `origin` | string | App identifier for analytics |
| `gasPrice` | string | Gas price hint in wei for upstream providers that support it |

**Example response:**
```json
{
  "amountIn": "100000000",
  "amountOut": "34820000000000000",
  "minOutputAmount": "34645900000000000",
  "recipient": "0xYourWallet",
  "transaction": {
    "to": "0x599bfa1039c9e22603f15642b711d56be62071f4",
    "data": "0x...",
    "value": "0"
  }
}
```

**Key fields:**
- `transaction.to` — the Hydrex router contract address (use this, never hardcode)
- `transaction.data` — calldata to send as-is
- `transaction.value` — must be passed as `value` for native ETH swaps; always parse as BigInt
- `minOutputAmount` — on-chain floor; transaction reverts if output is below this
- `amountOut` — expected output amount in base units
- `recipient` — the taker address echoed back

---

### GET /aggregator/quote

Raw quote from a single upstream aggregator, without Hydrex router calldata. Useful for price display.

**Required parameters:** `fromTokenAddress`, `toTokenAddress`, `amount` (same as `/quote`).

**Key differences from `/quote`:**
- `chainId` is **optional** (defaults to Base 8453)
- `taker` is **optional** (but required when using OKX as source)
- `referral`, `referralFeeBps`, `origin`, and `gasPrice` are **not supported**
- Response does **not** include `transaction` calldata

---

### GET /aggregator/quote/all

Fetches quotes from all enabled aggregators in parallel. Returns an array with one entry per source.

---

### POST /quote/multi

Batches multiple independent swap legs into a single `executeSwaps` transaction. The legs do **not** chain (output of one is not input of the next).

**Required body fields:**
| Field | Type | Description |
|-------|------|-------------|
| `taker` | address | Final wallet receiving output tokens |
| `swaps` | array | Array of swap legs (see below) |

**Optional body fields:**
| Field | Type | Description |
|-------|------|-------------|
| `chainId` | string | EVM chain ID. Default: `"8453"` |
| `slippage` | string | Slippage in BIPS. Default: `"50"` |
| `source` | string | Comma-separated source filter. Omit for all. |
| `referral` | address | Referral address for Hydrex router attribution |
| `referralFeeBps` | string | Referral fee in BIPS. Default: `"0"` |
| `spread` | string | Aggregator fee in BIPS passed upstream |
| `spreadDirection` | string | `"input"` or `"output"` |
| `admin` | address | Wallet that receives the spread fee |
| `origin` | string | Origin label for tracking |

**Each swap item:**
| Field | Type | Description |
|-------|------|-------------|
| `fromTokenAddress` | address | Input token contract address |
| `toTokenAddress` | address | Output token contract address |
| `amount` | string (wei) | Input amount in base units |

**Example request body:**
```json
{
  "taker": "0xYourWalletAddress",
  "chainId": "8453",
  "slippage": "50",
  "swaps": [
    {
      "fromTokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "toTokenAddress": "0x4200000000000000000000000000000000000006",
      "amount": "100000000"
    }
  ]
}
```

---

### GET /dca/orders

Returns all DCA orders from the Hydrex DCA smart contract.

---

### GET /dca/orders/user/{address}

Returns all DCA orders for a specific wallet address.

---

## Executing a Swap (viem)

```javascript
import { createPublicClient, createWalletClient, http, maxUint256 } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";

const BASE_URL = "https://router.api.hydrex.fi";
const FROM_TOKEN = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const publicClient = createPublicClient({ chain: base, transport: http() });
const walletClient = createWalletClient({ account, chain: base, transport: http() });

// 1. Get quote
const params = new URLSearchParams({
  fromTokenAddress: FROM_TOKEN,
  toTokenAddress:   "0x4200000000000000000000000000000000000006", // WETH
  amount:           "100000000", // 100 USDC
  chainId:          "8453",
  taker:            account.address,
  slippage:         "50",
});
const quote = await fetch(`${BASE_URL}/quote?${params}`).then(r => r.json());

// 2. Approve if needed (ERC-20 only)
const ERC20_ABI = [
  { name: "allowance", type: "function", stateMutability: "view",
    inputs: [{ name: "owner", type: "address" }, { name: "spender", type: "address" }],
    outputs: [{ type: "uint256" }] },
  { name: "approve", type: "function", stateMutability: "nonpayable",
    inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }],
    outputs: [{ type: "bool" }] },
];
const allowance = await publicClient.readContract({
  address: FROM_TOKEN, abi: ERC20_ABI,
  functionName: "allowance", args: [account.address, quote.transaction.to],
});
if (allowance < BigInt(quote.amountIn)) {
  const { request } = await publicClient.simulateContract({
    address: FROM_TOKEN, abi: ERC20_ABI,
    functionName: "approve", args: [quote.transaction.to, maxUint256], account,
  });
  await walletClient.writeContract(request);
}

// 3. Send transaction
const hash = await walletClient.sendTransaction({
  to:    quote.transaction.to,
  data:  quote.transaction.data,
  value: BigInt(quote.transaction.value ?? "0"),
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
```

---

## Fee Integration

### Referral fee (on-chain)

Deducted from the output token by the router contract. Sent to `referral` wallet.

```
referral       = "0xYourPartnerWallet"
referralFeeBps = 10   // 0.10%
```

`amountOut` and `minOutputAmount` in the response already reflect the fee deduction.

### Spread fee (aggregator-level)

Charged at the upstream aggregator before the quote is returned.

```
spread          = 15        // 0.15%
spreadDirection = "input"   // or "output"
admin           = "0xYourPartnerWallet"
```

Both can be combined in a single quote request.

---

## Error Handling

| HTTP | Code | Meaning |
|------|------|---------|
| 400 | `MISSING_PARAMS` | Required query parameter absent |
| 400 | `INVALID_ADDRESS` | Malformed token or wallet address |
| 400 | `AMOUNT_TOO_SMALL` | Amount below minimum for any route |
| 401 | — | Invalid `X-Domain-Bypass-Key` (non-whitelisted domains only) |
| 404 | `NO_ROUTE_FOUND` | No liquidity path available |
| 422 | `SLIPPAGE_TOO_HIGH` | Requested slippage exceeds allowed maximum |
| 429 | — | Rate limit exceeded — respect `Retry-After` header |
| 500 | `AGGREGATOR_ERROR` | Upstream aggregator returned an error |
| 503 | — | Upstream aggregator unavailable — use exponential backoff |

Error responses follow the shape:
```json
{ "statusCode": 400, "message": "MISSING_PARAMS", "details": "..." }
```

---

## Common Token Addresses (Base)

| Token | Address |
|-------|---------|
| WETH | `0x4200000000000000000000000000000000000006` |
| USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| ETH (native) | `0x0000000000000000000000000000000000000000` |

---

## Key Contracts (Base Mainnet)

| Contract | Address | Basescan |
|----------|---------|----------|
| Hydrex Router | `0x599bfa1039c9e22603f15642b711d56be62071f4` | [View](https://basescan.org/address/0x599bfa1039c9e22603f15642b711d56be62071f4) |
| HydrexDCA (Proxy) | `0x612697A124aB01b3AcF44c6e0135539416BF3fAB` | [View](https://basescan.org/address/0x612697A124aB01b3AcF44c6e0135539416BF3fAB) |
| HydrexDCA (Implementation) | `0x4dD152531eD13C7BB89Cd30B25246662e8DD87E4` | [View](https://basescan.org/address/0x4dD152531eD13C7BB89Cd30B25246662e8DD87E4) |

> **Note:** The Router contract address may change with protocol upgrades. Always read `transaction.to` from the API response rather than hardcoding it.

---

## DCA Smart Contract

**Contract name:** `HydrexDCA` (upgradeable proxy)
**Proxy address (Base mainnet):** `0x612697A124aB01b3AcF44c6e0135539416BF3fAB`
**Implementation:** `0x4dD152531eD13C7BB89Cd30B25246662e8DD87E4` (verified on Basescan)

The contract is **custodial** — it holds the full `totalAmount` upfront and executes each swap at the configured interval. Swaps are triggered by Hydrex operators. A 0.5% protocol fee is deducted from each output amount.

### OrderCreateParams struct

```solidity
struct OrderCreateParams {
    address tokenIn;          // use 0xEeee...EeEE for native ETH
    address tokenOut;
    uint256 totalAmount;      // transferred from caller at creation
    uint256 numberOfSwaps;    // min 2, max 100
    uint256 interval;         // seconds between swaps; min 60, max 365 days
    uint256 minUsdPrice;      // 0 = no floor
    uint256 maxUsdPrice;      // 0 = no ceiling
    uint256 slippageTolerance; // basis points, max 10000
}
```

### Key functions

```
createOrder(OrderCreateParams) payable → orderId   // user function
cancelOrder(uint256 orderId)                        // user function; refunds remainingAmount
getOrder(uint256 orderId) view → Order
getUserOrders(address user) view → uint256[]
getUserOrdersPaginated(address, offset, limit) view → (Order[], total)
```

### Key events

```
OrderCreated(orderId, user, tokenIn, tokenOut, totalAmount, numberOfSwaps, amountPerSwap, interval)
DCASwapExecuted(orderId, user, tokenIn, tokenOut, amountIn, amountOut, feeAmount)
DCASwapFailed(orderId, user, reason)
OrderCancelled(orderId, user, refundAmount)
OrderCompleted(orderId, user)
```
