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 |
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 |
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:
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.
- Sweeps: activation minimum, sweep amount, address count and interval.
- Payouts: minimum, auto-approve amount, hourly and daily limits. See Payouts.
- Auto-convert: settlement asset, maximum slippage, THORChain fallback. See Auto-convert.
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:
- Point a DNS
Arecord for the name at the server. - Add the name to
COLDTILL_CADDY_ADDRESS, comma separated, and rundocker 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.