18) Local backup and session hygieneUse profile/logout backup behavior and local backup tooling to preserve local DB state before major changes.
Recommended practice:
backup before large route/ledger changes
backup before publishing major code changes
keep backups outside the repo
verify git status before every commit
Continued in Part 2 of 2: Quick Start, security, troubleshooting, boundaries, and project status.
UTT — Unified Trading Terminal
Part 2 of 2
Quick startImportant: UTT is designed to run with local configuration and local secrets. Do not paste real keys into tracked files. Keep runtime secrets outside the repository.
PrerequisitesRecommended baseline:
- Python 3.10+
- Node.js 18+ and npm
- Windows PowerShell for the Windows-oriented commands below
- a local Solana wallet extension if using Solana DEX features
- a Polkadot/Substrate wallet extension such as SubWallet if using Polkadot / Hydration flows
- UniSat if using Counterparty signing, broadcast, BTC balance, or Ordinals / inscription features
- MetaMask or another compatible browser EVM wallet if using Robinhood Chain execution features
- venue access and any required API credentials for the venues you plan to test
1) Clone the repositorygit clone https://github.com/eyemaginative/utt-unified-trading-terminal.git
cd utt-unified-trading-terminal
2) Configure backend environmentThe repository uses
backend.env.example as the public, sanitized backend runtime template. The backend environment is for runtime configuration and local pathing,
not for storing exchange API keys.
Relevant files:
- backend.env.example — safe public template committed to this repo
- backend/.env — local stub file that points the backend to your private env path
- backend/app/config.py — backend configuration loader
Recommended setup:
# Example only; choose your own private location outside the repo.
Copy-Item backend.env.example C:\path\to\utt-secrets\backend.env
Then create or update
backend/.env with only the external env pointer:
UTT_ENV_PATH=C:\path\to\utt-secrets\backend.env
The private
backend.env file lives outside the repo and contains local-only runtime configuration. Exchange API keys and RPC/API keys should be saved through
Profile → API Keys whenever the app supports that venue.
For Polkadot / Hydration work, keep the real RPC/API key out of the repository. The recommended pattern is to save the Dwellir/Hydration key through
Profile → API Keys using the Hydration venue key, while the private env keeps only non-secret runtime toggles and templates.
A safe local Hydration configuration uses placeholder/template values such as:
UTT_HYDRATION_RPC_PROVIDER=dwellir
UTT_HYDRATION_RPC_URL_TEMPLATE=https://api-hydration.n.dwellir.com/{api_key}
UTT_HYDRATION_WS_URL_TEMPLATE=wss://api-hydration.n.dwellir.com/{api_key}
UTT_HYDRATION_RPC_URL=
UTT_HYDRATION_ENABLE_ROUTER_QUOTES=0
UTT_HYDRATION_ENABLE_SWAP_TX=1
UTT_HYDRATION_ENABLE_EXACT_BUY=1
UTT_HYDRATION_ENABLE_MANUAL_POOL_FALLBACK=1
UTT_HYDRATION_MANUAL_POOL_LIVE_RESERVES=1
UTT_HYDRATION_ENABLE_EXTERNAL_USD_PRICES=1
UTT_HYDRATION_EXTERNAL_USD_PRICE_SOURCE=coingecko
UTT_HYDRATION_ENABLE_SDK_PRICE_CACHE=1
UTT_HYDRATION_PRICE_CACHE_USE_SIDECAR=0
UTT_HYDRATION_PRICE_CACHE_USE_SDK_FALLBACK=0
UTT_HYDRATION_PRICE_CACHE_TTL_S=300
UTT_HYDRATION_PRICE_CACHE_ERROR_BACKOFF_S=600
UTT_HYDRATION_EXTERNAL_USD_PRICE_TIMEOUT_S=5
UTT_HYDRATION_PRICE_CACHE_STRATEGY=spot_then_sell
UTT_HYDRATION_PRICE_CACHE_SPOT_IMPLEMENTATION=direct
UTT_HYDRATION_USE_SIDECAR=0
UTT_HYDRATION_SIDECAR_URL=http://127.0.0.1:8787
UTT_HYDRATION_AUTOSTART_SIDECAR=0
UTT_HYDRATION_PRICE_CACHE_AUTOSTART_SIDECAR=0
UTT_HYDRATION_SIDECAR_QUOTE_CACHE=1
UTT_HYDRATION_SIDECAR_QUOTE_CACHE_TTL_MS=30000
UTT_HYDRATION_SIDECAR_QUOTE_CACHE_STALE_TTL_MS=300000
UTT_HYDRATION_SIDECAR_QUOTE_BACKOFF_MS=120000
UTT_HYDRATION_SIDECAR_QUOTE_CACHE_MAX_ENTRIES=100
UTT_HYDRATION_HELPER_STEP_TIMEOUT_S=30
The persistent Hydration sidecar is optional and normally only needed for controlled SDK diagnostics. For the public-safe path, keep sidecar autostart disabled and leave sidecar usage off unless you intentionally start:
cd backend
$env:UTT_HYDRATION_SIDECAR_HOST="127.0.0.1"
$env:UTT_HYDRATION_SIDECAR_PORT="8787"
node app\services\hydration_sidecar.mjs
If sidecar diagnostics are intentionally enabled, set
UTT_HYDRATION_USE_SIDECAR=1 and/or
UTT_HYDRATION_PRICE_CACHE_USE_SIDECAR=1 in your private env for that local test only.
Hydration asset IDs, decimals, external price IDs, and route/pool metadata are intended to be managed through the Token Registry and Route Registry rather than hardcoded into tracked env files.
Counterparty / UniSat configurationCounterparty public data uses the Counterparty API, while wallet control remains in UniSat. A public-safe baseline is:
COUNTERPARTY_ENABLED=1
COUNTERPARTY_API_BASE_URL=https://api.counterparty.io:4000
COUNTERPARTY_NETWORK=mainnet
COUNTERPARTY_WALLET_PROVIDER=unisat
# Keep broadcast off unless intentionally performing a reviewed live test.
COUNTERPARTY_LIVE_BROADCAST_ENABLED=0
Additional Counterparty fee, Bitcoin transaction lookup, cache, and timeout controls may be configured in the private env as needed. Do not store a Bitcoin private key, seed phrase, or signed PSBT in the repository.
The configured Counterparty custody address should be managed through
Wallet Addresses. Browser wallet account discovery is useful for comparison and signing, but passive backend balance and accounting paths should not depend on invisible browser state.
Robinhood Chain configurationRobinhood Chain is separate from the Robinhood Crypto brokerage adapter. A public-safe read-only baseline is:
ROBINHOOD_CHAIN_ENABLED=1
ROBINHOOD_CHAIN_RPC_HTTP=https://rpc.mainnet.chain.robinhood.com/
ROBINHOOD_CHAIN_RPC_WS=
ROBINHOOD_CHAIN_CHAIN_ID=4663
ROBINHOOD_CHAIN_TIMEOUT_S=15
ROBINHOOD_CHAIN_CACHE_TTL_S=30
ROBINHOOD_CHAIN_ERROR_BACKOFF_S=120
ROBINHOOD_CHAIN_MAX_CONCURRENT=1
ROBINHOOD_CHAIN_EXPLORER_API_BASE=https://robinhoodchain.blockscout.com/api/v2
ROBINHOOD_CHAIN_SWAP_PROVIDER=0x
ROBINHOOD_CHAIN_ZEROX_API_BASE=https://api.0x.org
# Keep live execution off unless intentionally performing a bounded test.
ROBINHOOD_CHAIN_LIVE_EXECUTION_ENABLED=0
Store the 0x API key through
Profile → API Keys using venue
zerox. Do not put the key in the README, source files, or tracked env files.
Enabling
ROBINHOOD_CHAIN_LIVE_EXECUTION_ENABLED=1 is not sufficient by itself. Live execution also requires the global trading gates, an accepted registry capability, a matching saved wallet, a fresh provider plan, an explicit database authority, and explicit browser-wallet confirmation for each transaction stage.
3) Create and activate a backend virtual environmentcd backend
python -m venv .venv
.\.venv\Scripts\Activate.ps1
4) Install backend dependenciesIf the repo uses
requirements.txt:
pip install -r requirements.txt
If the repo uses
pyproject.toml, install according to that project file instead.
The Hydration helper services also use Node-based backend dependencies. From the
backend directory, install the backend JS helper dependencies when using Polkadot / Hydration features:
These dependencies support helper-side Hydration tooling such as
hydration_quote.mjs and
hydration_sidecar.mjs.
5) Start the backendA common local run command is:
uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
6) Install frontend dependenciesIn a separate terminal:
7) Configure frontend envA typical local setting is:
VITE_API_BASE=http://127.0.0.1:8000
Start the frontend9) Add venue API keys in the appVenue credentials are added inside the app through the user profile, not by editing tracked env files.
Open:
- Profile
- API Keys
- add the venue
- enter the required key material for that venue
- save it through the UI
The current codebase uses profile and API-key management flows with local encrypted secret-bundle handling rather than relying on committed backend files.
10) Live trading safety gatesUTT separates UI capability from live backend routing. A venue may appear in the Order Ticket and still be blocked from live routing until backend gates are intentionally opened.
Typical live routing controls include:
DRY_RUN
ARMED
LIVE_VENUES
venue-specific gate variables such as OKX_ENABLE_TRADING
For OKX live submit/cancel testing, the backend process must be started from an environment equivalent to:
$env:DRY_RUN="false"
$env:ARMED="true"
$env:LIVE_VENUES="coinbase,cryptocom,dex_trade,gemini,kraken,robinhood,okx"
$env:OKX_ENABLE_TRADING="1"
Then restart the backend from that same terminal. If any required gate is missing, the backend should reject live OKX routing with a clear message such as
Venue 'okx' is not enabled for LIVE routing.
Do not grant withdrawal permission to OKX API keys for UTT live order testing. The OKX workflow only requires the permissions needed for balances, market data, and trade placement/canceling.
Counterparty and Robinhood Chain use additional venue-specific gates:
COUNTERPARTY_LIVE_BROADCAST_ENABLED
ROBINHOOD_CHAIN_LIVE_EXECUTION_ENABLED
These gates do not authorize an automatic transaction. Counterparty still requires explicit UniSat signing and a separate explicit broadcast confirmation. Robinhood Chain still requires a matching execution authority, a fresh provider plan, and a separate explicit MetaMask confirmation for each approval or swap transaction.
Installation notes by environmentWindowsThe repository and current operator workflow are heavily Windows-tested and PowerShell-oriented. Windows is the easiest platform to start with.
Linux / macOSThe backend and frontend stacks are portable in principle, but local pathing, shell scripts, wallet extension workflows, and some venue-specific tooling may need adaptation.
Environment and secrets modelUTT is intentionally structured so that public source code can live in git while live credentials remain local.
What belongs in the repository- code
- schema and model definitions
- example env files such as backend.env.example
- non-sensitive defaults
- UI assets intended for publication
- utility scripts that do not contain secrets
What does not belong in the repository- real API keys
- private keys and PEM files
- local DB files
- production runtime logs
- wallet seed phrases and mnemonics
- local backup data
- locally generated venue key material
Recommended practice- keep private env files outside the repo
- use tracked stub files only
- add venue API credentials through Profile → API Keys
- scan staged diffs before every push
- keep wallet and account testing material separate from source control
Counterparty / UniSat notesCounterparty assets are Bitcoin-controlled metaprotocol assets. UTT therefore treats market discovery, wallet signing, broadcast, and accounting as separate stages.
Data and custody modelThe backend can read public Counterparty data and resolve the configured custody address from Wallet Addresses. UniSat remains the browser wallet for account discovery, inscriptions, signing, and any operator-enabled broadcast.
Relevant concepts:
configured custody address = authoritative account used by backend balance/accounting reads
connected UniSat address = browser account used for explicit wallet actions
Counterparty asset quantity = metaprotocol balance
BTC quantity = Bitcoin balance and transaction funding
A mismatch between the configured custody address and the connected UniSat address should be treated as a blocking review condition, not silently corrected.
Market and execution modesThe Counterparty ticket separates:
Dispenser Purchase
Protocol Order
A dispenser purchase targets a visible fixed lot and exact BTC payment. A protocol order uses the Counterparty order protocol and may include expiration blocks and different compose semantics. UTT should not silently switch modes because one path is unavailable.
PSBT and broadcast modelThe backend builds or normalizes unsigned compose output and may expose a PSBT handoff when the result is signable. Before
signPsbt is enabled, UTT checks the source address, selected level, funding requirements, miner fee, and available input metadata.
The browser flow is:
unsigned compose preview
→ explicit UniSat signPsbt
→ signed, not broadcast result
→ separate explicit pushPsbt confirmation when operator-enabled
COUNTERPARTY_LIVE_BROADCAST_ENABLED=0 keeps broadcast disabled. When it is
1, UTT still does not broadcast automatically. Backend code never holds the signing key or submits the transaction.
Accounting previewsCounterparty accounting previews can separate:
- acquired Counterparty asset quantity
- dispenser or order BTC consideration
- Bitcoin miner fee
- historical BTC/USD observation
- custody-address scope
- source UTXO identity and candidate basis-lot coverage
- transaction-level warnings and unresolved basis conditions
These are review surfaces. They do not automatically mutate the ledger, create lots, consume FIFO, or decide tax treatment.
Robinhood Chain notesRobinhood Chain is an EVM chain integration and is intentionally independent from Robinhood brokerage API credentials.
Identity and registry modelToken identity comes from Token Registry rows and chain metadata rather than frontend symbol assumptions. Pair objectives and directional capabilities bind:
symbol
side
input token
output token
amount mode
provider
probe / ceiling amount
wallet
execution status
A quote or discoverable token does not automatically become an executable pair.
Wallet and transaction modelPassive chain operations use backend RPC reads. Signing remains in the browser wallet.
For an ERC-20 exact-input swap, the lifecycle may require:
Stage 1 — finite approval
Stage 2 — exact-input swap
Each stage has its own claim and explicit browser-wallet request. UTT rejects stale plans, reused claims, wallet mismatches, chain mismatches, destination mismatches, calldata mismatches, amount overruns, and invalid value fields.
Quantity and Total remain manually editable. Auto-calc may synchronize them when enabled, but the backend authority and accepted exact-input ceiling remain the safety boundary.
Receipt reconciliationA confirmed receipt is reconciled against saved execution evidence. Preferred reconciliation uses receipt logs plus historical balance cross-checks.
Some Robinhood Chain RPC endpoints serve transactions and receipts but do not retain the historical state needed for before/after token balance calls. For the explicit
metadata is not found / non-archive condition, UTT can use a receipt-log fallback for ERC-20-to-ERC-20 reconciliation while still requiring:
- exact transaction hash
- exact sender and destination
- exact calldata hash
- zero native transaction value for the controlled ERC-20 swap
- receipt status 1
- exact input-token transfer from the wallet
- positive output-token transfer to the wallet
- output at or above the accepted minimum
- fee calculation from receipt gas fields
- one approval submission and one swap submission
Historical balances that are available but disagree still fail closed. The fallback is not permission to ignore a real mismatch.
Current execution boundaryThe currently live-validated path is the bounded WETH-USDG exact-input BUY / SELL lifecycle. Generic registry pair discovery and custom-pair Order Book work may exist as preparation or future roadmap items, but arbitrary live custom-token execution is not represented as complete by this README.
Solana DEX notesThe Solana side of UTT is designed to fit into the same terminal as the CEX workflows rather than being a separate application.
Current flow areas in the codebase- wallet-aware order ticket behavior
- Jupiter Metis route handling
- Jupiter Ultra order and execute support
- Jupiter Trigger and limit-related routing
- Raydium swap path construction
- token resolution and token-account-aware routing
- token registry lookups
- balance and wallet helper flows
Wallet behaviorThe frontend integrates wallet selection and wallet-manager behavior for supported Solana wallets. Because wallet and account state matters, two different wallet extensions can behave differently if they are connected to different actual addresses with different balances and token accounts.
A route succeeding in one wallet and failing in another does not necessarily indicate a code bug. It may indicate:
- different wallet address
- different token balances
- missing associated token account for a given mint
- router-specific account requirements
Polkadot / Hydration DEX notesThe Polkadot / Hydration side of UTT is designed as an opt-in DEX venue path. It is intended to coexist with CEX and Solana DEX workflows without enabling broad SDK quote polling by default.
Current flow areas in the codebase- polkadot_hydration venue selection
- Hydration chain/RPC diagnostics
- Hydration balance retrieval
- Token Registry-based Hydration asset resolution
- Token Registry-based external price metadata
- Route Registry-based UTTT-HDX pool metadata
- live UTTT-HDX reserve lookup
- manual XYK pseudo-orderbook construction
- manual UTTT-HDX order-ticket transaction preparation for sell/exact-in and buy/exact-out flows
- compact Order Book and Order Ticket Hydration price-status UI driven by refresh-free status endpoints
- generic SDK router quotes blocked by default for non-manual pairs
- Hydration order-ticket pre-trade checks and execution path
- Hydration swap recording into swap_orders
- All Orders reflection for confirmed Hydration swaps
- price-cache status reporting for cache-only, live refresh, stale, partial, and error-backoff states
- persistent Hydration sidecar quote-cache / singleflight / backoff diagnostics
- Hydration wallet-history ingestion, materialization, missing-basis, transfer-link, and FIFO workflows
- Spread / Bridge dashboard pricing based on Solana UTTT/USD versus Hydration-derived UTTT/USD
- bridge transfer-record preview, local PLANNED record creation, evidence linking, reconciliation, and read-only basis/apply previews
Token Registry requirementsHydration assets should be configured through Token Registry rows rather than tracked env JSON. For example:
HDX hydration native decimals 12 price source coingecko price id hydradx
DOT hydration 5 decimals 10 price source coingecko price id polkadot
USDT hydration 10 decimals 6 price source stable price id stable
UTTT hydration 1001331 decimals 6 price source derived price id UTTT-HDX*HDX-USD
Route Registry requirementsThe UTTT-HDX route should be configured through the Hydration Route Registry with the route type, fee bps, and live pool account metadata. The intended safe path is:
UTTT-HDX manual/live route
→ live pool reserves
→ manual XYK pseudo-orderbook
→ order ticket execution
→ record_submit
→ swap_orders
→ All Orders
Confirmed non-XYK Hydration routes should also be configured through the Route Registry instead of hardcoded backend route maps. The current manual Router route pattern supports route JSON such as:
DOT-HDX manual_router / Router / confirmed
DOT(5) → Aave → aDOT(1001) → Omnipool → HDX(0)
HDX-DOT manual_router / Router / confirmed
HDX(0) → Omnipool → aDOT(1001) → Aave → DOT(5)
These rows allow the Order Ticket to distinguish between:
confirmed manual Router routes that may build Router.sell / Router.buy calls
synthetic price-only orderbooks that must remain non-tradable
generic SDK router routes that remain disabled unless intentionally enabled
The intermediate
aDOT route asset should exist in Token Registry for readability and future route tooling:
aDOT hydration 1001 decimals 10 price source blank / none
It is not normally a user-facing priced balance asset in UTT; it is an intermediate routing leg.
Pricing modelThe public-safe Hydration pricing path uses:
HDX/USD = external price source from Token Registry, normally CoinGecko hydradx
DOT/USD = external price source from Token Registry, normally CoinGecko polkadot
USDT/USD = stable
USDC/USD = stable
HOLLAR/USD = stable
UTTT/HDX = UTTT-HDX live route reserve ratio
UTTT/USD = UTTT/HDX x HDX/USD
Hydration pricing endpoints expose cache status explicitly so the UI can distinguish safe polling from live refresh behavior:
GET /api/polkadot_dex/hydration/prices/status
-> status-only, refresh-free, sidecar-autostart-free, safe for UI polling
GET /api/polkadot_dex/hydration/prices?refresh=false
-> cache-only response; may be fresh, stale, partial, or unavailable, but does not refresh
GET /api/polkadot_dex/hydration/prices?refresh=true
-> controlled backend refresh using external USD prices and the UTTT-HDX manual/live route
The Order Book and Order Ticket use the status-only endpoint for UI status display. The Order Book keeps Hydration price status inline with the existing depth / auto / route row so switching venues does not resize the widget or push the asks/bids area downward. The Order Ticket shows the same pricing state as a compact
Prices pill in the Polkadot controls area.
The price response includes
statusDetail and cache classification fields such as:
cache_only_fresh
cache_only_partial_stale
live_fresh
partial_stale
error_backoff
refresh_failed_stale
Generic Hydration SDK router quotes are disabled by default for the public-safe configuration. The persistent
hydration_sidecar.mjs service includes quote-cache, singleflight, stale-serve, and per-key error-backoff protection for controlled SDK diagnostics, but SDK fallback remains disabled in the normal pricing path.
Hydration wallet-history and ledger workflowUTT now includes a Hydration wallet-history ingestion path intended to bring self-custody Hydration activity into the same local ledger workflow as venue and wallet-address activity.
Current Hydration history workflow areas include:
- optional Subscan-backed Hydration history provider support
- safe provider=none default behavior when no history provider is configured
- dry-run-first ingestion and coverage diagnostics
- page-windowed backfill controls for deeper history scans
- wallet-address transaction caching before any deposit / withdrawal materialization
- trusted amount parsing with amount_v2 validation for integer-looking provider amounts
- materialization of cached Hydration wallet transactions into AssetDeposit and AssetWithdrawal rows
- raw provenance metadata such as provider, source type, source address, transaction hash, and network
- missing-basis lot creation for detected deposits without assigning fake USD basis
- transfer-link preview and metadata-only linking for likely internal transfers
- a withdrawal-before-deposit safety gate for transfer-link candidates
- explicit-only FIFO withdrawal lot-impact rebuilding
- strict default handling for insufficient inventory rather than partial or forced consumption
- opening-balance correction support for known missing historical inventory, with basis remaining unknown
- LP / Omnipool special handling for 2-POOL-style rows so they remain visible but are not forced through normal FIFO
This workflow is intentionally conservative. Normal eligible withdrawals can be applied to lots only through explicit dry-run / apply operations. Transfer-linked withdrawals are skipped by default, and LP / pool-token rows are classified for special handling instead of being consumed through normal inventory logic.
Spread / Bridge transfer-record workflowThe Spread / Bridge dashboard is intended to prepare cross-chain UTTT movement before any bridge execution path is enabled.
Current safe sequence:
preview transfer record
→ create local PLANNED bridge-transfer record
→ link source-side evidence
→ link destination-side evidence
→ reconcile the local record
→ preview basis / tax treatment
→ preview future apply-basis-transfer plan
This workflow is planning-only until the operator is ready for a real bridge test. It classifies source activity as a
TRANSFER_OUT candidate and destination activity as a
TRANSFER_IN candidate, but the actual basis-transfer apply endpoint remains intentionally absent. A future apply endpoint should require real linked
AssetWithdrawal and
AssetDeposit rows, reviewed basis availability, and explicit confirmation such as
confirm_apply_basis_transfer=true.
Auth, profile, and local credential handlingThe codebase includes auth, profile, and local credential-management work. In practical terms, that means UTT is intended to be an operator workstation, not just a stateless public dashboard.
Examples of functionality reflected in the current repository include:
- profile and auth routing
- API-key management UI flows
- DB-backed and encrypted secret-bundle patterns in code
- local runtime settings and operator preferences
Venue API keys are added through the
Profile / API Keys interface and stored in the application’s local credential store rather than being committed to backend files or repository env files.
Token registry and wallet toolingThe repository includes token-registry-related backend and frontend work. This supports:
- symbol and mint resolution
- display-friendly token labeling
- registry-managed token metadata
- registry-managed external price source and price ID metadata
- market-metrics source resolution through Token Registry external price IDs
- Solana token tooling inside the operator UI
- Hydration asset ID, decimals, and external price metadata
- Hydration Route Registry metadata for manual/live UTTT-HDX pool routing
There is also wallet-address handling in the backend, which supports broader local wallet and workflow integration. Cached wallet-address snapshots can contribute to AppHeader portfolio totals, so self-custody balances can be represented without requiring the balances table to be opened first.
Hydration wallet-history ingestion extends this local wallet tooling by caching indexed wallet transactions, materializing them into local deposits and withdrawals, and linking them to the missing-basis, FIFO lot, and bridge transfer-record workflows without requiring live secrets or runtime database files to be committed.
Orderbook and order-ticket modelUTT uses a unified terminal style where the order book, order ticket, tables, scanners, and other panes are all parts of one coordinated workstation.
Order bookCurrent work includes:
- venue-aware order book display
- pseudo-orderbook behavior for DEX routes
- Counterparty dispenser / protocol-order books with exact BTC and USD context
- Robinhood Chain synthetic provider-backed books for registry-authorized directional quote context
- manual/live Hydration UTTT-HDX orderbook generation
- inline Hydration price-cache status display that does not create an extra notification row
- router-quote safety gating for generic Hydration SDK pairs
- right-lane terminal tile integration
Order ticketCurrent work includes:
- venue-aware order entry
- Counterparty dispenser / protocol-order mode selection, compose preview, fee tier, UniSat PSBT signing, and separately gated broadcast
- Robinhood Chain editable Quantity / Total controls, optional Auto-calc, bounded authority, finite approval, separate swap request, and lifecycle restoration
- Solana wallet-manager integration
- Jupiter and Raydium route selection for DEX paths
- Hydration manual UTTT-HDX sell/exact-in and buy/exact-out transaction preparation
- compact Hydration Prices status pill using refresh-free backend status polling
- blocked generic Hydration swap-tx path when router quotes are disabled
- operator status and preflight behavior
- simplified widget controls with redundant Lock buttons and the Order Ticket top-left resize handle removed
Data and runtime stateYou may see empty tracked directories such as
backend/data/ that exist only to preserve folder structure. Non-secret backend helper scripts may live under
backend/tools/.
That does not mean the repository is intended to contain live runtime data.
In general:
- keep runtime DB files out of source control
- keep generated key material out of source control
- keep tool scripts under backend/tools/ only when they contain no secrets or generated private material
- keep local backups out of source control
- use .gitignore and external paths appropriately
TroubleshootingFrontend starts but cannot reach backendCheck:
- backend is running
- VITE_API_BASE points to the correct backend URL
- backend host and port are reachable from the frontend
Backend starts but venue requests failCheck:
- local runtime env path is correct
- the venue API key was actually added and saved in Profile → API Keys
- the correct venue was configured in the profile
- no real credentials were placed into tracked files
Counterparty / UniSat data, signing, or broadcast does not workCheck:
- COUNTERPARTY_ENABLED=1
- COUNTERPARTY_API_BASE_URL points to the intended Counterparty API
- COUNTERPARTY_NETWORK=mainnet matches the UniSat network
- the intended Bitcoin address exists in Wallet Addresses
- the connected UniSat account matches the configured custody address for the transaction under review
- the selected book row still exists and has the expected dispenser / protocol-order identity
- the requested quantity matches dispenser lot constraints
- BTC funding covers consideration plus miner fee
- the compose preview reports a recognized PSBT
- required UTXO metadata is present before signPsbt
- COUNTERPARTY_LIVE_BROADCAST_ENABLED=1 only when intentionally broadcasting
- broadcast is requested only after a signed-but-not-broadcast result exists
Expected safety behavior:
compose preview may succeed without signing
signPsbt requires explicit user approval
pushPsbt requires a second explicit confirmation
backend signing remains unavailable
automatic broadcast remains false
If a broadcast fails, retain the signed transaction result for review and do not blindly repeat the compose/sign/broadcast sequence. Determine whether the transaction was accepted, rejected, or already propagated before trying again.
Counterparty balances or USD values look incompleteCheck:
- the configured Counterparty Wallet Addresses row
- Counterparty API balance response
- Token Registry price metadata for deterministically mapped assets
- BTC/USD availability
- ASSET-BTC orderbook liquidity
- whether the visible value is a best-bid valuation or an ask-only reference
- stale-fallback warnings and cache age
Ticker-only market-data matches are intentionally not considered authoritative enough for legacy Counterparty asset accounting.
Robinhood Chain balances, history, or quotes do not loadCheck:
- ROBINHOOD_CHAIN_ENABLED=1
- chain ID is 4663
- the HTTP RPC URL is reachable
- the configured wallet address is valid
- required EVM token contracts and decimals exist in Token Registry
- the Blockscout API base is reachable for history
- the 0x API key is saved through Profile → API Keys under venue zerox
- the quote provider is not in error backoff
- the selected pair has a matching registry objective and directional capability
Passive balance, history, quote, and lifecycle reads should not request MetaMask.
Robinhood Chain approval or swap is blockedCheck:
- global DRY_RUN, ARMED, and venue live-gate state
- ROBINHOOD_CHAIN_LIVE_EXECUTION_ENABLED=1
- the execution authority matches symbol, side, provider, wallet, amount mode, and amount ceiling
- the provider plan is fresh
- the claim ID has not expired or already been consumed
- MetaMask is connected to chain ID 4663 and the saved wallet
- finite allowance is sufficient for Stage 2
- transaction destination and calldata hash match the accepted plan
- ERC-20 transaction value is 0 wei
- no second request has already been submitted
Do not solve a stale-plan error by resubmitting an approval. A confirmed finite approval remains valid while a new Stage 2 quote / plan is prepared.
Robinhood Chain receipt refresh returns an RPC historical-state errorA transaction can be confirmed while historical token balance reads fail on a non-archive RPC.
Check:
- direct transaction lookup succeeds
- direct receipt lookup succeeds
- receipt status is 1
- the exact error is the recognized historical-state-unavailable condition
- receipt transfer logs contain the exact input and sufficient output
- saved transaction and calldata hashes match
- submission counts remain one approval and one swap
The receipt-log fallback applies only to explicit unavailable historical state. It must not hide a historical balance mismatch that the RPC can actually return.
Robinhood Chain All Orders row has the wrong direction or economicsFor a confirmed
WETH-USDG SELL, expected normalization is:
side = sell
quantity = actual WETH input
gross / net = actual USDG output
average = USDG output / WETH input
limit = minimum USDG output / exact WETH input
fee asset = ETH
For a BUY, quantity remains the acquired WETH output and the displayed rate uses the BUY orientation. If an older frontend cache still shows the wrong side, hard-refresh and refetch All Orders before treating it as a backend defect.
Robinhood balances show stale available / hold valuesIf the Balances page or Order Ticket shows a Robinhood USD row where
available equals
total but you know funds are reserved by open orders, check whether the balance refresh actually completed.
A response like this means the UI did not receive fresh Robinhood balances:
Skipping refresh for robinhood: in cooldown after recent failure
Timeout fetching balances for robinhood after 12s; entering cooldown
The balance refresh path should use a fast open-order hold overlay and should not wait for a full paginated order-history refresh. The Orders page can still run the full venue order refresh separately.
Check:
POST /api/balances/refresh
GET /api/balances/latest?venue=robinhood&with_prices=true
Expected after a real refresh:
count > 0
captured_at updates
USD hold reflects open BUY reserves when available
available no longer incorrectly equals total when a hold is known
If order refresh is slow, test it separately:
POST /api/orders/refresh?venue=robinhood&force=true
A slow full order refresh does not necessarily mean the balance refresh should fail.
Solana wallet connects but a trade failsCheck:
- which wallet address is actually connected
- whether that address has the required input token balance
- whether that address has the required token account for the mint being used
- which router is selected (Metis, Ultra, or Raydium)
Hydration balances or UTTT-HDX orderbook do not loadCheck:
- the Dwellir/Hydration key is saved through Profile → API Keys
- Hydration Token Registry rows exist for HDX, DOT, USDT, and UTTT
- HDX and DOT have valid external price IDs
- the UTTT-HDX Route Registry row has live pool-account metadata
- broad router quotes are disabled unless intentionally debugging SDK behavior
- backend logs are not showing generic Hydration orderbook calls for pricing pairs such as HDX-USDT, DOT-USDT, or UTTT-USDT
The normal safe-path pricing flow should use
/api/polkadot_dex/hydration/prices,
/api/polkadot_dex/hydration/prices/status, and the UTTT-HDX manual/live route, not generic Hydration orderbook requests for USD pricing.
Hydration price cache or status looks staleThe persistent sidecar does not need to stay open for normal UI polling. Keep it running only while intentionally testing protected SDK diagnostics or a private env configuration that explicitly enables sidecar usage.
Check:
- /api/polkadot_dex/hydration/prices/status is reachable and reports safe_for_ui_polling=true
- Order Book / Order Ticket status indicators are using the status endpoint rather than triggering refresh=true
- /api/polkadot_dex/hydration/prices?refresh=false is being used for cache-only reads
- /api/polkadot_dex/hydration/prices?refresh=true is used only for controlled backend refreshes
- statusDetail.classification is inspected before treating missing prices as failures
- SDK fallback remains disabled unless intentionally testing the protected sidecar path
- UTT_HYDRATION_AUTOSTART_SIDECAR=0 and UTT_HYDRATION_PRICE_CACHE_AUTOSTART_SIDECAR=0 are used for public-safe operation
Expected safe states include:
status_only
cache_only_fresh
cache_only_partial_stale
live_fresh
partial_stale
error_backoff
refresh_failed_stale
Hydration generic orderbook or swap-tx requests are blockedThis is usually expected. Generic SDK-routed pairs such as
DOT-USDT are blocked by default when router quotes are disabled. The expected protective response includes:
hydration_router_quotes_disabled
hydration_swap_tx_requires_router_quotes
quoteStatus.status = disabled
Manual/live routes such as
UTTT-HDX can still build pseudo-orderbooks and unsigned transaction payloads through the manual XYK route. That path supports sell/exact-in and buy/exact-out transaction preparation without reopening generic SDK router quote polling.
Confirmed manual Router routes such as
DOT-HDX and
HDX-DOT can build Router.sell exact-in and Router.buy exact-out payloads when their Route Registry rows are enabled and confirmed. If the Order Book shows a synthetic price-only fallback, the Order Ticket should remain blocked until a confirmed route row or a safe SDK route is available.
For Hydration route issues, check:
- Token Registry contains the visible assets and any intermediate route asset such as aDOT
- Route Registry rows use the correct mode: manual_xyk for XYK live-pool routes, manual_router for Router route JSON
- confirmed=true is set only after a successful live test
- route_mode=auto is selected in Order Ticket so the backend can prefer confirmed manual routes before generic SDK paths
- SDK router quote toggles remain disabled unless intentionally debugging SDK behavior
Hydration Route Registry rows do not make the ticket tradableIf a manual Router route row exists but the Order Ticket remains blocked, inspect the route registry and swap-tx preflight.
Check route registry:
GET /api/polkadot_dex/hydration/route_registry?include_disabled=true
Expected for a confirmed manual Router route:
routeMode: manual_router
poolType: Router
enabled: true
confirmed: true
route: non-empty JSON route legs
Then test unsigned swap transaction building:
POST /api/polkadot_dex/hydration/swap_tx
Expected for a confirmed manual route:
manualRouterFallback: true
executionConfirmed: true
manualCustomSwap.enabled: true
routeModeEffective: manual_router
If the route returns only synthetic orderbook data, it is price context only and should not enable trading.
Hydration wallet-history rows do not appear in deposits or withdrawalsCheck:
- the Hydration history provider is configured intentionally, such as a Subscan API key saved through Profile → API Keys or a private env path
- /api/hydration_wallet_history/status reports the expected provider and key availability
- ingestion is first run with dry_run=true
- page-windowed coverage diagnostics show expected assets before cache/materialization is applied
- cached wallet-address transaction rows exist before materialization
- materialization is run separately from the provider fetch
- deposit rows that lack basis are rebuilt into missing-basis lots before withdrawal FIFO impact is applied
The safe sequence is:
coverage dry run
→ cache trusted wallet tx rows
→ preview materialization
→ apply materialization
→ rebuild missing-basis lots
→ preview withdrawal lot impact
→ apply only clean assets explicitly
Hydration withdrawal FIFO shows insufficient inventoryCheck:
- whether deposits exist for the same venue, wallet, and asset
- whether missing-basis lots have been created for detected deposits
- whether withdrawals predate available deposit lots
- whether the asset needs an opening-balance correction for known historical inventory
- whether the row is transfer-linked and intentionally skipped
- whether the row is an LP / Omnipool-style asset such as 2-POOL and should be held for special handling
The FIFO rebuild path is explicit-only. Do not use
allow_partial=true,
force_rebuild=true, or broad all-asset application unless you are intentionally performing a controlled ledger repair.
OKX Order Ticket, live submit, or cancel does not workCheck:
- OKX credentials are saved through Profile → API Keys for venue okx
- the OKX account/API region matches the account you actually use
- the API key has trade permission for submit/cancel testing
- withdrawal permission is not required and should not be granted for UTT order testing
- /api/venues?include_disabled=true reports OKX as enabled and trading-capable
- the browser has been hard-refreshed after frontend capability changes
- backend live gates are set only when intentionally testing live trading
Expected capability row:
venue: okx
supports.trading: true
supports.balances: true
supports.orderbook: true
supports.markets: true
For a live-gate rejection, inspect the backend message. A response like this means the UI path is working, but the live gate is closed:
Venue 'okx' is not enabled for LIVE routing.
For OKX order state diagnostics:
GET /api/okx/diagnostics?private=true&ccy=DOGE
GET /api/okx/order_diagnostics?symbol=DOGE-USD&limit=100&include_samples=true
GET /api/venue_orders/latest?venue=okx&page_size=100
GET /api/all_orders?scope=VENUES&venue=okx&page_size=100
For OKX fill basis preview:
GET /api/okx/fill_basis_preview?symbol=DOGE-USD&wallet_id=default&limit=100&include_items=true
A preview status of
insufficient_inventory is expected if the DOGE was transferred into OKX from another venue or wallet and the transfer-in basis has not yet been linked or entered.
Market Cap or Volume windows show unavailable dataCheck:
- backend is running and /api/market_metrics/summary is reachable
- the asset exists in local balance / wallet / registry state, or is explicitly selected in the UI
- Token Registry rows use external_price_source and external_price_id where deterministic mapping is needed
- CoinGecko rate limits have not forced the backend into temporary backoff
- backend cache files under backend/data/ are local runtime data and should not be committed
The normal path is:
DB-owned/tracked asset
→ Token Registry external price ID if configured
→ env override if configured
→ automatic CoinGecko symbol search for explicitly selected assets
→ small hardcoded fallback map for bootstrap assets
If a newly integrated venue such as OKX does not appear in a Market Cap or Volume source dropdown, check the local context payload rather than scanning a huge JSON file for common words:
$j = Invoke-RestMethod "http://127.0.0.1:8000/api/market_metrics/summary?assets=db&limit=1000"
$j.venue_filter_options
$j.asset_context.DOGE | ConvertTo-Json -Depth 8
$j.items | Where-Object { $_.asset -eq "DOGE" } | Select-Object asset,is_owned,owned_venues,tracked_venues,venue_filter_keys | ConvertTo-Json -Depth 8
Expected for an OKX-held DOGE balance:
owned_venues includes okx
tracked_venues includes okx
venue_filter_keys includes okx
The Market Cap and Volume windows each maintain browser-side snapshots. If one window updates before the other, click Refresh in that window or clear only that window's browser cache key.
UI layout looks wrongThe terminal UI uses pane and window logic with multiple specialized widgets. Layout issues are usually related to dependencies, recent layout changes, or stale frontend state after major UI updates.
For Hydration specifically, the Order Book price-cache indicator is intentionally inline with the existing depth / auto / route status row. It should not add a standalone notification row or change widget height when switching between CEX venues and
polkadot_hydration.
The Order Book and Order Ticket no longer depend on visible Lock controls for normal operation. If old local UI state behaves oddly after upgrading, clear local widget state or reload the app so the current unlocked widget behavior is applied.
Current boundaries and non-goalsThe repository includes many execution and accounting surfaces, but several boundaries are intentional:
- UTT does not store live secrets in source control.
- UTT does not require withdrawal permission for OKX order testing.
- UTT does not auto-apply FIFO merely because a venue fill exists.
- UTT does not invent missing USD basis for transferred-in assets.
- UTTT bridge execution is not enabled by the planning dashboard.
- UTTT basis-transfer mutation is not enabled by the read-only preview endpoint.
- Generic Hydration SDK polling is not the normal price path and remains disabled unless explicitly enabled for diagnostics.
- Counterparty backend services do not hold Bitcoin private keys, sign PSBTs, or broadcast transactions.
- Counterparty broadcast is separately gated and separately confirmed; it is never automatic.
- Robinhood Chain passive reads and database-only authority changes do not request MetaMask.
- Robinhood Chain backend services do not sign or send transactions.
- Robinhood Chain unlimited approval, automatic retry, and automatic second transactions remain disabled.
- Robinhood Chain live-validated execution is currently bounded to the accepted WETH-USDG BUY / SELL lifecycle; arbitrary custom-pair live execution is not implied.
- Market Cap / Volume windows are discovery and monitoring tools, not order execution tools.
Security notesThis project interacts with trading infrastructure and wallet and account workflows. Treat it accordingly.
Recommended operator posture- use local-only secrets
- review staged diffs before every push
- use separate accounts and wallets for testing
- avoid storing sensitive values in plaintext inside the repo
- keep local DB, backup, and key files outside version control
Important disclaimerThis software is provided for operator workflows and development or testing purposes. Use it at your own risk. Nothing in this repository should be treated as financial advice, investment advice, or a guarantee of trading outcomes.
Development philosophyUTT is being developed as a practical operator terminal with an emphasis on:
- local-first workflows
- unified venue handling
- terminal-style density and control
- security-conscious secret handling
- incremental, surgical changes instead of destructive rewrites
ContributingContributions are best when they are scoped, testable, and operationally safe.
The current contribution model is straightforward:
[list=1]
- open an issue describing the change
- discuss scope before major architectural changes
- avoid committing secrets, runtime data, or local credential material
- keep changes surgical and easy to review
LicenseThis project is licensed under the
MIT License.
See the top-level
LICENSE file for the full license text.
StatusUTT is an actively evolving trading terminal codebase with ongoing work across:
- UI and layout refinement, compact AppHeader tooling, and consistent safety-state presentation
- Counterparty / UniSat mainnet integration, balances, collectibles, market context, dispenser / order modes, unsigned compose review, explicit PSBT signing, separately gated broadcast, All Orders reflection, and BTC accounting previews
- Robinhood Chain chain-ID-4663 integration, MetaMask linkage, Token Registry identity, balances, history, 0x quote discovery, synthetic books, bounded WETH-USDG BUY / SELL execution, non-archive RPC reconciliation, and direction-correct All Orders economics
- OKX balances, orderbooks, rules, fills diagnostics, fill basis preview, live-gated submit, live-gated cancel, and Market Cap / Volume source filtering
- CEX order-economics normalization, fee/net display, and All Orders cancelability
- balance cost basis, average cost, basis badges, lot drilldowns, 1D gain, and total gain display
- Market Cap / Volume owned/unowned and venue/source filters backed by cached market-metrics context
- Solana wallet and router integration
- Polkadot / Hydration UTTT-HDX routing
- Hydration price-cache status, external USD pricing, and UTTT/USD derivation
- Hydration sidecar quote-cache / singleflight / backoff safety
- manual UTTT-HDX orderbook and buy/sell transaction preparation
- confirmed DOT-HDX and HDX-DOT manual Router buy/sell transaction preparation through route-registry rows
- compact Hydration UI status indicators for Order Book and Order Ticket
- Hydration wallet-history ingestion and ledger materialization
- Spread / Bridge transfer-record planning, canonical supply context, and read-only basis/apply previews
- missing-basis lots, transfer-link previews, and explicit-only FIFO lot impact
- LP / Omnipool special handling for pool-token activity
- registry, scanner, Market Cap, and Volume tool windows
- auth, profile, and API-key handling
- venue adapter coverage
- wallet-address and self-custody portfolio visibility
- unified order and ledger workflows
- upcoming Robinhood Chain custom-pair registry discovery, synthetic Order Book generalization, and bounded custom-token execution work
Expect active iteration rather than a frozen, final product. Current live-validated Robinhood Chain execution should be read as the accepted WETH-USDG bounded workflow, not as universal support for every registry pair.