Configuration
Comet Engine is configured via environment variables (with .env support for local development).
Core
| Variable | Default | Description |
|---|---|---|
PORT | 8100 | HTTP listen port |
MONGO_URI | mongodb://localhost:27017 | MongoDB connection string |
MONGO_DB | comet_engine | MongoDB database name |
Authentication
| Variable | Default | Description |
|---|---|---|
SERVICE_TOKENS | (empty) | Comma-separated allowlist of X-Service-Token values |
WALLET_ENCRYPTION_KEY | (empty) | CRITICAL — Master secret for AES-GCM sealing of user EVM private keys. Losing this loses every wallet. |
Network (mainnet / testnet)
One switch picks the network for every chain at once:
| Variable | Default | Description |
|---|---|---|
ENGINE_NETWORK | mainnet | mainnet or testnet. Anything else means mainnet. |
Mainnet reads the plain variables (CELO_RPC_URL, CELO_RELAYER_PRIVATE_KEY, CELO_IMC_CONTRACT, STELLAR_RELAYER_SEED, …). Testnet reads their *_TESTNET_* counterparts (CELO_TESTNET_RPC_URL, CELO_TESTNET_RELAYER_PRIVATE_KEY, CELO_TESTNET_IMC_CONTRACT, STELLAR_TESTNET_RELAYER_SEED, …), falling back to the plain variable when a testnet one isn't set. Both networks are configured, and behave, the same way — switching is a one-line change.
The engine also serves the network it is not running on, view-only, to platform admins: send X-Network: mainnet (or testnet) on a GET request to read that network's chains, balances and journal. Writes to it are refused. GET /api/v1/networks lists both.
Replaces CELO_NETWORK
Earlier versions switched each chain separately (CELO_NETWORK, BASE_NETWORK, …). Those variables are no longer read.
Relayers
Every chain operation is signed by a relayer — a wallet that pays gas and, for issuance, signs the mint.
- The engine's relayers are the ones in
.envfor the active network (CELO_RELAYER_PRIVATE_KEYon mainnet,CELO_TESTNET_RELAYER_PRIVATE_KEYon testnet;STELLAR_RELAYER_SEED/STELLAR_TESTNET_RELAYER_SEED). They belong to the platform: the super admin tenant signs with them by default. - Each tenant must bring its own relayer (Wallet Config → generate or import) for issuance, IMM, AMM and treasury operations. The engine's relayer is never used for a tenant's operations — a tenant without one is refused.
- IMC is the exception: it is always minted with the engine's relayer, and never on a tenant's behalf (see IMC & Airtime →).
Chain Configuration
Assets are auto-discovered from env vars using the pattern {CHAIN}_{SYMBOL}_CONTRACT.
Per-Chain Variables
| Variable | Required | Description |
|---|---|---|
{CHAIN}_RPC_URL | Yes | JSON-RPC endpoint |
{CHAIN}_CHAIN_ID | Yes | EVM chain ID |
{CHAIN}_RELAYER_PRIVATE_KEY | Yes | Platform relayer private key (hex) |
{CHAIN}_UNISWAP_FACTORY | No | UniswapV2Factory address |
{CHAIN}_UNISWAP_ROUTER | No | UniswapV2Router02 address |
{CHAIN}_{SYMBOL}_CONTRACT | No | ERC-20 contract address |
{CHAIN}_{SYMBOL}_TYPE | No | Asset type (utility, stablecoin, airtime) |
{CHAIN}_{SYMBOL}_BACKING | No | Backing type (none, fiat-reserve, airtime) |
{CHAIN}_TESTNET_{NAME} | No | Testnet value of any variable above, used while ENGINE_NETWORK=testnet |
Adding a New Asset
Deploy the contract and set the env var. No code changes needed:
CELO_MYTOKEN_CONTRACT=0x...
CELO_MYTOKEN_DECIMALS=18Adding a New Chain
CUSTOM_CHAINS=base
BASE_RPC_URL=https://mainnet.base.org
BASE_CHAIN_ID=8453
BASE_RELAYER_PRIVATE_KEY=0x...Chain Selection
Select chain via ?chain= query parameter. Defaults to the first configured chain (typically celo).
POST /api/v1/assets/USDT/mint?chain=celo
POST /api/v1/assets/USDT/mint?chain=baseStellar Chain
Set STELLAR_RELAYER_SEED to enable a separate, non-EVM wallet family (see Wallets API →). Absent this variable, chain=stellar requests return 400 chain "stellar" not configured — every other chain and endpoint is unaffected.
| Variable | Default | Description |
|---|---|---|
STELLAR_RELAYER_SEED / STELLAR_TESTNET_RELAYER_SEED | (empty) | Required to enable Stellar on that network. Secret seed (S...) of the relayer/issuer account — pays fees, funds every new user wallet on creation, and in phase 1 issues assets registered on this chain. This account must already be funded on-chain before Stellar wallets will work — an unfunded relayer fails wallet creation with stellar: relayer account ... is not funded. |
STELLAR_HORIZON_URL | the network's public Horizon | https://horizon.stellar.org on mainnet, https://horizon-testnet.stellar.org on testnet |
STELLAR_NETWORK_PASSPHRASE | the network's passphrase | Public passphrase on mainnet, Test SDF Network ; September 2015 on testnet |
STELLAR_BLOCK_EXPLORER | https://stellar.expert/explorer/testnet | Explorer base URL used in responses/UIs |
Stellar assets are auto-discovered the same way EVM assets are, using the STELLAR_{SYMBOL}_CONTRACT-style pattern (asset issuer address, not a contract address).
Full setup walkthrough (generating and funding the relayer keypair) is in Setting Up Stellar →.
Every functionality this touches
Once configured, ?chain=stellar (or "chain": "stellar") works on: Create/Get Wallet, Wallet Balance, Token Balance, Mint, Send, Burn (Holder), Register/Deregister Asset, and Receipt Tokenization. It does not apply to Approve or Burn (Relayer) — Stellar has no allowance concept, both return 501 — or to the Celo /swap/* endpoints, which are Uniswap-specific and have no chain parameter at all. Stellar has its own native AMM instead, at separate /stellar/* endpoints — see Stellar AMM →.
PSP / Mobile Money Top-up
Set these to enable the PSP top-up admin endpoints (POST /api/v1/admin/topup/stk-push, /api/v1/admin/topup/transfer) and their callback receiver. Absent PSP_API_KEY/PSP_API_SECRET, those endpoints return 503 PSP integration not configured.
| Variable | Default | Description |
|---|---|---|
PSP_BASE_URL | https://payments.mamlakapsp.com | PSP API base URL |
PSP_MERCHANT_ID | (empty) | Merchant ID issued by the PSP |
PSP_API_KEY | (empty) | Required to enable PSP top-up. |
PSP_API_SECRET | (empty) | Required to enable PSP top-up. |
PSP_CALLBACK_SECRET | (empty) | Shared secret used to verify the X-Mamlaka-Signature header on incoming callbacks |
PSP_CALLBACK_BASE_URL | (empty) | This engine's own publicly-reachable base URL, used to build the callback URL sent to the PSP on each top-up/transfer request |
Asset Type & Backing
| Type | Backing | Description |
|---|---|---|
utility | none | Standard token (USDT, USDC) |
airtime | airtime | Backed by real airtime float (IMC) |
Airtime-backed assets use special journaling: mints debit the airtime inventory account, burns credit it. They are also proof-of-reserve checked: a mint is refused unless the gateway float covers every unit in circulation after it — float_kes ≥ (total_supply + new) × CBK USD/KES. See IMC & Airtime →.
Airtime Gateway (IMC)
Check the reseller account without sending anything: go run ./cmd/airtimecheck (account, float, commission, recent sales). The dashboard's Treasury → IMC & Airtime shows the same, flags when the gateway's Airtel commission differs from AIRTIME_BONUS_PCT, lists the gateway's sales, and can request a float top-up (POST /admin/imc/airtime/float-topup {"amount", "payingPhoneNumber"}, mainnet only).
The gateway is simulated on testnet and live on mainnet, set by ENGINE_NETWORK alone; no variable overrides it. On testnet, redemptions succeed with a SIM- reference and no real airtime is sent, and the simulated gateway counts as backing for mints.
IMC is minted from airtime bought at the gateway and burned when redeemed back to airtime. The gateway is ImpalaPay — the same organization account the Comet Wallet backend uses.
| Variable | Default | Description |
|---|---|---|
IMPALAPAY_BASE_URL | https://airtime-api.impalapay.com/api/app | ImpalaPay airtime reseller API |
IMPALAPAY_MERCHANT_ID | (empty) | Reseller merchant ID (e.g. 001) |
IMPALAPAY_USERNAME | (empty) | Reseller username |
IMPALAPAY_PASSWORD | (empty) | Reseller password — the engine signs in with these (/reseller/login), reads the float and its API key from /details, and sends airtime with /airtime |
AIRTIME_BONUS_PCT | 6 | Extra airtime the gateway credits on a top-up (the merchant discount): 5,000 KES buys 5,300 KES of airtime |
Exchange Rates
| Variable | Default | Description |
|---|---|---|
CBK_RATES_URL | CBK's forex table endpoint | Where the Central Bank of Kenya rates are read from — the engine's only rate source. Checked every 15 minutes. See Exchange Rates → |
FX_RATE_API_BASE_URL | https://open.er-api.com/v6/latest | Fallback only, for currencies CBK doesn't publish |
FX_SPREAD_BPS | 150 | Engine margin around CBK's mid on KES legs |
IMM_STABLE_SPREAD_BPS | 50 | Engine margin on USD-pegged pairs; also sets the IMM cycle's default internal rate (CBK − this) |
Onboarding Email
New tenants and admin users get two emails: a welcome (for a tenant, how to get started; for an admin user, the dashboard sections they can access) and their credentials (temporary dashboard password; for a tenant, also the API key and secret). The emails go out in the background and are logged under Settings → Email. The log records what was sent and whether it was delivered, never the body.
| Variable | Default | Description |
|---|---|---|
SMTP_HOST | — | Mail server, e.g. smtp.office365.com (Microsoft 365) |
SMTP_PORT | 587 | 587 = STARTTLS, 465 = TLS |
SMTP_USERNAME | — | Mailbox to sign in as |
SMTP_PASSWORD | — | Its password (keep secret) |
SMTP_FROM | SMTP_USERNAME | Display sender, e.g. Comet Engine <info@example.com> |
DASHBOARD_URL | http://localhost:3001 | Where the emails send people to sign in |
ENGINE_PUBLIC_URL | http://localhost:8100 | The API URL given to tenants |
DOCS_URL | http://localhost:5173 | The developer docs link |
Check the mailbox without sending anything: go run ./cmd/smtpcheck.
Microsoft 365
Microsoft 365 turns SMTP sign-in off by default. If the check reports SmtpClientAuthentication is disabled for the Tenant, turn on authenticated SMTP twice in the Microsoft 365 / Exchange admin centre:
- For the organisation.
- For the mailbox: Users → the mailbox → Mail → Manage email apps → Authenticated SMTP.
Security defaults also block it, and the mailbox needs a password that isn't protected by MFA-only sign-in.
Endpoints (super admin / service token):
| Endpoint | Does |
|---|---|
POST /admin/tenants · POST /admin/admin-users | Email a welcome and the credentials automatically ("sendEmail": false to skip) |
POST /admin/tenants/:id/send-credentials | New temporary password emailed with the API key; {"welcome": true} adds the welcome, {"rotateSecret": true} issues and includes a new API secret |
POST /admin/admin-users/:id/send-credentials | New temporary password emailed ({"welcome": true} adds the welcome) |
GET /admin/emails/status?check=true | Is email configured, and can the engine sign in (sends nothing) |
POST /admin/emails/test {"to": "..."} | Sends a sample welcome email |
GET /admin/emails?to=&limit= | Recent emails and whether they were sent |
Cardano Proxy (legacy — not supported)
| Variable | Default | Description |
|---|---|---|
CARDANO_RPC_URL | (empty) | External Cardano proxy service URL |
CARDANO_SERVICE_TOKEN | (empty) | Bearer token for the Cardano proxy |