# Configuration

Coldtill is configured with environment variables. `coldtill init` writes the essentials to `deploy/.env`,
which Docker Compose passes to every container; the compose file adds per-service values.

Coldtill was called Keyhold before. Each `COLDTILL_` variable that isn't set falls back to its old
`KEYHOLD_` name and logs a warning, so an existing `.env` keeps working until you rename it. API keys
issued before the rename (`kh_…`) keep working too; new ones start `ct_`.

## Core

| Variable | Default | Purpose |
|---|---|---|
| `COLDTILL_MODE` | `testnet` | `testnet` or `mainnet`. Decides which networks are seeded. Each install runs in one mode |
| `COLDTILL_SITE_URL` | `http://localhost:8080` | Public URL. Used for checkout links, cookies and the CSRF origin check. Mainnet requires `https://` |
| `COLDTILL_LISTEN` | `:8080` | Address the `api` process listens on |
| `COLDTILL_DATABASE_URL` | local default | PostgreSQL connection URL |
| `COLDTILL_SECRET` | none | 64 hex characters (32 bytes). Signs sessions, seals two-factor and webhook secrets, keys the payout approval codes. Required by `api` and `watcher`; the compose file blanks it for `sweeper` and `payouts` |
| `COLDTILL_TRUST_PROXY` | `false` | Take the client IP from the rightmost `X-Forwarded-For` entry. The compose file sets it for `api`, behind Caddy. Leave it off if nothing in front of Coldtill sets that header |
| `COLDTILL_SECURE_COOKIES` | `true` for `https://` site URLs | Mark session cookies `Secure` |

## Hot wallets

| Variable | Default | Purpose |
|---|---|---|
| `COLDTILL_AGE_RECIPIENT` | none | Public half of the hot-wallet identity. The `api` uses it to encrypt keys you generate or import |
| `COLDTILL_AGE_IDENTITY_FILE` | `/data/coldtill-identity.age` | `sweeper` and `payouts` only: the encrypted identity |
| `COLDTILL_HOT_PASSPHRASE_FILE` | none | `sweeper` and `payouts` only: file holding the passphrase, for unattended restarts |
| `COLDTILL_HOT_PASSPHRASE` | none | The passphrase itself. Takes precedence over the file. Use it to supply the passphrase at start-up instead of storing it |

## Editions

Coldtill is built in three editions. The rate is compiled into the binary and enforced on chain
for new deposit wallets.

| Edition | Build | Sweep fee | Who |
|---|---|---|---|
| Pro | default (`EDITION=pro`) | 0% | Paid licence, production |
| Standard | `-tags standard` | 1% | Cheaper paid tier |
| Free | `-tags free` | 2% | Free to use |

Fee-edition images need the FeeConfig addresses at build time:

```
make image-free FEE_CONFIG_EVM=0x… FEE_CONFIG_TRON=T…
```

Do not lock FeeConfig or the Solana program until the NFT-share / splitter recipient is final.
`coldtill-fees lock-evm` refuses unless `COLDTILL_CONFIRM_FEE_LOCK=yes`.

## Solana

| Variable | Default | Purpose |
|---|---|---|
| `COLDTILL_SOL_PROGRAM_ID` | `EuGW7oFaoXT2acbTyNUzhRjs4sY1W8iq7BPKkGWZ1bk8` | Deposit program. Change it only if you deploy your own build; the dashboard asks for extra confirmation when it differs |
| `COLDTILL_SOL_GENESIS` | devnet or mainnet by mode | Pins the Solana cluster. Watching, sweeping and paying out refuse to run against a different cluster. `any` disables the check, for local validators only |

## Email

Payout approval codes are emailed. On testnet without SMTP they are printed to the `api` log instead. On
mainnet Coldtill starts without SMTP, but held payouts can't be approved until it is configured.

| Variable | Default | Purpose |
|---|---|---|
| `COLDTILL_SMTP_HOST` | none | SMTP server |
| `COLDTILL_SMTP_PORT` | `587` | SMTP port (STARTTLS) |
| `COLDTILL_SMTP_USERNAME`, `COLDTILL_SMTP_PASSWORD` | none | SMTP credentials |
| `COLDTILL_SMTP_FROM` | none | Sender address |

## Update checks

| Variable | Default | Purpose |
|---|---|---|
| `COLDTILL_UPDATE_CHECK` | on | Once a day the API asks coldtill.com for the latest version and security advisories, sending only its version and edition. `off` turns it off |
| `COLDTILL_UPDATE_FEED` | `https://coldtill.com/releases.json` | Release feed URL, for mirrors or testing. Format: [Updates and advisories](https://coldtill.com/docs/updates.md) |

## Auto-convert

| Variable | Default | Purpose |
|---|---|---|
| `COLDTILL_ONECLICK_URL` | `https://1click.chaindefuser.com` | NEAR Intents 1Click API. Setting it in testnet mode turns auto-convert on against that endpoint, for testing with a stub |
| `COLDTILL_ONECLICK_API_KEY` | none | Optional 1Click JWT. Without one, 1Click adds a 0.2% fee to every quote |
| `COLDTILL_THORNODE_URL`, `COLDTILL_MIDGARD_URL` | Liquify public gateway | THORChain endpoints |

On mainnet the provider HTTP client never follows redirects and refuses private and reserved addresses, so
provider URLs must be public.

## Compose-only

| Variable | Purpose |
|---|---|
| `POSTGRES_PASSWORD` | Database password; also embedded in `COLDTILL_DATABASE_URL` |
| `COLDTILL_IMAGE` | Image to run (default `coldtill:local`) |
| `COLDTILL_UID`, `COLDTILL_GID` | User the `sweeper` and `payouts` containers run as, so they can read `secrets/` |
| `COLDTILL_CADDY_ADDRESS` | Site address for Caddy: a bare domain (`pay.example.com`) gets an automatic certificate. Comma-separate several names (`pay.example.com, pay.shop.com`) when projects have their own checkout domain |
| `COLDTILL_HTTP_PORT`, `COLDTILL_HTTPS_PORT` | Host ports Caddy publishes (default 80 and 443) |
| `COLDTILL_TRUSTED_PROXIES` | Space-separated ranges of proxies in front of Caddy whose client-IP header is believed (default: private ranges only) |
| `COLDTILL_CLIENT_IP_HEADER` | Header those proxies put the visitor's IP in (default `X-Forwarded-For`) |

### Behind Cloudflare

With Cloudflare's proxy (orange cloud) in front, every request reaches the server from a Cloudflare address.
Tell Caddy to believe Cloudflare's `CF-Connecting-IP` header, but only from Cloudflare's own ranges, so rate
limits apply per visitor and the header can't be forged by connecting to the server directly:

```bash
cd deploy
echo "COLDTILL_TRUSTED_PROXIES=$( { curl -fsS https://www.cloudflare.com/ips-v4; echo; curl -fsS https://www.cloudflare.com/ips-v6; } | xargs)" >> .env
echo "COLDTILL_CLIENT_IP_HEADER=CF-Connecting-IP" >> .env
docker compose up -d caddy
```

Set Cloudflare's SSL/TLS mode to **Full (strict)**. Cloudflare's ranges change rarely; re-run the first command
if they do.

## Per-project settings

Most behaviour is configured per project in the dashboard, not in the environment:

- **Branding:** name, logo, colour, theme, checkout message, support and return links, payment expiry. See
  [Branding](https://coldtill.com/docs/branding.md).
- **Sweeps:** activation minimum, sweep amount, address count and interval.
- **Payouts:** minimum, auto-approve amount, hourly and daily limits. See [Payouts](https://coldtill.com/docs/payouts.md).
- **Auto-convert:** settlement asset, maximum slippage, THORChain fallback. See [Auto-convert](https://coldtill.com/docs/auto-convert.md).

Per network (global admin): enabled, confirmations, nodes, and which tokens are enabled.

## Checkout domains

A project can send customers to its own hostname, such as `pay.shop.com`, instead of the dashboard's address.
A server admin sets it under Project settings → Checkout domain (two-factor required). Before saving:

1. Point a DNS `A` record for the name at the server.
2. Add the name to `COLDTILL_CADDY_ADDRESS`, comma separated, and run `docker compose up -d caddy`.

On a checkout domain Coldtill serves only `/pay/`, `/api/checkout/`, `/api/v1/`, static assets, branding images and
`/healthz`. The login page, the dashboard and `/api/dash` answer 404 there, so the checkout name can't be used to
phish admins. New payment links use the domain; links already sent keep working on the old address too.

For DNS, Cloudflare certificates and a worked example with several projects, see
[Guide: projects, domains and a private dashboard](https://coldtill.com/docs/guides/domains-and-dashboard.md).

---

Source: https://coldtill.com/docs/configuration/ · All Coldtill docs: https://coldtill.com/llms.txt
