Skip to content

Configuration ​

Comet Engine is configured via environment variables (with .env support for local development).

Core ​

VariableDefaultDescription
PORT8100HTTP listen port
MONGO_URImongodb://localhost:27017MongoDB connection string
MONGO_DBcomet_engineMongoDB database name

Authentication ​

VariableDefaultDescription
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:

VariableDefaultDescription
ENGINE_NETWORKmainnetmainnet 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 .env for the active network (CELO_RELAYER_PRIVATE_KEY on mainnet, CELO_TESTNET_RELAYER_PRIVATE_KEY on 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 ​

VariableRequiredDescription
{CHAIN}_RPC_URLYesJSON-RPC endpoint
{CHAIN}_CHAIN_IDYesEVM chain ID
{CHAIN}_RELAYER_PRIVATE_KEYYesPlatform relayer private key (hex)
{CHAIN}_UNISWAP_FACTORYNoUniswapV2Factory address
{CHAIN}_UNISWAP_ROUTERNoUniswapV2Router02 address
{CHAIN}_{SYMBOL}_CONTRACTNoERC-20 contract address
{CHAIN}_{SYMBOL}_TYPENoAsset type (utility, stablecoin, airtime)
{CHAIN}_{SYMBOL}_BACKINGNoBacking type (none, fiat-reserve, airtime)
{CHAIN}_TESTNET_{NAME}NoTestnet 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:

env
CELO_MYTOKEN_CONTRACT=0x...
CELO_MYTOKEN_DECIMALS=18

Adding a New Chain ​

env
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=base

Stellar 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.

VariableDefaultDescription
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_URLthe network's public Horizonhttps://horizon.stellar.org on mainnet, https://horizon-testnet.stellar.org on testnet
STELLAR_NETWORK_PASSPHRASEthe network's passphrasePublic passphrase on mainnet, Test SDF Network ; September 2015 on testnet
STELLAR_BLOCK_EXPLORERhttps://stellar.expert/explorer/testnetExplorer 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.

VariableDefaultDescription
PSP_BASE_URLhttps://payments.mamlakapsp.comPSP 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 ​

TypeBackingDescription
utilitynoneStandard token (USDT, USDC)
airtimeairtimeBacked 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.

VariableDefaultDescription
IMPALAPAY_BASE_URLhttps://airtime-api.impalapay.com/api/appImpalaPay 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_PCT6Extra airtime the gateway credits on a top-up (the merchant discount): 5,000 KES buys 5,300 KES of airtime

Exchange Rates ​

VariableDefaultDescription
CBK_RATES_URLCBK's forex table endpointWhere 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_URLhttps://open.er-api.com/v6/latestFallback only, for currencies CBK doesn't publish
FX_SPREAD_BPS150Engine margin around CBK's mid on KES legs
IMM_STABLE_SPREAD_BPS50Engine 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.

VariableDefaultDescription
SMTP_HOST—Mail server, e.g. smtp.office365.com (Microsoft 365)
SMTP_PORT587587 = STARTTLS, 465 = TLS
SMTP_USERNAME—Mailbox to sign in as
SMTP_PASSWORD—Its password (keep secret)
SMTP_FROMSMTP_USERNAMEDisplay sender, e.g. Comet Engine <info@example.com>
DASHBOARD_URLhttp://localhost:3001Where the emails send people to sign in
ENGINE_PUBLIC_URLhttp://localhost:8100The API URL given to tenants
DOCS_URLhttp://localhost:5173The 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:

  1. For the organisation.
  2. 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):

EndpointDoes
POST /admin/tenants · POST /admin/admin-usersEmail a welcome and the credentials automatically ("sendEmail": false to skip)
POST /admin/tenants/:id/send-credentialsNew 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-credentialsNew temporary password emailed ({"welcome": true} adds the welcome)
GET /admin/emails/status?check=trueIs 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) ​

VariableDefaultDescription
CARDANO_RPC_URL(empty)External Cardano proxy service URL
CARDANO_SERVICE_TOKEN(empty)Bearer token for the Cardano proxy

Released under the MIT License.