All checks were successful
deploy / deploy (push) Successful in 48s
Instead of fetching up to 50k inventory snapshots (~442MB), scope the query to only the time range of the current submission batch with a 15-minute buffer. For a typical 1-hour batch this drops from 50k rows to ~300 rows, well within the 1280Mi pod memory limit. The coalesced_at_idx on intent_inventory_snapshots covers the BETWEEN clause so this remains efficient. Proof: history-writer OOM kills from refreshQuoteOutcomes inventory fetch Assumptions: 15min buffer covers the attribution window for all submissions Still fake: heuristic gap outcomes may attribute trades imprecisely
14 KiB
14 KiB
Implementation Turn: Pair-native trade semantics and multi-asset outcome truth
Status: open Opened: 2026-05-18
Goal
Replace hidden BTC/EURe assumptions in shared runtime paths with pair-native asset semantics so maker decisions, taker requests, settlement attribution, valuation visibility, dashboard labels, and alerts work from DB asset/pair config without silently ignoring non-BTC/EURe assets.
Selected backlog items
- none selected; this turn was opened directly from the operator request on 2026-05-18.
Design Rules
- DB asset, pair, strategy config, and price route rows are the canonical runtime model.
- Existing nBTC/EURe behavior and 49 bps edge must survive every refactor.
- nBTC/Ethereum USDC must be a first regression pair because it exposes the current hidden EURe assumptions.
- Legacy fields may remain for old stored history and API compatibility, but new runtime logic must use generic pair-native fields.
- Missing route, stale price, missing decimals, missing inventory, or missing DB config fails closed.
- Tracked assets without valuation remain visible with explicit reasons.
- No new live trading is enabled by import alone.
- No manual deployment or cluster repair is part of this turn.
Problem Statement
The DB-backed configuration turn made assets and pairs durable, but several runtime paths still behave as if there is one preferred BTC/EURe trading path:
- taker preflight parses
amount_eure, requireseure_per_btc, and computes BTC receive from EURe - strategy stores and displays
eure_notionaleven when the quote notional is USDC - outcome attribution takes
btcAssetandeureAssetand ignores other pair assets - portfolio valuation special-cases BTC, EURe, and USDC instead of making unvalued assets explicit
- dashboard request and lifecycle copy still says EURe-to-BTC
- alerts and health still describe stale truth around one
activePair
The implementation should turn the current DB pair model into the actual shared runtime model, not just a dashboard/config layer.
Backend Changes
1. Canonical pair-native trade terms
- Add or consolidate helpers for resolving a directed pair from DB config:
- by
pair_id - by
asset_inandasset_out - by default taker pair when the operator has not specified a pair
- by
- Define normalized runtime terms used by strategy, request preflight, commands, and UI:
pair_id,venuesource_asset_id,destination_asset_idsource_symbol,destination_symbolsource_decimals,destination_decimalssource_amount_units,destination_amount_unitsnotional,notional_asset_id,notional_symbolmax_notional,min_notionalprice_route_id,reference_price_id
- Keep compatibility alias mapping:
amount_eure->source_amountonly for old request callerseure_notionalmay be emitted temporarily only as a deprecated alias when the notional asset is EURemax_notional_euremay be read from old rows but should not be the primary field
- Add tests that new decision/request builders expose generic fields for both nBTC/EURe and nBTC/USDC.
2. Generic route-rate adapter
- Update market price events for current adapters to include generic route fields:
base_asset_idquote_asset_idquote_per_basebase_per_quoteprice_route_idreference_pair
- Preserve old fields such as
eure_per_btcandusdc_per_btcfor stored-row compatibility while new strategy/request code reads generic fields first. - Replace strategy direction names like
btc_to_eureandusdc_to_btcin new logic with route-relative direction:base_to_quotequote_to_base
- Preserve display labels that include actual DB symbols.
- Add tests for BTC/EUR and BTC/USDC adapter payloads and route-relative direction classification.
3. Strategy migration
- Rename internal variables away from
maxNotionalEureandeure_notionalwhere they are not specifically EURe. - Compute fair output/input from the generic route-rate adapter.
- Store new decision fields:
notionalnotional_asset_idnotional_symbolmax_notionalprice_route_idreference_price_id- asset decimals
- Store command fields with enough pair-native data for later attribution:
- source and destination asset ids
- proposed source/destination amounts
- expected inventory delta or enough terms to derive it
- Keep current pair config version and edge bps persistence unchanged.
- Add regression tests:
- nBTC/EURe exact-in and exact-out still produce expected decisions
- nBTC/USDC decision persists USDC notional as
notional_symbol=USDC, not EUR - unsupported route fails closed with
price_route_missingorunsupported_price_route
4. Pair-native taker preflight
- Change request preflight input handling:
- accept
pair_id - accept
asset_inandasset_outas an alternative - accept
source_amountoramount - accept
amount_eureonly as a legacy alias when no generic amount is supplied
- accept
- Resolve the pair from DB config and require taker mode or both mode.
- Use DB asset decimals to parse the source amount.
- Load the latest price for the pair's route, not just latest BTC/EUR price.
- Compute expected destination amount from generic route fields:
- source is route base -> destination = source * quote_per_base
- source is route quote -> destination = source * base_per_quote
- Apply slippage to produce minimum destination amount.
- Persist preflight fields generically:
- pair id/config/version/edge
- source/destination assets and units
- route id/reference price id
- expected/min destination units
- slippage/deadline/signer/verifier
- Keep live submit gated behind existing executor controls.
- Add tests:
- nBTC/EURe legacy alias still works
- nBTC/USDC pair preflight does not require
amount_eureoreure_per_btc - route missing blocks before solver quote
- stale route blocks before solver quote
- insufficient source inventory emits source-asset-specific reason
5. Pair-native outcome attribution
- Refactor quote outcome attribution so the active delta assets are derived from expected command deltas.
- Refactor request outcome attribution so delta assets are derived from preflight source/destination terms.
- Remove
btcAsset/eureAssetparameters from outcome refresh APIs or make them compatibility fallbacks only. - Persist attribution deltas by actual asset id and use DB metadata only for formatting.
- Replace hardcoded humanized text such as "EURe decrease and BTC increase" with generic source/destination wording.
- Add tests:
- maker BTC/USDC command completes when inventory shows BTC decrease and USDC increase, or the correct inverse depending on command side
- taker USDC/BTC request completes from USDC decrease and BTC increase
- unrelated third-asset movements do not create false completion
- ambiguous matching remains ambiguous
- old BTC/EURe rows still normalize for dashboard history
6. Portfolio and valuation visibility
- Make portfolio inputs carry all tracked assets from DB.
- Build valuation assets from available price routes instead of symbol-only checks where practical.
- For this turn, support:
- BTC wrappers valued from BTC/EUR route
- EURe as EUR cash-equivalent
- USDC valued from BTC/EUR plus BTC/USDC route
- For any other tracked asset, include an explicit unvalued entry:
valuation_status=unvaluedvaluation_reason=valuation_route_missing
- Ensure totals include only valued assets and expose unvalued assets separately.
- Add tests:
- tracked USDC remains valued
- a tracked non-BTC/non-EURe/non-USDC asset appears with
valuation_route_missing - no tracked asset disappears from balance rows
7. Dashboard migration
- Request creation UI:
- add pair selection from DB taker-enabled or both-mode pairs
- label amount as "Spend "
- submit
pair_idandsource_amount - remove EURe-to-BTC headers and copy
- Strategy/lifecycle UI:
- display generic notional with
notional_symbol - show pair route and config version from DB
- show settlement deltas from attributed asset metadata
- keep old rows readable when only
eure_notionalexists
- display generic notional with
- Funds/portfolio UI:
- show every tracked asset
- show valuation source or unvalued reason
- show deposit addresses by chain/assets without implying only BTC/EURe
- Status bar:
- replace single "Reference BTC/EUR" tile with route-aware reference status or latest key route labels
- avoid one active-pair wording when multiple pairs are enabled
- Add dashboard tests for request form payloads, generic notional labels, unvalued asset rows, and no hardcoded EURe-to-BTC request title.
8. Alerts and runtime health
- Replace single
activePairalert context with active pair and price-route sets from DB config where service behavior is pair-scoped. - Reference price stale alerts should identify the route or pair whose price is stale.
- Runtime health should not mark all trading truth healthy because the old active pair has a price while another enabled tradeable pair has no fresh route.
- Keep service-level alerts for DB unavailable, ingest disconnected, and inventory stale.
- Add tests for:
- stale route alert scoped to the affected pair
- active pair set shown in service state
- strategy/executor armed critical truth check still fires when any enabled trading pair has critical stale truth
9. Static cleanup and ops compatibility
- Update or retire ops scripts that still read removed pair/asset env vars, especially
scripts/ops/watch_live_mm.py. - Static tests should prevent reintroducing runtime dependencies on
TRADING_BTC_ASSET_ID,TRADING_EURE_ASSET_ID,NEAR_INTENTS_PAIR_FILTER, orSTRATEGY_GROSS_THRESHOLD_PCT. - Keep deployment config free of trading asset/pair/edge env vars.
Data and Persistence
- Prefer additive payload fields over destructive schema rewrites.
- Existing stored rows with
eure_notionalremain readable. - New rows should use generic fields first.
- If a DB schema change is necessary, add it through the tracked schema path and make it idempotent.
- Preserve raw event payloads and normalized records for replay.
Edge Cases
- Pair exists but is observe-only: preflight and trading block with
pair_not_taker_enabledorpair_not_maker_enabled. - Pair has no price route: block with
price_route_missing. - Price route exists but no fresh price event: block with
stale_reference_priceorreference_price_unavailable. - Asset decimals missing: block with
asset_decimals_missing. - Source inventory unavailable or stale: block before solver quote or command.
- Source spend exceeds inventory: block with a source-symbol-specific reason.
- Valuation route missing: asset remains visible as unvalued; no fake zero-value assumption.
- Old rows with BTC/EURe-only fields: dashboard remains readable through compatibility normalizers.
- Multiple enabled pairs: status and alerts show pair/route scope instead of one global active pair.
Concrete Implementation Order
Phase 1. Canonical helpers and route-rate model
- Add pair/route resolution helpers.
- Add generic route-rate extraction with compatibility fallback for current price payloads.
- Add tests for BTC/EUR and BTC/USDC route math.
Phase 2. Strategy generic fields
- Update strategy decision and command payloads to use generic notional and route-relative direction.
- Preserve current nBTC/EURe behavior.
- Add nBTC/USDC strategy regression tests.
Phase 3. Taker preflight
- Update request controller and request helpers to accept pair-native input.
- Compute min destination amount from generic route fields.
- Persist generic preflight fields.
- Add preflight regression tests.
Phase 4. Outcome attribution
- Refactor maker and taker outcome attribution around expected deltas.
- Remove fixed BTC/EURe active-asset assumptions from refresh paths.
- Add BTC/USDC attribution tests and compatibility tests.
Phase 5. Portfolio valuation visibility
- Add valued/unvalued tracked-asset model.
- Preserve current BTC/EURe/USDC valuation.
- Add tests for explicit unvalued tracked assets.
Phase 6. Dashboard and status surfaces
- Update request form, lifecycle tables, strategy tables, funds rows, and status bar.
- Add dashboard tests proving labels come from DB assets and no EURe-to-BTC-only request UI remains.
- Build dashboard bundle.
Phase 7. Alerts, ops scripts, deployment validation
- Make runtime health and alerts pair/route-aware.
- Update stale ops scripts or mark them inactive with tests.
- Run static deployment tests.
- Run full test suite.
- Deploy through repo workflow.
- Validate live dashboard bootstrap, service state, pair config, and current pair quote behavior.
Test Plan
- Generic route-rate unit tests.
- Strategy nBTC/EURe compatibility tests.
- Strategy nBTC/USDC generic notional tests.
- Taker preflight pair-native tests.
- Taker preflight fail-closed tests.
- Outcome attribution generic expected-delta tests.
- Portfolio valued/unvalued tracked-asset tests.
- Dashboard request/lifecycle/funds label tests.
- Alert/runtime-health pair-set tests.
- Static deployment/config tests.
- Full
npm test. - Dashboard build.
Validation Checklist Against The Proof
- nBTC/EURe current pair still loads from DB and uses 49 bps unless operator changed DB config.
- nBTC/USDC pair decisions use
notional_symbol=USDCand do not display as EUR notional. - Request preflight accepts
pair_idplussource_amount. - Request preflight for a non-EURe pair does not require
amount_eure. - Outcome attribution can complete a BTC/USDC row from matching inventory deltas.
- Dashboard has no EURe-to-BTC-only request title or settlement copy in active UI.
- Every tracked asset appears in balances with either value or unvalued reason.
- Alerts and state expose active pair/route scope.
- Deployment config still contains no trading asset/pair/edge env vars.
- Services deploy from repo push only.
Known Fakes Allowed At Start Of This Turn
- Only BTC/EUR and BTC/USDC reference route adapters are real.
- Arbitrary non-BTC asset valuation remains unavailable unless a supported route is configured.
- NEAR Intents remains the only live venue.
- Venue-native terminal fills remain unavailable unless existing relay/status paths already provide them.
- Fee-complete realized PnL remains unavailable.