TIDE docs
v0.1.0
Live demo Get help
● Set up · Settings and keys

Configuration
one document, four layers.

TIDE reads its operator settings from one JSON document. You edit it in the in-app operator panel, host it yourself for many installs, or both. The only build setting is your domain.

01Two places, one rule

WhereWhat goes thereChange needs
Build setting --dart-define=TIDE_HOST=…Your domain. Site, docs, terms, privacy, support e-mail and referral links are built from it.A new build
Operator config documentFee, referrals, cashback, feature switches, names, links, RPC endpoint and API keysOperator panel → Save and apply, or a new hosted document. No rebuild.
No .env file

TIDE has no .env and no other --dart-define values. TIDE_HOST is the only one in the code (lib/core/config/app_endpoints.dart). Keys are never compiled in: RuntimeKeys.assertShipsKeyless() runs at start-up and fails a debug build if one ever is.

02Your domain (TIDE_HOST)

Default: tide.mikodes.com. Pass your own host (no https://) on every run and build:

flutter run -d chrome --dart-define=TIDE_HOST=app.example.com
flutter build web --release --dart-define=TIDE_HOST=app.example.com
Derived valueResult for app.example.com
Sitehttps://app.example.com
Docs link in the apphttps://app.example.com/docs
Terms / privacyhttps://app.example.com/terms, /privacy
Support e-mailsupport@app.example.com
Referral linkhttps://app.example.com/r/<code>

If you set Support email, Website, Terms URL or Privacy URL in the operator panel, the app uses those and ignores the derived value. The Android https referral link has its own host in AndroidManifest.xml (Rebranding → Deep links).

03The four layers

Weakest first. The strongest layer that supplies a value wins that value, leaf by leaf (lib/core/config/config_layers.dart).

LayerWhat it is
1. Built-in defaultAdminConfigModel.defaults, compiled in. No fee, no payouts, no copy execution.
2. Cached remote documentThe last hosted document this device fetched. Used only when a fetch fails.
3. Remote documentThe document you host, fetched at start-up. Not wired in the shipped build (how to wire it).
4. Operator panelWhat was saved on this device. Stored sparse: only values you changed.

Operator panel → In force right now lists every value the app is running on and which layer it came from.

04The document

This is the full shape, with the shipped defaults. Operator panel → Copy as JSON gives you the same shape with your values (only the ones you changed).

{
  "config_version": 1,
  "app_name": "TIDE",
  "tagline": "Swap on Solana. See what other wallets did.",
  "support_email": "",
  "website_url": "",
  "terms_url": "",
  "privacy_url": "",
  "announcement": "",
  "min_app_version": "",
  "default_slippage_pct": 1.0,
  "admin_locked": false,
  "flags": {
    "swap_enabled": true,
    "feed_enabled": true,
    "watchlist_enabled": true,
    "profiles_enabled": true,
    "realised_standings_enabled": true,
    "push_enabled": false,
    "referral_enabled": false,
    "cashback_enabled": false,
    "evm_chain_enabled": false,
    "copy_execution_enabled": false
  },
  "fee": {
    "fee_enabled": false,
    "fee_bps": 0,
    "min_fee_usd": 0,
    "solana_fee_wallet": "",
    "evm_fee_wallet": ""
  },
  "referral": {
    "referral_enabled": false,
    "tiers": 0,
    "tier1_share_bps": 0,
    "tier2_share_bps": 0,
    "payout_threshold_usd": 0
  },
  "cashback": {
    "cashback_enabled": false,
    "tiers": []
  },
  "keys": {
    "helius_api_key": "",
    "zerox_api_key": "",
    "solana_rpc_url": "",
    "evm_rpc_url": ""
  },
  "extra_restricted_regions": []
}

An unknown flag key is ignored. A region code that is not two letters is dropped. Every rate is clamped to its code cap on the way in (caps).

05Feature switches

KeyDefaultWhat it does
swap_enabledonSwap screen
feed_enabledonFeed of followed wallets
watchlist_enabledonFollowing wallets
profiles_enabledonProfile of a followed wallet
realised_standings_enabledonThe Realised tab. Off removes the tab and every route into it.
push_enabledoffPush registration, if you wire a token provider (push)
referral_enabledoffMaster switch for the referral programme
cashback_enabledoffMaster switch for volume cashback
evm_chain_enabledoffNot active in this version Base is scaffolded but no quote source is wired, so the switch has no effect.
copy_execution_enabledoffCopy execution module (read first)

06Endpoints and keys

Key in keysPurposeGet it at
solana_rpc_url RecommendedYour Solana RPC endpoint. Replaces the public one, and makes SPL balances load on the web build.Any Solana RPC provider
helius_api_keyNot active in this version Stored and shown as set, but no network call reads it in 0.1.0. To use Helius, paste the full URL https://mainnet.helius-rpc.com/?api-key=<your-key> into solana_rpc_url instead.helius.dev
zerox_api_keyNot active in this version Reserved for the EVM chain, which has no quote source.
evm_rpc_urlNot active in this version Same reason. Not shown in the panel.
A hosted document is public

Anything you put in a hosted document can be read by anyone who finds its URL, and every app build can read it. Use RPC URLs and keys that are restricted in your provider's dashboard (allowed origins, rate limits). A support dump made with toJsonRedacted() shows keys only as [set].

07Remote document for many installs

The panel changes only the device it runs on. To give every install the same configuration:

  1. Configure one buildSet everything in the operator panel.
  2. Copy the documentOperator panel → Copy as JSON.
  3. Add the lockAdd "admin_locked": true to the document, so users cannot change the fee on their own phones.
  4. Host itAny HTTPS static file works, for example https://<your-domain>/tide-config.json.
  5. Wire a fetcherThis is the one code change in the install. In lib/main.dart, pass fetchRemote to AdminConfigService:
    // lib/main.dart — add at the top:
    import 'dart:convert';
    import 'package:http/http.dart' as http;
    
    // replace AdminConfigService(store: adminStore) with:
    AdminConfigService(
      store: adminStore,
      fetchRemote: () async {
        final r = await http.get(Uri.parse('https://<your-domain>/tide-config.json'));
        if (r.statusCode != 200) return null;
        return jsonDecode(r.body) as Map<String, dynamic>;
      },
    ),
  6. Rebuild and releaseFrom then on you change the hosted file; the app reads it on each start.

With admin_locked the panel becomes read-only on every install and says why. Without it, the device's own panel values win, which is what you want while you are the only user. The snippet passes flutter analyze against 0.1.0; the URL is yours to fill in. Firestore or Remote Config work the same way: return the document as a map.

08Push notifications and Firebase (optional)

TIDE works without Firebase and ships no Firebase files. Push is a keyless hook: PushNotificationService in lib/core/notifications/push_notification_service.dart accepts a tokenProvider you supply. To use Firebase: create your own project, add the firebase_core and firebase_messaging packages, run flutterfire configure, initialise Firebase in lib/main.dart, and pass tokenProvider: () => FirebaseMessaging.instance.getToken(). Sending pushes needs a server or the Firebase console; TIDE includes none. These steps are from docs/FIREBASE-SETUP.md and were not run for these docs.

If you store config in Firestore, keep it read-only for clients and never store a wallet address next to a referral code (why).

09Where data is stored

DataWhere
Seed phrase and keysPlatform secure storage (iOS Keychain, Android Keystore; browser storage on web), encrypted by TIDE with AES-256-GCM first
Operator panel valuesOn the device (AdminConfigStore)
Watchlist, notifications, preferences, copy rulesOn the device
Payout ledger and traded volumeOn the device; export as CSV from the Earn screen

There is no server and no database. Nothing is uploaded.