---
title: Self-hosting and configuration
description: Run your own Hexkit instance — setup, environment variables, proxy routes, and contracts.
url: https://pr-1-9bee85ef9c41.thally.app/self-hosting
---

# Self-hosting and configuration

Run your own Hexkit instance — setup, environment variables, proxy routes, and contracts.

Most people should just use [web3-toolkit.vercel.app](https://web3-toolkit.vercel.app).
This page is for running your own instance or contributing to the codebase.

## Run it locally

#### Install

    ```bash
    git clone https://github.com/Timidan/hexkit.git
    cd hexkit
    npm install
    ```

#### Configure

    ```bash
    cp .env.example .env
    ```

    Set `LIFI_API_KEY` and `VITE_WALLETCONNECT_PROJECT_ID`. Everything else has a
    working default.

#### Start

    ```bash
    npm run dev
    ```

    Vite serves on `http://localhost:5173` and proxies the API routes below, so no
    separate backend is needed.

#### Verify

    ```bash
    npm run build   # tsc -b && vite build
    npm run lint
    ```

    Run both before opening a pull request.

## Environment variables

Hexkit is a browser app, so anything secret lives behind a server route. Provider
keys are held by the serverless functions in `api/` and injected per request; the
browser bundle never sees them. Anything prefixed `VITE_` **is** compiled into the
bundle and is therefore public.

| Variable | Scope | Purpose |
|---|---|---|
| `LIFI_API_KEY` | Server | Injected by the `/api/lifi-earn` and `/api/lifi-composer` proxies |
| `ALLOWED_ORIGINS` | Server | Comma-separated origin allowlist for the proxies |
| `PROXY_SECRET` | Server | Shared secret gate. See the warning below |
| `GEMINI_API_KEY` | Server | Key for the AI concierge |
| `VITE_WALLETCONNECT_PROJECT_ID` | Browser | WalletConnect project id |
| `VITE_LLM_MODE` | Browser | Set to `off` to skip the LLM and use the rules-based fallback |

> **Warning:**
  Do not set `PROXY_SECRET` on a browser-facing deployment. When it is set the
  proxies require an `x-proxy-secret` header, and the web client does not send one —
  `proxyHeaders()` in `earnApi.ts` deliberately returns an empty object. Setting it
  makes every proxied call return `403`. It is only usable for server-to-server
  callers that set the header themselves.

## Proxy routes

Each provider gets one route. `vite.config.ts` proxies these in development;
`vercel.json` rewrites them to the matching serverless function in production.

| Route | Upstream | Notes |
|---|---|---|
| `/api/lifi-earn` | LI.FI Earn API | `LIFI_API_KEY` injected server-side |
| `/api/lifi-composer` | LI.FI Composer API | `LIFI_API_KEY` injected server-side |
| `/api/lifi-intents` | `order.li.fi` | No key required; the endpoints are open |
| `/api/edb` | EDB bridge | Debugger only |
| `/api/repo` | Sourcify | Verified source lookups |

### The intents proxy

`/api/lifi-intents` exists for CORS parity and a server-side allowlist, not to add a
credential. It forwards only these subpaths:

```text
quote/request
orders/submit
orders/status
orders
routes
chains/supported
```

Anything else returns `404 unsupported_intents_path`. The handler caps request bodies
at 16 KiB, applies a best-effort per-IP rate limit of 120 requests per minute, and
times out upstream calls after 25 seconds.

> **Note:**
  That rate limit is per warm serverless instance and resets on a cold start. Treat
  it as friction against accidental loops, not as authentication.

## Chains

Chain metadata lives in `src/chains/registry.ts`, which drives explorer links and RPC
selection across every module.

Cross-chain vault deposits are offered on the eight chains in
`destinationTokenOptions.ts`: Ethereum (1), Optimism (10), BNB Chain (56),
Gnosis (100), Polygon (137), Base (8453), Arbitrum (42161), and Avalanche (43114).

## Intent contracts

Addresses used when building and settling intent orders are pinned in
`src/lib/intents/contracts.ts`, and are identical across supported chains.

| Constant | Address |
|---|---|
| `INPUT_SETTLER_ESCROW` | `0x000025c3226C00B2Cdc200005a1600509f4e00C0` |
| `OUTPUT_SETTLER_SIMPLE` | `0x0000000000eC36B683C2E6AC89e9A75989C22a2e` |
| `POLYMER_ORACLE` | `0x0000003E06000007A224AeE90052fA6bb46d43C9` |
| `PERMIT2` | `0x000000000022D473030F116dDEE9F6B43aC78BA3` |

`buildStandardOrder` picks the oracle by route: same-chain orders let the output
settler act as its own oracle, while cross-chain orders use `POLYMER_ORACLE` for
attestation.

## Deadline tuning

`buildDeadlinePlan` in `src/lib/intents/deadlines.ts` derives both order deadlines
from the quote:

- `fillDeadline` — the solver's cutoff. `min(quote validUntil, now + 15 minutes)`.
- `expires` — when a refund unlocks. `fillDeadline + 30 minutes`.

Both defaults are arguments (`maxFillTtlSec`, `refundGraceSec`) rather than
constants, so they can be tightened without touching call sites.

## The EDB backend

The debugger depends on the Rust EDB service behind `/api/edb`. Every other module —
including all of LI.FI Earn — runs without it.