Skip to main content

BorderCash Usage Guide

This guide covers installing, configuring, and operating a BorderCash deployment. It is written for developers and enterprises evaluating or running the platform.

For what the product is and how it is architected, see BorderCash Overview.


1. Prerequisites​

RequirementNotes
Docker and Docker ComposeRecommended and supported path
Python 3.10+Only for non-container installs
PostgreSQL 15Containerised by default
Redis 7Containerised by default
A valid Pluggedspace licenceRequired. Without it the node runs read-only.
Provider API credentialsStellar, payments, and utility providers as applicable

Outbound internet access is required on first run to pull images and Python packages. After installation, licence verification is offline.


2. Installation​

2.1 Quick start​

git clone https://github.com/pluggedspace/BorderCash.git
cd BorderCash
cp .env.example .env

Edit .env before starting anything. At minimum you must set the licence block and replace every generate_a_... placeholder. See Configuration.

docker compose up --build -d
docker compose exec web python manage.py migrate
docker compose exec web python manage.py createsuperuser

The API is then served on the canonical port:

curl http://localhost:8000/healthz

2.2 Verifying the installation​

docker compose ps # all services should be healthy
docker compose exec web python manage.py check

manage.py check is the important one. It fails loudly on a missing or placeholder licence:

  • app.E001 - licence token empty or a placeholder
  • app.E002 - public key missing or still the bundled default

2.3 Non-container installation​

Manual setup (advanced)
  1. Create a virtual environment and install dependencies.
  2. Configure a PostgreSQL database and Redis instance.
  3. Export the environment variables from .env.example.
  4. Run python manage.py migrate.
  5. Serve with Gunicorn and run Celery worker and beat separately.

3. Configuration​

All configuration is environment driven. .env.example is the authoritative template.

3.1 Core​

VariablePurpose
APP_NAME, APP_VERSIONReported in headers and diagnostics
DEBUGMust be False in production
DJANGO_SECRET_KEYDjango secret. Generate a unique 64-character value.
SERVER_JWT_KEYSigning key for access tokens. Must be unique per deployment.
ALLOWED_HOSTSComma-separated hostnames
CSRF_TRUSTED_ORIGINSOrigins permitted to submit state-changing requests
CORS_ALLOWED_ORIGINSClient origins allowed by CORS
FRONTEND_URLPublic URL of the client
PORT8000

3.2 Datastores​

VariablePurpose
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORDDatabase credentials
DATABASE_URLFull connection string
REDIS_PASSWORDRedis auth
CELERY_BROKER_URL, CELERY_RESULT_BACKENDBroker and result backend

3.3 Licensing​

PLUGGEDSPACE_LICENSE_ENGINE_URL=https://registry.pluggedspace.org
PLUGGEDSPACE_API_KEY=
PLUGGEDSPACE_DEPLOYMENT_ID=
PLUGGEDSPACE_LICENSE_TOKEN=
PLUGGEDSPACE_PUBLIC_KEY=

Never commit .env. It is gitignored and is also excluded from release artifacts by the packaging script.

3.4 CORS​

The Flutter client sends these headers on every request:

Authorization, Content-Type, Accept, X-Request-ID, X-Request-Timestamp,
X-Client-Platform, X-Client-Version

CORS_ALLOW_HEADERS must list all of them or browser preflights fail and the client receives ERR_FAILED on every call. Note the singular ALLOW: CORS_ALLOWED_HEADERS is silently ignored and the library falls back to a default list that omits the custom headers.


4. The admin console​

The console is served at /admin/. Sign in with the superuser created during installation.

4.1 Home versus app pages​

Two different page types appear in the admin, and they are easy to confuse:

PageWhat it is
/admin/ - HomeThe executive dashboard: total users, today's transaction volume, pending KYC queue, disputes and anomalies, platform status, and a quick-action launcher.
/admin/<app>/ - an app pageA plain list of the models registered under that Django app (for example Users, Products, Transactions). Not a dashboard.

Every app page carries a "← Back to Dashboard" link.

4.2 Enabling and disabling modules​

Admin → App → Platform Feature & Module Toggles is a single record that controls what the product exposes. Changes take effect immediately - the client fetches its feature flags from /api/v1/config/features/ at start-up and on refresh.

ToggleControls
Enable Shop & UtilitiesProducts, cart, airtime, gift cards, bill payment
Enable Investments & StocksTokenized stock trading and portfolios
Enable Virtual IBAN Accounts (EUR/GBP)Virtual IBAN generation
Enable African NUBAN AccountsAfrican virtual accounts and transfers
Enable Mobile Money (M-Pesa / MoMo)Mobile money deposits and withdrawals
Enable KYC RequirementsIdentity verification enforcement
Enable Dispute & Support System (Monica)User-raised disputes
Enable Crypto Deposits & SwapsChangelly-backed crypto flows
Enable Fiat On/Off-RampsBank, Transak, Railsr transfers
Enable Points & Rewards SystemLoyalty and referrals
Enforce 2FA on LoginOTP required at sign-in
Enforce Enhanced KYC VerificationExternal biometric/ID verification
Global Maintenance ModePuts non-admin API routes into maintenance
Maintenance Notice MessageBanner shown while maintenance is active

The same record selects the active brokerage provider (Stellar DEX, Alpaca Markets, or Alpha Vantage) and whether fractional share trading is permitted.

4.3 Brokerage providers​

The active brokerage is a single setting on the same Platform Feature & Module Toggles record: Active Stock Brokerage Provider.

ProviderInstrumentsTradingNotes
Stellar DEXCurated registry fileYesTokenized assets, 24/7. Needs an issuer address per instrument.
Alpaca MarketsSynced from the providerYesReal US equities, fractional shares, paper-trading by default.
Alpha Vantage—NoMarket data only. Buy/Sell fail by design.

Switching is admin-only — there is no API endpoint for it. Edit the record in Django Admin; the change applies immediately, because the client reads its feature flags and the brokerage engine resolves the provider per request.

Populating the instrument catalogue​

Nothing is typed in by hand any more. Run:

docker compose exec web python manage.py sync_instruments

or pin a provider explicitly:

docker compose exec web python manage.py sync_instruments --provider alpaca
docker compose exec web python manage.py sync_instruments --provider stellar
docker compose exec web python manage.py sync_instruments --provider alpaca --limit 100
docker compose exec web python manage.py sync_instruments --provider alpaca --deactivate-missing

Alpaca populates itself from GET /v2/assets, so it is safe to schedule (e.g. nightly via celery beat). Add credentials under Provider & API Secret Configurations with provider alpaca, or set ALPACA_API_KEY_ID / ALPACA_API_SECRET_KEY.

Stellar cannot be discovered live — the DEX has no asset-list endpoint and a price lookup needs a (code, issuer) pair. Instruments come from invest/data/stellar_instruments.json, which ships empty. Add the real issuer public keys for the equities you intend to list, then re-run the command. Entries without an issuer_address are skipped with a warning, because a price lookup without one raises AssetIssuerInvalidError.

The same symbol can exist on both rails at once: instruments are keyed on (provider, symbol), and the API only ever returns the active provider's rows.

Price refresh​

docker compose exec web python manage.py update_stock_prices --verbose

Instruments with no cached price yet are populated on the first successful fetch. For Alpaca, order sizing uses a live quote at submit time rather than the cached price, so a stale cache cannot cause a spurious "insufficient balance" rejection.

If you do not want to curate a Stellar list, switch the active provider to Alpaca — it populates itself.

4.4 Branding​

Admin → App → Organization & Branding Configurations sets the organisation name, legal entity, logo, favicon, three colour tokens, support contacts, and the Terms/Privacy URLs. These are database records, so a rebuild is never required.

Admin → App → Provider & API Secret Configurations stores third-party API credentials. Values are encrypted at rest and displayed masked.


5. Running the client​

The Flutter client targets web, desktop, and mobile from one codebase.

cd FRONTEND/border
flutter run -d chrome --dart-define=BASE_URL=http://localhost:8000

Brand colours can be overridden at build time:

flutter run -d chrome \
--dart-define=BASE_URL=http://localhost:8000 \
--dart-define=PRIMARY_COLOR=0xFF1E3A8A \
--dart-define=ACCENT_COLOR=0xFF10B981

5.1 Navigation​

The bottom navigation bar is assembled dynamically from the active module flags:

TabAlways presentNotes
HomeYesBalance, quick actions, recent activity
BankingYesCards, virtual accounts, IBAN details
InvestOnly when Investments is enabledPortfolio and stock market
ShopOnly when Shop & Utilities is enabledCatalogue, cart, orders, gift cards
ProfileYesKYC status, settings, security

5.2 Screen walkthrough​

  • Home - total and local balance, Deposit / Withdraw / Send P2P / Banking quick actions, recent transactions, and a notification bell.
  • Banking & Cards - virtual account and card presentation showing IBAN/SWIFT, bank name, and auto-convert settings.
  • Invest - portfolio value, holdings, profit/loss, and the tokenized stock market with buy/sell.
  • Shop - product catalogue with categories and search, cart, shipping and fees, checkout, and order history.
  • Utilities - airtime, data bundles, combo bundles, gift cards, and bill payment, each scoped by country and provider.
  • Profile - KYC status and submission, personal details, security settings, transaction PIN, and points/referrals.

Navigation uses the platform default back behaviour. On a detail page the platform shows a back affordance; on a root tab it does not, because there is nothing to pop.


6. API reference​

Interactive documentation is generated on every node:

  • Swagger UI - http://localhost:8000/swagger/
  • ReDoc - http://localhost:8000/redoc/
  • OpenAPI schema - http://localhost:8000/swagger.json

6.1 Authentication​

Obtain a token, then send it as a bearer credential:

curl -X POST http://localhost:8000/api/login/ \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"<password>"}'
curl http://localhost:8000/api/balance/ \
-H "Authorization: Bearer <access_token>"

6.2 Public configuration endpoint​

GET /api/v1/config/ requires no authentication and intentionally returns no secrets - only branding, enabled modules, and active banking rails. This is what the client uses to configure itself.

6.3 Health endpoints​

EndpointPurpose
/healthzBasic liveness
/livezLiveness probe
/readyzReadiness probe
/api/v1/node/Node identity and licence status

7. Operating a deployment​

7.1 Routine commands​

docker compose ps # service health
docker compose logs -f web # web logs
docker compose logs -f celery_worker # background task logs
docker compose exec web python manage.py migrate # apply migrations
docker compose exec web python manage.py test # run the test suite
docker compose restart web # restart after config change

7.2 Backups​

Admin → Backup schedules and records jobs; uploads are tracked alongside each job. Confirm that your storage credentials are valid before relying on the schedule, and restore into a scratch environment periodically to prove the backups are usable.

7.3 Scheduled tasks​

celery_beat dispatches scheduled work from the database schedule, including periodic account monitoring. Both Celery services run a broker ping health check rather than the image's default HTTP probe, so docker compose ps reporting healthy reflects real broker connectivity.

7.4 Before going to production​

  • DEBUG=False
  • DJANGO_SECRET_KEY and SERVER_JWT_KEY regenerated and unique
  • Database, Redis, and Celery passwords rotated away from defaults
  • Real licence token and public key installed (manage.py check clean)
  • Superuser password rotated away from the initial value
  • ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, and CORS_ALLOWED_ORIGINS set to real origins
  • All four custom client headers present in CORS_ALLOW_HEADERS
  • Static assets collected at build time
  • Backups scheduled and a restore rehearsed
  • Provider credentials populated in Provider & API Secret Configurations
  • Module toggles set to match what you intend to offer

8. Known gaps and beta features​

These are known and deliberate. Plan around them rather than discovering them in production.

AreaCurrent behaviourImpact
Utility top-ups - airtime, data, billsRequests validate and return pending_integrationNo dispatch to live provider APIs. Treat as beta.
Gift cardsCatalogue endpoints work; fulfilment pendingRequires provider credentials
Shop checkoutCreates a pending order and empties the cartDoes not yet charge a wallet or reserve stock
Monica AI assistantRequires a real GROQ_API_KEYUnset, it returns "something went wrong"
Stellar instrument listShips empty in invest/data/stellar_instruments.jsonNeeds real Stellar issuer addresses, or switch to Alpaca which self-populates
Brokerage switchingAdmin only, no APIEdit Platform Feature & Module Toggles

Empty state is expected on a fresh install. The Shop catalogue, utility providers, and gift cards ship with no seed data, so those pages are blank until you create records in the admin.


9. Troubleshooting​

9.1 Every write returns 403​

The node is in read-only maintenance because the licence is missing or invalid. Reads, /admin/, and health checks still work so you can diagnose it.

docker compose exec web python manage.py check
  • app.E001 - the token is empty or still a placeholder such as paste_your_...
  • app.E002 - the public key is missing or still the bundled default

A placeholder is the most common cause, because it looks like a credentials problem rather than a licensing one. There is no switch to bypass enforcement; install a valid licence.

9.2 Client calls fail with ERR_FAILED in the browser​

A CORS preflight is being rejected. Check that CORS_ALLOW_HEADERS includes x-client-platform and x-client-version in addition to x-request-id and x-request-timestamp. See CORS.

9.3 Celery shows unhealthy​

Should not occur on a stock deployment. The services use a broker ping rather than the image's HTTP probe. If it does, check Redis connectivity and that the broker URL matches the configured Redis password.

9.4 The invest or notifications screen reports a type error​

A stale client build. Stop the running process with q and start it again - a hot reload does not recompile the Dart kernel, so code changes will not take effect.

9.5 Background tasks are not running​

docker compose logs --tail 50 celery_beat celery_worker

Confirm Redis is healthy and that CELERY_BROKER_URL carries the correct password.


10. Support and security​

  • Security vulnerabilities - report privately to support@pluggedspace.org. Please do not open a public issue. See SECURITY.md.
  • Licensing and releases - the Pluggedspace registry at https://registry.pluggedspace.org.
  • In-product support - the support email and phone configured under Organization & Branding Configurations are what your users see.