unrip/archive/implementation/20260518T162356Z-db-backed-asset-registry-and-multi-pair-strategy-configuration-implementation.md
philipp fb547f24d9
All checks were successful
deploy / deploy (push) Successful in 48s
fix: scope inventory fetch by submission time window to prevent OOM
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
2026-06-16 17:17:19 +02:00

207 lines
9.4 KiB
Markdown

# Implementation Turn: DB-backed asset registry and multi-pair strategy configuration
Status: open
Opened: 2026-05-12
## Goal
Move supported assets, enabled pairs, pair filters, and per-pair edge/trading limits out of environment variables and into durable Postgres state, with a re-importable NEAR Intents asset catalog and fail-closed live trading behavior.
## Selected backlog items
- none selected; this turn was opened directly from the approved operator request on 2026-05-12.
## Design rules
- DB state is the source of truth for assets, enabled pairs, edge, limits, and pair modes.
- Env remains only for infra/secrets/service wiring.
- Importing an asset never enables trading by itself.
- Pair config updates are versioned. Historical decisions must remain explainable.
- Services fail closed if DB config is missing, stale, invalid, or internally inconsistent.
- Current nBTC/EURe behavior must survive the migration before any additional pair trades.
## External source
The supported asset catalog comes from the NEAR Intents 1Click tokens endpoint:
```text
GET https://1click.chaindefuser.com/v0/tokens
```
The docs describe this endpoint as the source for supported token `assetId` values. A live check on 2026-05-12 returned an array of 163 token records and included:
- `nep141:nbtc.bridge.near`
- `nep141:btc.omft.near`
- `nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near`
## Backend changes
### 1. Add config schema
Add tables through the tracked schema path:
```sql
supported_asset_import_runs
trading_assets
trading_pairs
pair_strategy_configs
pair_price_routes
pair_config_audit_log
```
Minimum fields:
- `supported_asset_import_runs`: run id, source URL, fetched_at, status, token_count, added_count, updated_count, retired_count, raw response hash, error.
- `trading_assets`: asset id, venue, symbol, label, decimals, blockchain, contract address, latest price, price updated at, supported, retired_at, enabled_for_inventory, raw payload, updated_at.
- `trading_pairs`: pair id, venue, asset_in, asset_out, mode, enabled, status, created_at, updated_at.
- `pair_strategy_configs`: config id, pair id, version, active, edge_bps, max_notional, min_notional, slippage_bps, min_deadline_ms, price_max_age_ms, inventory_max_age_ms, created_at, created_by, reason.
- `pair_price_routes`: route id, pair id, source, base asset id, quote asset id, route config JSON, max age, enabled.
- `pair_config_audit_log`: audit id, entity type, entity id, action, old value, new value, changed_at, changed_by, reason.
### 2. Build supported-token importer
- Add a pure normalizer for 1Click token records.
- Add an idempotent importer that fetches `/v0/tokens`, validates shape, upserts `trading_assets`, and writes `supported_asset_import_runs`.
- Mark previously supported assets as `supported=false` and `retired_at=<run time>` when missing from the latest import.
- Keep raw payloads and source metadata.
- Add an operator-safe command/API to rerun the importer.
- Add tests for success, duplicate import, token update, asset retirement, malformed response, and network failure.
### 3. Seed current production truth
- Seed current nBTC, legacy OMFT BTC, and EURe assets if absent.
- Seed both directed nBTC/EURe pair rows needed by maker quote response.
- Seed the active strategy config at 49 bps.
- Seed current max notional/deadline/freshness settings from existing production defaults.
- Keep legacy OMFT BTC inventory-visible but not active for maker trading unless explicitly enabled.
### 4. Runtime DB config loader
- Add a loader module that returns:
- asset registry map
- enabled pair set
- pair strategy config by pair
- tracked asset ids
- supported/retired flags
- Cache briefly but refresh without restart.
- Expose config load state in service `/state`.
- Fail closed when DB is unreachable or config is invalid.
- Store config version metadata in emitted decisions and commands.
### 5. Replace env-backed pair filtering
- Remove runtime dependency on `NEAR_INTENTS_PAIR_FILTER`.
- `near-intents-ingest` loads enabled observed pairs from DB.
- Ingest should preserve raw quote data but only normalize/publish pairs approved for observation/trading.
- Pair updates must apply without redeploy.
- Keep a safe disabled state when no pairs are configured.
### 6. Replace env-backed strategy edge
- Remove runtime dependency on `STRATEGY_GROSS_THRESHOLD_PCT`.
- Strategy selects the active `pair_strategy_configs` row for the incoming quote pair.
- Decisions include `pair_id`, `pair_config_id`, `pair_config_version`, `edge_bps`, and active limits.
- Unsupported/disabled/unpriced pairs emit explicit rejected decisions, not silent drops where practical.
- Existing nBTC/EURe math remains the first compatibility target.
### 7. Generalize asset and pair math
- Replace `tradingBtc`/`tradingEure` assumptions in strategy with pair assets from DB.
- Use DB decimals for amount conversion.
- Preserve current BTC/EURe reference-price support first.
- Block pairs whose price route is missing or stale.
- Keep pair direction explicit instead of inferring BTC/EURe-only names.
### 8. Price route model
- Current market reference is BTC/EUR-specific.
- Add DB price routes before enabling non-BTC/EURe trading.
- For this turn, support the current nBTC/EURe route and make other pairs blocked with `price_route_missing`.
- Persist enough price-route metadata so later sources can be added without another env-based pair model.
### 9. Inventory and funding visibility
- Inventory sync uses DB `trading_assets` for tracked assets.
- Dashboard shows balances for known/tracked assets including retired assets.
- Funding/deposit observations label assets from DB metadata.
- Assets may be `supported=false` but still inventory-visible.
### 10. Dashboard controls
- Add an asset catalog section:
- import status
- import button/control
- counts for supported/retired/known assets
- searchable asset table
- Add a pair config section:
- pair rows with mode/status
- active strategy config and edge bps
- price route status
- controls to enable observe-only and update edge
- Live trading enablement must remain explicit and separated from import.
- Show pair config version in quote lifecycle rows.
### 11. Alerts and health
- Runtime health uses DB active pair set instead of a single `activePair`.
- Alerts must include pair id where applicable.
- Missing DB config or invalid active pair config becomes a critical blocked state for trading services.
- NEAR upstream incident relevance should be scoped to DB tracked assets/chains.
### 12. Deployment config cleanup
- Remove pair/asset/edge env vars from `deploy/k8s/base/unrip.yaml` after DB seed and runtime loading are in place.
- Keep infra/secrets env vars.
- Ensure a repo push deploys the whole change without manual database editing.
## Concrete implementation order
### Phase 1. Schema and seed
- Add DB schema and helper functions.
- Add seed routine for current nBTC/EURe production config.
- Add tests proving seed is idempotent.
### Phase 2. Asset importer
- Implement 1Click token fetch/normalize/import.
- Add CLI/control path for re-import.
- Add tests for import and retirement semantics.
### Phase 3. Runtime config loader
- Implement DB loader and fail-closed validation.
- Convert asset registry consumers that only need labels/decimals.
- Add tests for missing/invalid config.
### Phase 4. Ingest migration
- Replace env/file pair filter with DB pair set.
- Preserve current pair behavior.
- Add tests for pair update and no-config disabled behavior.
### Phase 5. Strategy migration
- Load pair strategy config from DB.
- Remove global env edge use from runtime.
- Persist pair config version in decisions/commands.
- Add current-pair compatibility and edge-version tests.
### Phase 6. Dashboard and controls
- Add asset import and pair config surfaces.
- Add controls for re-import and observe-only/pair edge update.
- Add tests proving dashboard data comes from DB.
### Phase 7. Deployment cleanup and validation
- Remove asset/pair/edge env vars from deployment config.
- Run targeted tests and full test suite.
- Build dashboard bundle.
- Deploy through repo workflow.
- Validate live importer, current pair config, quote ingest, decisions, and dashboard bootstrap.
## Test plan
- Asset import normalizer tests.
- Import idempotency tests.
- Retired asset tests.
- Schema/seed idempotency tests.
- Runtime config loader tests.
- Fail-closed missing DB config tests.
- Ingest DB pair filter tests.
- Strategy per-pair edge tests.
- Strategy config versioning tests.
- Dashboard asset/pair config tests.
- Deployment config static test proving pair/edge env vars are absent.
## Validation checklist against the proof
- DB contains current assets and current nBTC/EURe pair.
- Supported-token importer can rerun and record a new import run.
- Current active pair and 49 bps edge load from DB.
- NEAR Intents ingest no longer depends on `NEAR_INTENTS_PAIR_FILTER`.
- Strategy no longer depends on `STRATEGY_GROSS_THRESHOLD_PCT`.
- Dashboard shows asset import status and pair strategy config.
- New imported assets are not trade-enabled by default.
- Current pair still emits decisions and quote responses when armed.
- All services deploy from repo push.
## Known fakes allowed at start of this turn
- Only current BTC/EURe pricing route is real.
- Additional imported pairs may be observe-only or blocked until price routes exist.
- Venue-native terminal fills remain unavailable unless existing paths already provide them.
- Fee-complete realized PnL remains unavailable.