Files

3.8 KiB

Design

Context

See proposal.md and issue #99. The existing worker handles Jupiter wallets and publishes immutable snapshots; EMC already supports multiple venues and stable row reconciliation. Phoenix trader state contains all subaccounts but represents positions in signed base lots rather than USD.

Goals / Non-Goals

Add only read-only REST access and venue-level failure isolation. Keep wallet lists, process names, UI, polling and Jupiter behavior. System documentation in r35157_docs is explicitly in scope. No SDK runtime, trading, streaming or generalized integration framework.

Decisions

  • Add PhoenixPerpsService and PhoenixPerpsPosition in com.r35157.libs.phoenix.perps; implementation in impl.ref. Return subaccount identity, symbol, signed base quantity and current USD size as domain data, without referencing Evelyn or UI types.
  • GET trader/state/{authority}?traderPdaIndex=0, exchange/markets for baseLotsDecimals, and market/{symbol}/mark-price for active symbols. Empty portfolios need only trader state. Reuse each symbol's mark price within a lookup. Use the existing HTTP/Jackson stack with 10-second request timeout and no redirects/retries. All numeric conversion uses BigInteger/BigDecimal; JSON floating numbers must be parsed directly as decimal.
  • Formula verified against official Rise SDK ts/src/units.ts: baseLotsToBaseUnits(lots, decimals) = lots / 10**decimals. USD notional = abs(base quantity) * markPrice.price. This avoids additional Solana PDA derivation needed for trader-view notional. Negative decimal exponents are supported. No use of entryPriceUsd or virtualQuotePositionLots.
  • Validate authority, PDA index, required arrays/numeric fields, duplicate subaccount/position identities and matching market symbols. Missing positions arrays are permitted only as the documented optional field; null/wrong types fail. A malformed nonempty portfolio fails atomically, not partially.
  • Hub supplies one shared read-only Phoenix adapter with no portfolio cache; each Evelyn instance owns independent last-successful state keyed by wallet and venue. Existing single scheduled worker reads Jupiter and Phoenix separately with independent catches; publish ordered Jupiter/Phoenix venues after each success. Interruption ends the cycle.
  • Stable Phoenix IDs are PDA:subaccount:symbol; sequence numbers are not identity. Labels add -PERP only if absent. No new tree level.

Risks / Trade-offs

  • REST prices and position snapshots may come from different slots → periodic current estimates, not an atomic exchange valuation; document this limitation.
  • Public reads currently work without credentials although generated OpenAPI security annotations suggest auth → use observed public access; reject 401/403 and retain data, never attempt signing/login automatically.
  • Up to one mark request per active symbol per wallet → live verification exposed HTTP 429 during bursts. Space request starts at least one second apart in the shared adapter, and honor Retry-After (seconds/HTTP date, 60 seconds fallback). During cooldown fail fast without external calls, preserving venue failure isolation. No automatic retries or generic cache framework. This increases cycle duration before the existing one-minute delay.
  • Previous data may persist on failure → log wallet/venue and document the existing lack of stale indicators.

Migration Plan

No new configuration entries or dependencies. Build and restart through normal deployment after review. Do not start full Hub/trading during verification. Leave changes uncommitted and the OpenSpec change active.

Sources