Skip to content
markpaper

README.md

v0.1.0 · 11.3 KB

Download file
# @markpaper/phoenix-kit

**Summary (EN).** A toolkit for the existing Phoenix perpetuals API and Solana transaction mechanics: exact ticks and lots, public reads, trader snapshots, on-chain account decoding, delegation checks, pending-write memory, and an optional signing process.

---

The TypeScript core has no runtime dependency. It preserves order identities as exact strings, keeps mutable state inside factories, and accepts injected transports and clocks. Timing, retry, freshness, batching, and fee policies are caller supplied. Importing the core performs no network requests and loads no signing key.

## Installation

This package is not published to npm. Build it in the workspace or install an audited local tarball produced by `pnpm pack`:

```powershell
pnpm --filter @markpaper/phoenix-kit build
npm install .\markpaper-phoenix-kit-VERSION.tgz
```

Node.js 22.12 or newer; ESM. The optional signing runtime uses reviewed workspace pins `@ellipsis-labs/rise` 0.5.28, `@solana/kit` 4.0.0 and `ws`. The root pnpm lockfile is the single dependency lock. A package installation with optional dependencies omitted supports the core only. Rise declares a Bun engine requirement; the retained runtime has offline tests under Node, but an engine warning may be emitted during installation.

## Imports

```ts
import { createQuant, createRestClient, account, pending } from '@markpaper/phoenix-kit';
```

| Namespace | Contents |
|---|---|
| `numbers` | Exact decimal arithmetic, u64/i64 parsing, tick and lot conversion |
| `address` | Canonical base58 public keys and signatures |
| `markets` | Metadata decoders, caller-configured cache protection, status, bands, exchange keys and pins |
| `rest` | Public read client, exchange status and mark-price decoding |
| `account` | State/view decoding, stable reads, exact order rows and position sizes |
| `onchain` | Protocol addresses and limits, trader header, capabilities, GlobalConfig, preferences and key-risk verdict |
| `binding` | Independent signer/header checks and a binding gate |
| `ids` | Compound exact order identity; no JavaScript numeric surrogate registry |
| `pending` | Instance-scoped slot gates, placement/cancel echoes and unknown-outcome locks |
| `orders` | Intent IDs, reduce-only sizing, IoC fill evidence, rounding and bounded cancel batches |
| `margin` | Margin rejection classification and free margin from a decoded view |
| `signer` | HTTP client and fail-closed response decoders; no local signing key |
| `ops` | Pure account status checks and a restart placement hold |

The signing process and unsigned delegation builder are explicit imports:

```ts
const runtime = await import('@markpaper/phoenix-kit/experimental/signer');
const { buildDelegateTraderTx } = await import('@markpaper/phoenix-kit/experimental/delegation');
```

## Functions and their knowledge-base sources

| Functions / objects | Knowledge-base file | Topic |
|---|---|---|
| `decimalOf`, `createQuant`, `parseU64`, `parseI64`, `isBidSeq`, integer bounds and `QUOTE_LOTS_PER_USD` | `markets-and-numbers.md` | Exact units and integer identities |
| `base58Decode`, `base58Encode`, `isBase58Pubkey`, `isSignatureString` | `account-and-delegation.md` | Case-sensitive canonical Solana identities |
| `decodeMarkets`, `MARKET_STATUSES`, `assertMarketsSane`, `createMarketCache`, `tradability`, `afterHoursBand`, `decodeExchangeView`, `pinMarkets` | `markets-and-numbers.md` | Market identity, cache freshness and exchange metadata |
| `createRestClient`, `REST_URL`, `decodeMids`, `decodeExchangeStatus`, `PhoenixHttpError` | `api-and-reads.md` | Public reads, retries and status |
| `decodeTraderState`, `decodeTraderView`, `PhoenixReadError`, `positionsToSizes`, `ordersToRows`, `viewContradictsState`, `readGatedState`, `readStableAccount`, `readPositionLots` | `api-and-reads.md` | Complete snapshots, causal gates and plausibility |
| `PROGRAM_ADDRESS`, `LOG_AUTHORITY`, `GLOBAL_CONFIGURATION`, `COMPUTE_BUDGET_PROGRAM`, `TRADER_HEADER`, `TRADER_DISCRIMINANT`, `parseTraderHeader`, `describeCapabilities`, `isFrozen`, `traderRestrictions` | `account-and-delegation.md` | On-chain header and capabilities |
| `GLOBAL_CONFIG_LAYOUT`, `GLOBAL_CONFIG_DISCRIMINANT`, `parseGlobalConfig`, `checkViewKeysAgainstChain`, preference constants, `describePreferences`, `keyRiskOf` | `signing-and-sidecar.md` | Exchange-key cross-check and delegation key risk |
| `configProblems`, `headerProblems`, `headerConfigProblems`, `headerDelegationProblems`, `signerIdentityProblems`, `checkBinding`, `createBindingGate` | `account-and-delegation.md` | Binding proof and write authorization |
| `orderKey`, `isValidOrderId` | `orders.md` | Compound order identity |
| `createWriteMemory` and returned slot, echo, unknown-resolution, placement-lock and band-memory methods | `api-and-reads.md` | Own writes over an indexer snapshot |
| `createIntentIds`, `iocLimitTicks`, `clampIocToBand`, `reducibleLots`, `planReduceOnly`, `resolveIocFill`, `avgFillPx`, `cancelOutcomeFor`, `chunkCancelIds`, `createCancelBatcher`, `isTooManyOrders`, `isSplittableCancelFailure`, `isBandEvidence` | `orders.md` | Order evidence and cancel outcomes |
| `MAX_CANCEL_IDS`, `MAX_TX_BYTES`, `MAX_LIMIT_ORDERS_PER_SIDE` | `orders.md` | Transaction and venue limits |
| `isMarginReject`, `freeMarginUsd` | `margin.md` | Margin rejection and explicit available headroom |
| `createSignerClient`, `decodeCpi`, `decodeWriteAnswer`, `decodeSidecarHealth` | `signing-and-sidecar.md` | Local signing transport and outcome semantics |
| `evaluateAccountStatus`, `exchangeProblems`, `solLow`, `createStartupHold` | `ops.md` | Pure operational checks |
| Optional `createSidecar`, `parseConfig`, `createHttpHandler`, `openJournal`, packet/instruction/transaction builders, return-data/log classifiers and injected RPC adapters | `signing-and-sidecar.md` | One signature per intent and durable resolution |
| Optional `buildDelegateTraderTx` | `account-and-delegation.md` | Owner-signed unsigned delegation transaction |

## Quick examples

None of these examples sends orders by itself.

```ts
const quant = createQuant({ tickSize: market.tickSize, baseLotsDecimals: market.baseLotsDecimals });
const ticks = quant.pxToTicks(requestedPrice, 'floor');
const lots = quant.sizeToLots(requestedSize, 'floor');
// Pass ticks.toString() and lots.toString(); never turn an order sequence into Number.

const client = createRestClient({
  ...config.restPolicy, // all request timing and retry fields are explicit
  fetch: config.fetch,
});
const markets = decodeMarkets(await client.markets());

const memory = pending.createWriteMemory(config.writeMemoryPolicy);
const snapshot = await account.readStableAccount({
  rest: client, memory,
  authority: config.ownerPublicKey, traderPda: config.traderPda,
  catchUpMs: config.catchUpMs, catchUpGapMs: config.catchUpGapMs,
});
if (!snapshot.trusted) throw new Error(snapshot.reason ?? 'Untrusted account snapshot');
// equityAvailable means a view decoded successfully; its slot must be assessed separately.
```

`createMarketCache` requires `ttlMs`, `freshForMs` and `failedBackoffMs`. Its stale fallback is explicit cache safety behavior: failed refreshes retain old metadata, and `fresh()` still becomes false at the caller's age bound. `afterHoursBand` requires that freshness verdict. Isolated-only metadata is reported; this kit's stable read and delegation helpers operate on the cross account at indices 0/0.

`createBindingGate` blocks before its first successful proof. Later unavailable reads retain the previous confirmed verdict. Frozen or reduce-only capabilities are restrictions separate from binding identity.

`createWriteMemory` requires all lag and timing fields. Each instance belongs to one account. Its `placementKeyFor(symbol, side, priceTicks)` and `placementLockOf(key)` are generic placement identities. Feed it only writes with evidence that the account changed. An empty IoC or a cancel of a missing order must not advance a permanent fill gate. Unknown outcomes remain distinct from definite refusals and are never automatically resent by the client.

A signed resting order remains locked until its transaction outcome is resolved; elapsed time cannot prove it was never posted. Resolution requires a response bearing the requested signature. Duplicate intent IDs with conflicting evidence are refused. Unsigned execution-window release depends on the caller's lifetime bounds and an accepted read started after that window; those bounds must cover the configured signer.

## Signer setup

The optional `signer/sidecar.mjs` is experimental and starts only when invoked directly or through `main`. See its README for the required settings. It defaults to readonly. Writes require a durable journal, a caller-defined client-order namespace, explicit fee/compute/timing policies, a delegated key, and an authenticated loopback listener. Its notional caps are disabled unless explicitly supplied.

The unsigned delegation builder verifies the on-chain account header and simulates with `sigVerify:false`. It returns unsigned bytes and simulation diagnostics. The owner wallet remains responsible for reviewing, signing and submitting those bytes.

## What is marked `@experimental`

- The complete optional signing runtime remains opt-in; live behavior may differ from offline mocks.
- After-hours band clamping and band-rejection retry memory: edge inclusivity and fills at the edge are unverified.
- The `TooManyLimitOrders` matcher follows the public Rust event names; the live response form has not been recorded.
- `keyRiskOf` follows the public Rust preference/spot-collateral semantics; the SwapNative attack path has not been verified.
- PostOnly-market packet acceptance, PostOnly reduce-only, conditional-order flags and per-ID cancel compute requirements need venue verification.

Resting reduce-only orders are not assumed to be automatically resized when positions later shrink. Cancel confirmation only counts when `effective` is true and the exact ID is absent from `notFound`. Exact slot agreement is required for delta-only fill evidence.

## Knowledge base

The separately licensed reference is `knowledge/phoenix/` and its README. The core preserves only venue mechanics and generic transport/account functionality.

## Development

```powershell
pnpm --filter @markpaper/phoenix-kit typecheck
pnpm --filter @markpaper/phoenix-kit test
pnpm --filter @markpaper/phoenix-kit build
```

Offline tests include the optional signer, generated account keys, synthetic binary headers/GlobalConfig, packet rules, unknown outcomes, journal persistence and mocked readonly startup. No user snapshots or transaction blobs are included.

The public smoke script requires `PHX_KIT_SMOKE_GAP_MS`, `PHX_KIT_SMOKE_TRIES`, `PHX_KIT_SMOKE_TIMEOUT_MS`, `PHX_KIT_SMOKE_RETRY_MS`, `PHX_KIT_SMOKE_RETRY_AFTER_MAX_MS` and `PHX_KIT_SMOKE_RETRY_AFTER_FALLBACK_MS`. Supply your request policy, then run `pnpm --filter @markpaper/phoenix-kit smoke`. It reads public status, markets, marks and an orderbook only.

## License

Apache License 2.0 — see [LICENSE](LICENSE). Distributions and derivatives must retain [NOTICE](NOTICE) and the “markpaper — phoenix-kit” attribution. The optional SDK dependencies are MIT; their notices are retained under `signer/`. Knowledge-base text is separately CC BY 4.0.

## Disclaimer

Phoenix APIs and protocol behavior can change. Recheck unverified mechanics against official documentation and controlled requests. The software is supplied as is, without warranties; its use is your responsibility.
All files