Repository navigation
Expand file tree
/
Copy path.env.example
More file actions
359 lines (332 loc) · 19.1 KB
/
Copy path.env.example
File metadata and controls
359 lines (332 loc) · 19.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# ── Database (Neon Postgres) ──────────────────────────────────────────────────
# Pooled connection string from the Neon console (Connection Details → Pooled).
# Used by the app (Drizzle over Neon's serverless driver), the keeper scripts,
# and `npm run db:migrate`. SERVER-ONLY.
DATABASE_URL=postgres://user:password@ep-xxx-pooler.region.aws.neon.tech/neondb?sslmode=require
# ── Sessions ──────────────────────────────────────────────────────────────────
# Signs the HttpOnly session cookie (HS256 JWT) issued after SEP-10 sign-in.
# At least 32 characters. Rotating it signs everyone out.
# openssl rand -base64 48
SESSION_SECRET=
# ── File storage (Vercel Blob) ────────────────────────────────────────────────
# KYC documents are stored as private blobs. Create a Blob store in the Vercel
# dashboard (Storage → Blob) and copy its read/write token.
BLOB_READ_WRITE_TOKEN=
# Application Configuration
# Public origin of the app (referral links, e-mail links, SIWS domain default).
# Local development: http://localhost:3000
# Production: https://your-domain.com
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Email notifications via Resend
# Used for loan approved, loan funded, and payment overdue emails.
RESEND_API_KEY=
RESEND_FROM_EMAIL=TrustLend <notifications@your-domain.com>
RESEND_REPLY_TO_EMAIL=
# Stellar Testnet Configuration
# These values will be used by wallet setup, balance reads, and future Soroban contract interactions.
NEXT_PUBLIC_STELLAR_NETWORK=testnet
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
# ── Mobile wallets via WalletConnect ──────────────────────────────────────────
# Lets users connect a mobile Stellar wallet (LOBSTR, Freighter Mobile, …) by
# scanning a QR code, instead of needing a browser extension.
#
# Create a free project at https://cloud.reown.com (formerly WalletConnect Cloud)
# and paste its Project ID here. Add your deployed origin to the project's
# allowed domains, otherwise the relay rejects the pairing.
#
# Leave blank to hide/disable the WalletConnect option; the extension wallets
# (Freighter, xBull) and Albedo keep working either way.
#
# The WalletConnect session is negotiated on the network implied by
# NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE above (pubnet for mainnet, otherwise
# testnet), so keep that value in sync with the network you expect wallets on.
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
# ── Sign-In with Stellar (SIWS / SEP-0010) ────────────────────────────────────
# Web3-native login: users authenticate by signing a challenge with Freighter
# instead of a password. See lib/auth/siws-*.ts and app/api/auth/siws/*.
#
# Domain the SEP-10 challenge is bound to (home_domain / web_auth_domain).
# Both the challenge builder and verifier must use the same value. Defaults to
# the host of NEXT_PUBLIC_SITE_URL if unset.
NEXT_PUBLIC_SIWS_DOMAIN=localhost:3000
#
# SEP-10 server signing key (S...). SERVER-ONLY — generate a dedicated key,
# never reuse the platform admin key. stellar keys generate trustlend-siws --global
SIWS_SERVER_SECRET=
NEXT_PUBLIC_STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
NEXT_PUBLIC_STELLAR_FRIENDBOT_URL=https://friendbot.stellar.org
# Auth Role Persistence
# These keys are used internally to manage role-based redirects
NEXT_PUBLIC_PENDING_ROLE_KEY=trustlend_pending_role
# Comma-separated allowlist for the admin panel. Accepts e-mail addresses and
# Stellar public keys (G...), since accounts are wallet-based. An allowlisted
# account must ALSO have profiles.role = 'admin'.
# Example: GABC...XYZ,admin@example.com
TRADE_VAULT_ADMIN_EMAILS=
# ── Soroban RPC ───────────────────────────────────────────────────────────────
# Testnet Soroban RPC endpoint (no API key required for testnet)
NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
# ── Soroban Contract IDs ──────────────────────────────────────────────────────
# These IDs are generated when deploying contracts to the Stellar network.
# One command deploys everything and fills these in automatically:
#
# npm run deploy:testnet
#
# It writes the IDs straight into .env.local (updating these keys in place and
# leaving your other values alone), plus a reference copy in .env.contracts.
# Preview it first with `npm run deploy:testnet:dry`, or see all options with
# npm run deploy:testnet -- --help
NEXT_PUBLIC_REPUTATION_CONTRACT_ID=
NEXT_PUBLIC_ESCROW_CONTRACT_ID=
NEXT_PUBLIC_LENDING_CONTRACT_ID=
NEXT_PUBLIC_DEFAULT_CONTRACT_ID=
# Utilization-based interest-rate pool backing the lender pool screens.
NEXT_PUBLIC_POOLED_LENDING_CONTRACT_ID=
# DAO governance module controlling the platform fee (issue #22).
NEXT_PUBLIC_GOVERNANCE_CONTRACT_ID=
# N-of-M multisig gating rare, high-impact admin config changes (issue #73):
# whitelisting collateral assets, fee tables, linking governance/oracle, and
# moving insurance-fund balances. See docs/contracts/multisig-admin.md.
NEXT_PUBLIC_MULTISIG_ADMIN_CONTRACT_ID=
# TLEND governance token and its distribution contracts (issue #107). Optional —
# the core lending flows work without them.
NEXT_PUBLIC_TLEND_TOKEN_CONTRACT_ID=
NEXT_PUBLIC_TLEND_VESTING_CONTRACT_ID=
NEXT_PUBLIC_TLEND_AIRDROP_CONTRACT_ID=
# Contracts the lending contract calls into during activate_loan / repayment
# (deploy:testnet links them automatically), plus the standalone modules.
# Optional — the app reads them when set (lib/contracts/*).
NEXT_PUBLIC_REFERRAL_REWARDS_CONTRACT_ID=
NEXT_PUBLIC_BORROWER_LOYALTY_CONTRACT_ID=
NEXT_PUBLIC_TREASURY_CONTRACT_ID=
NEXT_PUBLIC_AUTO_COMPOUND_VAULT_CONTRACT_ID=
NEXT_PUBLIC_LIQUIDATION_AUCTION_CONTRACT_ID=
NEXT_PUBLIC_USDC_LENDING_POOL_CONTRACT_ID=
NEXT_PUBLIC_ZK_CREDIT_VERIFIER_CONTRACT_ID=
# ── On-chain loan lifecycle ───────────────────────────────────────────────────
# "required" (default whenever NEXT_PUBLIC_LENDING_CONTRACT_ID is set): every
# loan request must be signed on the LendingContract by the borrower, the
# lender who completes funding signs approve_loan, and the server signs
# activate_loan / record_payment / update_pool_state with ADMIN_SECRET_KEY.
# "off": database-only mode (the contracts are not called). See
# docs/onchain-lifecycle.md.
NEXT_PUBLIC_ONCHAIN_LOAN_LIFECYCLE=required
# ── Admin Stellar Address ─────────────────────────────────────────────────────
# This is the public G... address of the account that has administrative
# privileges on the contracts (e.g., freezing accounts, triggering payouts).
# Obtain this by running `stellar keys address trustlend-admin` or similar.
NEXT_PUBLIC_ADMIN_ADDRESS=
# ── Platform wallet ───────────────────────────────────────────────────────────
# Public address that receives pool deposits (client + server must agree; the
# deposit API verifies the payment landed here) and platform fees on repayment.
NEXT_PUBLIC_PLATFORM_STELLAR_ADDRESS=
PLATFORM_FEE_WALLET=
# Secret key of that same account. Pool withdrawals are paid out from it by the
# server; without it POST /api/pools/withdraw refuses rather than recording a
# withdrawal that never moved any XLM. SERVER-ONLY.
PLATFORM_WALLET_SECRET=
# ── Backend automation: cron jobs ─────────────────────────────────────────────
# Shared secret the serverless crons require in the Authorization header
# (`Bearer <CRON_SECRET>`). Set the same value in your Vercel Cron / scheduler.
# REQUIRED outside local dev: if unset, every /api/cron/* route refuses calls
# with 500. Only `next dev` (NODE_ENV=development) skips the check when unset.
CRON_SECRET=
# Admin Stellar SECRET key (S...). Signs activate_loan, record_payment and
# update_pool_state when the on-chain lifecycle is required (see above), is
# used by the Default-Management cron to sign
# mark_defaulted / record_default on-chain, and to PROPOSE (not execute)
# insurance payouts on the MultiSigAdmin contract (issue #73) — this key must
# itself be a registered multisig signer for that proposal to succeed. A human
# still has to gather the remaining approvals and call `execute`.
# SERVER-ONLY — never prefix with NEXT_PUBLIC_ and never commit a real value.
ADMIN_SECRET_KEY=
# Default-Management automation thresholds (days overdue). Optional — these are
# the defaults; the friendly grace window ends day 7, insurance pays at day 60.
DEFAULT_GRACE_PERIOD_DAYS=7
DEFAULT_INSURANCE_PAYOUT_DAYS=60
# ── Decentralized Credit Oracle ───────────────────────────────────────────────
# The authorized oracle is the only account allowed to post off-chain credit
# scores on-chain (via `submit_credit_score`). Register it once after deploy:
# stellar contract invoke --id <REPUTATION_ID> -- set_oracle \
# --admin <ADMIN_ADDRESS> --oracle <ORACLE_ADDRESS>
#
# Public G... address of the oracle (safe to expose to the browser).
NEXT_PUBLIC_ORACLE_ADDRESS=
#
# Secret S... key of the oracle — used by scripts/oracle-post-credit-score.mjs
# to sign on-chain posts. SERVER-ONLY. Never prefix with NEXT_PUBLIC_ and never
# commit this. Generate with: stellar keys generate trustlend-oracle --global
ORACLE_SECRET_KEY=
# ── Liquidation Keeper (scripts/liquidation-keeper.ts) ────────────────────────
# Automated bot that liquidates under-collateralized loans. Vercel Hobby only
# permits daily crons, so vercel.json runs POST /api/cron/liquidation once a
# day as a safety net and .github/workflows/keepers.yml hits the same endpoint
# every 5 minutes (needs the KEEPER_BASE_URL + CRON_SECRET repo secrets) — or
# run `npm run liquidation:keeper:service`
# (--interval=60) self-hosted. One-shot `npm run liquidation:keeper` remains
# available for cron schedulers. Requires ADMIN_SECRET_KEY (above) to sign
# liquidation transactions, plus NEXT_PUBLIC_LENDING_CONTRACT_ID /
# NEXT_PUBLIC_REPUTATION_CONTRACT_ID / NEXT_PUBLIC_ADMIN_ADDRESS.
#
# Where to source open loans from: "db" (the database, default) or "chain"
# (iterate the LendingContract directly — no database needed).
LIQUIDATION_SOURCE=db
# Evaluate only; never submit a liquidation transaction. Useful for staging.
LIQUIDATION_DRY_RUN=false
# If set, the keeper loops forever, sleeping this many seconds between runs
# (background-service mode). Leave unset for a single cron-triggered run.
LIQUIDATION_POLL_INTERVAL_SECS=
#
# ── Price oracle ──
# XLM/USD price used to value outstanding debt (always denominated in XLM).
# Since issue #267 this is only a FALLBACK: the keeper asks the live price
# oracle first and uses this value only when the oracle has nothing usable.
LIQUIDATION_XLM_PRICE_USD=0.12
# Set to "false" to ignore the live oracle and always use the value above.
# Automatically disabled under test.
LIQUIDATION_USE_LIVE_PRICES=true
# Same switch for the borrower dashboard's health factor gauge (issue #323).
# Set to "false" to value debt at the fallback price instead of asking the
# oracle. Automatically disabled under test.
DASHBOARD_USE_LIVE_PRICES=true
# Collateral asset prices as a JSON map: { "<assetAddress>": { "symbol": "...",
# "priceUsd": <num>, "decimals": <num> } }. Assets missing here are skipped
# (never guessed) — the keeper logs a warning and moves on.
LIQUIDATION_PRICE_TABLE_JSON=
# Fallback asset-volatility input (bps) fed into the contract's dynamic
# liquidation-threshold formula when an asset has no specific override.
LIQUIDATION_DEFAULT_ASSET_VOLATILITY_BPS=2000
#
# ── Alerts ──
# Incoming webhook URLs — the keeper posts on every liquidation and failure.
# Leave blank to disable either channel.
LIQUIDATION_SLACK_WEBHOOK_URL=
LIQUIDATION_DISCORD_WEBHOOK_URL=
# ── Fiat On/Off Ramp (Stellar Anchor, SEP-24) ────────────────────────────────
# Lets borrowers cash loans out to a bank account or mobile-money wallet via an
# anchor's interactive flow. Defaults target the SDF reference TEST anchor so it
# works out-of-the-box on testnet; point these at a regulated anchor for prod.
#
# Anchor home domain hosting /.well-known/stellar.toml (no protocol).
NEXT_PUBLIC_SEP24_ANCHOR_HOME_DOMAIN=testanchor.stellar.org
# Asset the borrower withdraws to fiat (the SDF test anchor exposes SRT & USDC).
NEXT_PUBLIC_SEP24_ASSET_CODE=SRT
# Optional asset issuer (G...). If blank it is resolved from the anchor's
# stellar.toml CURRENCIES list by matching the asset code above.
NEXT_PUBLIC_SEP24_ASSET_ISSUER=
# ── KYC / AML — SumSub Provider (Issues #118, #262) ──────────────────────────
# 3rd-party identity verification for borrowers AND lenders (large-volume
# lender compliance, issue #262). Verified lenders unlock regulated pools and
# higher deposit limits; status updates arrive automatically via webhook.
# Get credentials at https://sumsub.com → Dashboard → Apps & Webhooks.
#
# App Token (public — used to sign API requests from the server).
SUMSUB_APP_TOKEN=
# Secret Key (private — used to sign API request HMAC digests).
SUMSUB_SECRET_KEY=
# Webhook Secret — used to verify X-Payload-Digest on incoming callbacks.
SUMSUB_WEBHOOK_SECRET=
# Base URL (default: https://api.sumsub.com).
SUMSUB_BASE_URL=https://api.sumsub.com
# Verification level name configured in your SumSub dashboard.
SUMSUB_LEVEL_NAME=basic-kyc-level
#
# Webhook URL to register in SumSub dashboard:
# https://<your-domain>/api/kyc/webhook
#
# When these vars are empty the integration runs in DEVELOPMENT MODE:
# a fake applicantId is returned and the widget shows a simulator UI.
# ── API Rate Limiting (Issue #268) ────────────────────────────────────────────
# Every /api/* request is capped at 100 requests per minute per client IP by
# proxy.ts; exceeding it returns 429 with Retry-After and X-RateLimit-* headers.
# Individual routes apply stricter per-route policies on top (lib/rate-limit.ts).
#
# Optional Upstash Redis — enables counters shared across serverless instances.
# Without these the limiter falls back to a bounded in-memory store, which is
# per-instance only and resets on restart (fine for local dev / single node).
# Get credentials at https://console.upstash.com → Redis → REST API.
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
#
# Optional comma-separated list of IPs that skip rate limiting entirely
# (e.g. your monitoring/uptime prober or Prometheus scraper).
# RATE_LIMIT_WHITELIST=203.0.113.10,198.51.100.4
RATE_LIMIT_WHITELIST=
#
# Requests carrying `Authorization: Bearer <ADMIN_SECRET_KEY>` also bypass the
# limits — see ADMIN_SECRET_KEY above.
# =============================================================================
# Collateral Price Oracle (issue #267)
# =============================================================================
# Long-lived keeper that polls XLM and BTC prices and pushes them on-chain:
# npm run price:oracle # 5-second loop (the issue's requirement)
# npm run price:oracle:once # single cycle, for coarse cron schedulers
# npm run price:oracle:dry # aggregate and log, publish nothing
#
# ── Cadence ──
# Seconds between polls. Defaults to 5. Vercel Cron cannot go below 60s, which
# is why the 5s cadence needs the long-lived keeper rather than the cron route.
ORACLE_POLL_INTERVAL_SECS=5
# Age (ms) beyond which a price is considered unusable. Past this the keeper
# refuses to publish rather than pushing a stale valuation that could trigger a
# wrongful liquidation.
ORACLE_MAX_STALENESS_MS=120000
# Run a single cycle and exit, instead of looping.
ORACLE_RUN_ONCE=false
# Aggregate prices but never write on-chain.
ORACLE_DRY_RUN=false
#
# ── Assets ──
# Contract addresses of the tracked collateral assets. A symbol with no address
# is aggregated and logged but never published.
ORACLE_XLM_ASSET_ADDRESS=
ORACLE_BTC_ASSET_ADDRESS=
#
# ── Sources ──
# Prices are the median across every source that responds, so one dead or
# misbehaving API degrades the feed instead of breaking it. Set any of these to
# "true" to drop that source.
ORACLE_DISABLE_COINGECKO=false
ORACLE_DISABLE_BINANCE=false
ORACLE_DISABLE_STELLAR_DEX=false
# Optional CoinGecko demo API key, for higher rate limits.
COINGECKO_API_KEY=
# USDC issuer used as the USD leg of the Stellar DEX orderbook.
ORACLE_USDC_ISSUER=GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
#
# ── Alerts ──
# Falls back to the LIQUIDATION_* webhooks when these are unset.
ORACLE_SLACK_WEBHOOK_URL=
ORACLE_DISCORD_WEBHOOK_URL=
# ── Automated database backups (Issue #273) ───────────────────────────────────
# Used by scripts/backup.sh, run nightly at 00:00 UTC by the "Automated DB
# Backup" GitHub Actions workflow (.github/workflows/db-backup.yml).
#
# In CI these are repository *secrets*, not values in this file. Restore
# instructions and bucket setup live in docs/disaster-recovery.md.
#
# pg_dump needs the DIRECT (non-pooled) Neon connection string — the same
# DATABASE_URL as above with the "-pooler" segment removed from the host. Set
# BACKUP_DATABASE_URL when it differs; otherwise DATABASE_URL is used.
BACKUP_DATABASE_URL=
#
# Passphrase used to encrypt each dump with AES-256 before upload.
# ⚠️ Store this in a password manager as well as in CI. If it is lost, every
# backup ever taken becomes permanently unreadable. Rotating it does NOT
# re-encrypt existing backups — keep the old key as long as you keep old dumps.
BACKUP_ENCRYPTION_KEY=
#
# Destination bucket name, without the s3:// prefix. Should live in a different
# account or at least a different region from the database, and have versioning
# plus a restrictive bucket policy. See docs/disaster-recovery.md.
S3_BUCKET=
#
# Optional. Key prefix within the bucket (default: backups).
BACKUP_S3_PREFIX=backups
# Optional. Days to keep backups before the script prunes them (default: 30).
# An S3 lifecycle policy is preferred; this is a backstop.
BACKUP_RETENTION_DAYS=30
# Optional. Comma-separated schemas to exclude from the dump (default: none).
BACKUP_EXCLUDE_SCHEMAS=
# Optional. Set to 1 to dump, encrypt and verify without writing to S3.
BACKUP_DRY_RUN=0